diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000000000000000000000000000000000000..9a076e2288ef97a755bdae010e86fcc862c2cdea
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,372 @@
+pipeline {
+    agent {
+       node {
+           label 'ozgcloud-jenkins-build-agent-jdk21'
+        }
+    }
+
+    environment {
+        RELEASE_REGEX = /\d+.\d+.\d+/
+        SNAPSHOT_REGEX = /\d+.\d+.\d+-SNAPSHOT/
+        FAILED_STAGE = ""
+        SH_SUCCESS_STATUS_CODE = 0
+        ELSTER_TRANSFER_OPERATOR_NAME = 'ozgcloud-elster-transfer-operator'
+    }
+
+    options {
+        timeout(time: 1, unit: 'HOURS')
+        disableConcurrentBuilds()
+        buildDiscarder(logRotator(numToKeepStr: '10'))
+    }
+
+    stages {
+        stage('Check Version') {
+            steps {
+                script {
+                    FAILED_STAGE = env.STAGE_NAME
+                    def rootPom = readMavenPom file: 'pom.xml'
+                    def rootVersion = rootPom.version
+
+                    if(isReleaseBranch()){
+                        if ( !isReleaseVersion([rootVersion])) {
+                            error("Keine Release Version für Branch ${env.BRANCH_NAME}.")
+                        }
+                    } else {
+                        if ( !isSnapshotVersion([rootVersion])) {
+                            error("Keine Snapshot Version für Branch ${env.BRANCH_NAME}.")
+                        }
+                    }                   
+                }
+            }
+        }
+
+        stage('Set Version') {
+          when {
+            not {
+                anyOf {
+                    branch 'master'
+                    branch 'release'
+                }
+            }
+          }
+          steps {
+                script {
+                    FAILED_STAGE=env.STAGE_NAME
+                    JAR_TAG = getPomVersion('pom.xml').replace("SNAPSHOT", "${env.BRANCH_NAME}-SNAPSHOT")
+                }
+                configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) {
+                    sh "mvn -s $MAVEN_SETTINGS versions:set -DnewVersion=${JAR_TAG} -DprocessAllModules=true"
+                    
+                }
+          }
+        }
+        stage('Build OzgCloud ELster-Transfer Operator') {
+            steps {
+                script {
+                    FAILED_STAGE=env.STAGE_NAME
+                }
+                configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) {
+                    sh 'mvn -s $MAVEN_SETTINGS clean install -Dmaven.wagon.http.retryHandler.count=3'
+                        
+                    script {
+                      	try {
+	                        if (env.BRANCH_NAME == 'master') {
+		                        withSonarQubeEnv('sonarqube-ozg-sh'){
+		        					sh 'mvn -s $MAVEN_SETTINGS sonar:sonar'	                                
+		                        }
+		                    }
+		                } catch (Exception e) {
+	                        unstable("SonarQube failed")
+	                    }
+                    }
+                }
+            }
+        }
+
+        stage('Deploy to Nexus'){
+            steps {
+                script {
+                    FAILED_STAGE = env.STAGE_NAME
+                }
+                configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) {
+                    sh 'mvn -s $MAVEN_SETTINGS -DskipTests deploy'
+                    sh 'mvn -s $MAVEN_SETTINGS versions:revert'
+                }
+            }
+        }
+
+        stage('Build Docker image') {
+          steps {
+                script {
+                    FAILED_STAGE=env.STAGE_NAME
+                }
+
+                configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) {
+                    sh 'mvn -s $MAVEN_SETTINGS spring-boot:build-image -DskipTests -Dmaven.wagon.http.retryHandler.count=3'
+                }
+            }
+        }
+
+        stage('Tag and Push Docker image') {
+            steps {
+                script {
+                    FAILED_STAGE=env.STAGE_NAME
+                    IMAGE_TAG = buildVersionName()
+
+                    tagAndPushDockerImage(ELSTER_TRANSFER_OPERATOR_NAME, IMAGE_TAG)
+                
+                    if (isMasterBranch()) {
+                        tagAndPushDockerImage(ELSTER_TRANSFER_OPERATOR_NAME, 'snapshot-latest')
+                      
+                    }
+                    else if (isReleaseBranch()) {
+                        tagAndPushDockerImage(ELSTER_TRANSFER_OPERATOR_NAME, 'latest')
+                    }
+                }
+            }
+        }
+
+        stage('Test, build and deploy Elster-Transfer-Operator Helm Chart') {
+            steps {
+                script {
+                    FAILED_STAGE=env.STAGE_NAME
+                    HELM_CHART_VERSION = buildVersionName()
+
+                    sh "./run_helm_test.sh"
+
+                    dir('src/main/helm') {
+
+                        sh "helm package --version=${HELM_CHART_VERSION} ."
+
+                        deployHelmChart(HELM_CHART_VERSION)
+                    }
+                }
+            }
+        }
+        
+
+        stage('Trigger Dev rollout') {
+            when {
+                branch 'master'
+            }
+            steps {
+                script {
+                    FAILED_STAGE = env.STAGE_NAME
+
+                    doDevRollout()
+                }
+            }
+        }
+
+        stage('Trigger Test rollout') {
+            when {
+                branch 'release'
+            }
+            steps {
+                script {
+                    FAILED_STAGE = env.STAGE_NAME
+
+                    doTestRollout()
+                }
+            }
+        }
+        stage ('Deploy SBOM to DependencyTrack') {
+            steps {
+                script {
+                    IMAGE_TAG = buildVersionName()
+
+                    configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) {
+                        withCredentials([string(credentialsId: 'dependency-track-api-key', variable: 'API_KEY')]) {
+
+                            catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
+                                sh "mvn  --no-transfer-progress -s $MAVEN_SETTINGS io.github.pmckeown:dependency-track-maven-plugin:upload-bom -Ddependency-track.apiKey=$API_KEY -Ddependency-track.projectVersion=${IMAGE_TAG} -Ddependency-track.dependencyTrackBaseUrl=https://dependency-track.ozg-sh.de"
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    post {
+        always{
+            junit testResults: '**/target/surefire-reports/*.xml', skipPublishingChecks: true
+        }
+        failure {
+            script {
+                if (env.BRANCH_NAME == 'master' || env.BRANCH_NAME == 'release') {
+                    sendFailureMessage()
+                }
+            }
+        }
+    }
+}
+
+
+Void deployHelmChart(String helmChartVersion) {
+    withCredentials([usernamePassword(credentialsId: 'jenkins-nexus-login', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]){
+       
+        result = sh script: '''curl -u $USERNAME:$PASSWORD https://nexus.ozg-sh.de/service/rest/v1/components?repository=ozg-base-apps-snapshot -F file=@ozgcloud-elster-transfer-operator-'''+helmChartVersion+'''.tgz''', returnStdout: true
+
+        if (result != '') {
+            error(result)
+        }
+    }
+}
+
+String getHelmRepoUrl(){
+    if (isReleaseBranch()) {
+        return "https://nexus.ozg-sh.de/service/rest/v1/components?repository=ozg-base-apps"
+    }
+    return "https://nexus.ozg-sh.de/service/rest/v1/components?repository=ozg-base-apps-snapshot"
+}
+
+String validateBranchName(branchName) {
+    int maxLength = 30
+    if (branchName.length() > maxLength) {
+        String originalBranchName = branchName
+        branchName = branchName.substring(0, maxLength)
+        echo "WARNING: Branch name '${originalBranchName}' exceeded ${maxLength} characters. " +
+             "It has been truncated to '${branchName}' for deployment purposes."
+    }
+    return branchName
+}
+String buildVersionName() {
+    if (env.BRANCH_NAME == 'release') {
+        return getPomVersion('pom.xml')
+    }
+    return "${getPomVersion('pom.xml')}-${validateBranchName(env.BRANCH_NAME)}-${env.GIT_COMMIT.take(7)}".replaceAll("_", "-")
+}
+
+Boolean isMasterBranch() {
+    return env.BRANCH_NAME == 'master'
+}
+
+Boolean isReleaseBranch() {
+    return env.BRANCH_NAME == 'release'
+}
+
+String getElementAccessToken() {
+    withCredentials([string(credentialsId: 'element-login-json', variable: 'LOGIN_JSON')]) {
+        return readJSON ( text: sh (script: '''curl -XPOST -d \"$LOGIN_JSON\" https://matrix.ozg-sh.de/_matrix/client/v3/login''', returnStdout: true)).access_token
+    }
+}
+
+Void doDevRollout() {
+    cloneGitopsRepo()
+    setNewOperatorVersion('dev')
+    pushNewGitopsVersion('dev')
+}
+
+Void doTestRollout() {
+    cloneGitopsRepo()
+    setNewOperatorVersion('test')
+    pushNewGitopsVersion('test')
+}
+
+Void setNewOperatorVersion(String environment) {
+    dir('gitops') {
+        updateElsterTransferOperatorVersions(environment)
+    }
+}
+
+Void updateElsterTransferOperatorVersions(String environment){
+    def envFile = getApplicationValues(environment, ELSTER_TRANSFER_OPERATOR_NAME)
+    def envVersions = readYaml file: envFile
+
+    envVersions.ozgcloud_elster_transfer_operator.image.tag = IMAGE_TAG
+    envVersions.ozgcloud_elster_transfer_operator.helm.version = HELM_CHART_VERSION
+
+    writeYaml file: envFile, data: envVersions, overwrite: true
+}
+
+
+String getApplicationValues(String environment, String valuesFileName) {
+    return "${environment}/application/values/${valuesFileName}-values.yaml"
+}
+
+Void pushNewGitopsVersion(String environment) {
+    dir('gitops') {
+        if (!hasValuesFileChanged(environment)) {
+            return
+        }
+        
+        withCredentials([usernamePassword(credentialsId: 'jenkins-gitea-access-token', passwordVariable: 'TOKEN', usernameVariable: 'USER')]) {            
+            sh "git add ${environment}/application/values/ozgcloud-elster-transfer-operator-values.yaml"
+
+            sh "git commit -m 'jenkins rollout ${environment} ozgcloud operators version ${IMAGE_TAG}'"
+            sh 'git push https://${USER}:${TOKEN}@git.ozg-sh.de/ozgcloud-devops/gitops.git'
+        }
+    }
+}
+
+Boolean hasValuesFileChanged(String environment) {
+    return sh (script: "git status | grep '${environment}/application/values/ozgcloud-elster-transfer-operator-values.yaml'", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer
+}
+
+Void configureGit() {
+    final email = "jenkins@ozg-sh.de"
+    final name = "jenkins"
+
+    dir("gitops") {
+        sh "git config user.email '${email}'"
+        sh "git config user.name '${name}'"
+    }
+}
+
+Void cloneGitopsRepo() {
+    withCredentials([usernamePassword(credentialsId: 'jenkins-gitea-access-token', passwordVariable: 'TOKEN', usernameVariable: 'USER')]) {
+        sh 'git clone https://${USER}:${TOKEN}@git.ozg-sh.de/ozgcloud-devops/gitops.git'
+    }
+
+    configureGit()
+}
+
+Void tagAndPushDockerImage(String imageName, String newTag){
+    withCredentials([usernamePassword(credentialsId: 'jenkins-nexus-login', usernameVariable: 'USER', passwordVariable: 'PASSWORD')]) {
+        sh 'docker login docker.ozg-sh.de -u ${USER} -p ${PASSWORD}'
+
+        sh "docker tag docker.ozg-sh.de/${imageName}:build-latest docker.ozg-sh.de/${imageName}:${newTag}"
+        sh "docker push docker.ozg-sh.de/${imageName}:${newTag}"
+    }
+}
+
+String getPomVersion(String pomFile){
+    def pom = readMavenPom file: pomFile
+
+    return pom.version
+}
+
+Boolean isReleaseVersion(List versions) {
+    return matchRegexVersion(versions, RELEASE_REGEX)
+}
+
+Boolean isSnapshotVersion(List versions) {
+    return matchRegexVersion(versions, SNAPSHOT_REGEX)
+}
+
+Boolean matchRegexVersion(List versions, String regex) {
+    for (version in versions) {
+        if ( !(version ==~ regex) ) {
+            return false
+        }
+    }
+
+    return true
+}
+
+Void sendFailureMessage() {
+    def room = ''
+    def data = """{"msgtype":"m.text", \
+                    "body":"FachstelleServer: Build Failed. Stage: ${FAILED_STAGE} Build-ID: ${env.BUILD_NUMBER} Link: ${BLUE_OCEAN_URL}", \
+                    "format": "org.matrix.custom.html", \
+                    "formatted_body":"FachstelleServer: Build Failed. Stage: ${FAILED_STAGE} Build-ID: <a href='${BLUE_OCEAN_URL}'>${env.BUILD_NUMBER}</a>"}"""
+
+    if (env.BRANCH_NAME == 'master') {
+        room = "!iQPAvQIiRwRpNOszjw:matrix.ozg-sh.de"
+    }
+    else if (env.BRANCH_NAME == 'release') {
+        room = "!oWZpUGTFsxkJIYNfYg:matrix.ozg-sh.de"
+    }
+
+    sh "curl -XPOST -H 'authorization: Bearer ${getElementAccessToken()}' -d '${data}' https://matrix.ozg-sh.de/_matrix/client/v3/rooms/$room/send/m.room.message"
+}
diff --git a/README.md b/README.md
index f8b29dbac8ee924ad91b29cf5910112e1418acd6..c48731195327f9c2e2dd9ecd7d5b230db0d3d6b5 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,3 @@
-# ozgcloud-elster-transfer-operator
-
+# Ozgcloud-Elster-Transfer Operator
 Um die Mandantenfähigkeit des Elster-Transfer Service nutzen zu können, muss für jede Kommune ein Service User im Elster-Transfer angelegt werden.
 Hierfür soll ein neuer Operator für den Elster-Transfer entwickelt werden. Der Operator soll einen neuen Nutzer anlegen und die Logindaten in den Namespace der Kommune ablegen.
\ No newline at end of file
diff --git a/dependency-check-supressions.xml b/dependency-check-supressions.xml
new file mode 100644
index 0000000000000000000000000000000000000000..db1350f8a83ff8c231b055286a017cd62152964f
--- /dev/null
+++ b/dependency-check-supressions.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+    Ministerpräsidenten des Landes Schleswig-Holstein
+    Staatskanzlei
+    Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+
+    Lizenziert unter der EUPL, Version 1.2 oder - sobald
+    diese von der Europäischen Kommission genehmigt wurden -
+    Folgeversionen der EUPL ("Lizenz");
+    Sie dürfen dieses Werk ausschließlich gemäß
+    dieser Lizenz nutzen.
+    Eine Kopie der Lizenz finden Sie hier:
+
+    https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+
+    Sofern nicht durch anwendbare Rechtsvorschriften
+    gefordert oder in schriftlicher Form vereinbart, wird
+    die unter der Lizenz verbreitete Software "so wie sie
+    ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+    ausdrücklich oder stillschweigend - verbreitet.
+    Die sprachspezifischen Genehmigungen und Beschränkungen
+    unter der Lizenz sind dem Lizenztext zu entnehmen.
+
+-->
+<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
+   <suppress>
+      <vulnerabilityName>CVE-DUMMY</vulnerabilityName>
+   </suppress>
+</suppressions>
\ No newline at end of file
diff --git a/lombok.config b/lombok.config
new file mode 100644
index 0000000000000000000000000000000000000000..bf138b0fb9ba70009283b2196d3653bfa3b7e375
--- /dev/null
+++ b/lombok.config
@@ -0,0 +1,31 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
+lombok.log.fieldName=LOG
+lombok.log.slf4j.flagUsage = ERROR
+lombok.log.log4j.flagUsage = ERROR
+lombok.data.flagUsage = ERROR
+lombok.nonNull.exceptionType = IllegalArgumentException
+lombok.addLombokGeneratedAnnotation = true
\ No newline at end of file
diff --git a/mvnw b/mvnw
new file mode 100755
index 0000000000000000000000000000000000000000..66df2854281f4cb6869e4830dd1a7abd1e946c18
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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
+#
+#    https://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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+  if [ -f /usr/local/etc/mavenrc ] ; then
+    . /usr/local/etc/mavenrc
+  fi
+
+  if [ -f /etc/mavenrc ] ; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ] ; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+  CYGWIN*) cygwin=true ;;
+  MINGW*) mingw=true;;
+  Darwin*) darwin=true
+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+    if [ -z "$JAVA_HOME" ]; then
+      if [ -x "/usr/libexec/java_home" ]; then
+        JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+      else
+        JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+      fi
+    fi
+    ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+  if [ -r /etc/gentoo-release ] ; then
+    JAVA_HOME=$(java-config --jre-home)
+  fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+  [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+    JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="$(which javac)"
+  if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=$(which readlink)
+    if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+      if $darwin ; then
+        javaHome="$(dirname "\"$javaExecutable\"")"
+        javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+      else
+        javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+      fi
+      javaHome="$(dirname "\"$javaExecutable\"")"
+      javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+  if [ -n "$JAVA_HOME"  ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+    fi
+  else
+    JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+  if [ -z "$1" ]
+  then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ] ; do
+    if [ -d "$wdir"/.mvn ] ; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=$(cd "$wdir/.." || exit 1; pwd)
+    fi
+    # end of workaround
+  done
+  printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    # Remove \r in case we run on Windows within Git Bash
+    # and check out the repository with auto CRLF management
+    # enabled. Otherwise, we may read lines that are delimited with
+    # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+    # splitting rules.
+    tr -s '\r\n' ' ' < "$1"
+  fi
+}
+
+log() {
+  if [ "$MVNW_VERBOSE" = true ]; then
+    printf '%s\n' "$1"
+  fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+  exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+    log "Found $wrapperJarPath"
+else
+    log "Couldn't find $wrapperJarPath, downloading it ..."
+
+    if [ -n "$MVNW_REPOURL" ]; then
+      wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+    else
+      wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+    fi
+    while IFS="=" read -r key value; do
+      # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+      safeValue=$(echo "$value" | tr -d '\r')
+      case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+      esac
+    done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+    log "Downloading from: $wrapperUrl"
+
+    if $cygwin; then
+      wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+    fi
+
+    if command -v wget > /dev/null; then
+        log "Found wget ... using wget"
+        [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        else
+            wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        fi
+    elif command -v curl > /dev/null; then
+        log "Found curl ... using curl"
+        [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+        else
+            curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+        fi
+    else
+        log "Falling back to using Java to download"
+        javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+        # For Cygwin, switch paths to Windows format before running javac
+        if $cygwin; then
+          javaSource=$(cygpath --path --windows "$javaSource")
+          javaClass=$(cygpath --path --windows "$javaClass")
+        fi
+        if [ -e "$javaSource" ]; then
+            if [ ! -e "$javaClass" ]; then
+                log " - Compiling MavenWrapperDownloader.java ..."
+                ("$JAVA_HOME/bin/javac" "$javaSource")
+            fi
+            if [ -e "$javaClass" ]; then
+                log " - Running MavenWrapperDownloader.java ..."
+                ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+            fi
+        fi
+    fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+  case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+  esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+  wrapperSha256Result=false
+  if command -v sha256sum > /dev/null; then
+    if echo "$wrapperSha256Sum  $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+      wrapperSha256Result=true
+    fi
+  elif command -v shasum > /dev/null; then
+    if echo "$wrapperSha256Sum  $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+      wrapperSha256Result=true
+    fi
+  else
+    echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+    echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+    exit 1
+  fi
+  if [ $wrapperSha256Result = false ]; then
+    echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+    echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+    echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+    exit 1
+  fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  $MAVEN_DEBUG_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100644
index 0000000000000000000000000000000000000000..95ba6f54ac526de46248af840bab26f33f946b93
--- /dev/null
+++ b/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM     e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+    IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Found %WRAPPER_JAR%
+    )
+) else (
+    if not "%MVNW_REPOURL%" == "" (
+        SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+    )
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Couldn't find %WRAPPER_JAR%, downloading it ...
+        echo Downloading from: %WRAPPER_URL%
+    )
+
+    powershell -Command "&{"^
+		"$webclient = new-object System.Net.WebClient;"^
+		"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+		"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+		"}"^
+		"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+		"}"
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Finished downloading %WRAPPER_JAR%
+    )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+    IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+    powershell -Command "&{"^
+       "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+       "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+       "  Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+       "  Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+       "  Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+       "  exit 1;"^
+       "}"^
+       "}"
+    if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+  %JVM_CONFIG_MAVEN_PROPS% ^
+  %MAVEN_OPTS% ^
+  %MAVEN_DEBUG_OPTS% ^
+  -classpath %WRAPPER_JAR% ^
+  "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+  %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..04f57152090fc10b69de0cbcc6b6830b13547052
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0"?>
+<!--
+
+    Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+    Ministerpräsidenten des Landes Schleswig-Holstein
+    Staatskanzlei
+    Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+
+    Lizenziert unter der EUPL, Version 1.2 oder - sobald
+    diese von der Europäischen Kommission genehmigt wurden -
+    Folgeversionen der EUPL ("Lizenz");
+    Sie dürfen dieses Werk ausschließlich gemäß
+    dieser Lizenz nutzen.
+    Eine Kopie der Lizenz finden Sie hier:
+
+    https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+
+    Sofern nicht durch anwendbare Rechtsvorschriften
+    gefordert oder in schriftlicher Form vereinbart, wird
+    die unter der Lizenz verbreitete Software "so wie sie
+    ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+    ausdrücklich oder stillschweigend - verbreitet.
+    Die sprachspezifischen Genehmigungen und Beschränkungen
+    unter der Lizenz sind dem Lizenztext zu entnehmen.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+
+	<parent>
+		<groupId>de.ozgcloud.common</groupId>
+		<artifactId>ozgcloud-common-parent</artifactId>
+		<version>4.6.0</version>
+		<relativePath/>
+	</parent>
+
+	<groupId>de.ozgcloud</groupId>
+	<artifactId>ozgcloud-operator-elster-transfer</artifactId>
+	<version>1.0.0-SNAPSHOT</version>
+
+	<name>OzgCloud Operator Elster-Transfer</name>
+	<description>OzgCloud Operator Elster-Transfer</description>
+	<inceptionYear>2024</inceptionYear>
+
+
+	<properties>
+		<operator-sdk.version>5.6.0</operator-sdk.version>
+		<mustache.version>0.9.14</mustache.version>
+		<snakeyaml.version>2.0</snakeyaml.version>
+		
+		<squareup.okio.version>3.9.1</squareup.okio.version>
+		<spring-boot.build-image.imageName>docker.ozg-sh.de/ozgcloud-elster-transfer-operator:build-latest</spring-boot.build-image.imageName>
+	</properties>
+
+	<dependencies>
+
+		<!-- spring -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>io.javaoperatorsdk</groupId>
+			<artifactId>operator-framework-spring-boot-starter</artifactId>
+			<version>${operator-sdk.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-actuator</artifactId>
+		</dependency>
+         <dependency>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-core</artifactId>
+		</dependency>
+		<!-- tools -->
+		<dependency>
+			<groupId>org.yaml</groupId>
+			<artifactId>snakeyaml</artifactId>
+			<version>${snakeyaml.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.github.spullara.mustache.java</groupId>
+			<artifactId>compiler</artifactId>
+			<version>${mustache.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.squareup.okio</groupId>
+			<artifactId>okio</artifactId>
+			<version>${squareup.okio.version}</version>
+		</dependency>
+
+		<!-- test -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-test</artifactId>
+			<exclusions>
+				<exclusion>
+					<groupId>org.junit.vintage</groupId>
+					<artifactId>junit-vintage-engine</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+	</dependencies>
+
+	<distributionManagement>
+		<repository>
+			<id>ozg-nexus</id>
+			<name>ozg-releases</name>
+			<url>https://nexus.ozg-sh.de/repository/ozg-releases/</url>
+		</repository>
+		<snapshotRepository>
+			<id>ozg-snapshots-nexus</id>
+			<name>ozg-snapshots</name>
+			<url>https://nexus.ozg-sh.de/repository/ozg-snapshots/</url>
+		</snapshotRepository>
+	</distributionManagement>
+
+</project>
diff --git a/run_helm_test.sh b/run_helm_test.sh
new file mode 100755
index 0000000000000000000000000000000000000000..200cdb810dfbd79a48d3246c7522ff81cd0b644b
--- /dev/null
+++ b/run_helm_test.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
+set -e
+
+helm template  ./src/main/helm/ -f src/test/helm/helm-linter-values.yaml
+helm lint -f src/test/helm/helm-linter-values.yaml ./src/main/helm/
+cd src/main/helm && helm unittest -f '../../test/helm/**/*test.yaml'  .
diff --git a/src/main/helm/Chart.yaml b/src/main/helm/Chart.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..84336ad28972a154d0baa36cce54caa3538af1ce
--- /dev/null
+++ b/src/main/helm/Chart.yaml
@@ -0,0 +1,31 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+apiVersion: v2
+name: ozgcloud-elster-transfer-operator
+description: OZG Cloud Elster-Transfer Operator
+type: application
+version: 0.0.0-MANAGED-BY-JENKINS
+appVersion: "0.0.0-MANAGED-BY-JENKINS"
+icon: https://simpleicons.org/icons/helm.svg
\ No newline at end of file
diff --git a/src/main/helm/templates/_helpers.tpl b/src/main/helm/templates/_helpers.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..94704db3d944797dc66a192c9314adc1108d6de3
--- /dev/null
+++ b/src/main/helm/templates/_helpers.tpl
@@ -0,0 +1,58 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+{{/* error check 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec) */}}
+{{/* Namespace */}}
+{{- define "app.namespace" -}}
+{{- if gt (len (.Release.Namespace)) 63 -}}
+{{- fail (printf ".Release.Namespace %s ist zu lang (max. 63 Zeichen)" .Release.Namespace) -}}
+{{- end -}}
+{{ printf "%s" .Release.Namespace }}
+{{- end -}}
+
+
+{{- define "app.matchLabels" }}
+app.kubernetes.io/name: {{ .Release.Name }}
+app.kubernetes.io/namespace: {{ include "app.namespace" . }}
+component: ozgclud-elster-transfer-operator
+{{- end -}}
+
+
+{{- define "app.getCustomList" -}}
+{{- with (.Values.env).customList -}}
+{{- if kindIs "map" . -}}
+{{ include "app.dictToList" . }}
+{{- else if kindIs "slice" . -}}
+{{ . | toYaml }}
+{{- end -}}
+{{- end -}}
+{{- end -}}
+
+{{- define "app.dictToList" -}}
+{{- $customList := list -}}
+{{- range $key, $value := . -}}
+{{- $customList = append $customList (dict "name" $key "value" $value) }}
+{{- end -}}
+{{- $customList | toYaml -}}
+{{- end -}}
\ No newline at end of file
diff --git a/src/main/helm/templates/crds/operator.ozgcloud.de_OzgElsterTransferUser.yaml b/src/main/helm/templates/crds/operator.ozgcloud.de_OzgElsterTransferUser.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..069dbdad721b2257ca022357379f6b0f3123c203
--- /dev/null
+++ b/src/main/helm/templates/crds/operator.ozgcloud.de_OzgElsterTransferUser.yaml
@@ -0,0 +1,67 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: ozgcloudelstertransferusers.operator.ozgcloud.de
+spec:
+  group: operator.ozgcloud.de
+  names:
+    kind: OzgCloudElsterTransferUser
+    listKind: OzgCloudElsterTransferUserList
+    plural: ozgcloudelstertransferusers
+    singular: ozgcloudelstertransferuser
+  scope: Namespaced
+  versions:
+  - name: v1
+    schema:
+      openAPIV3Schema:
+        description: OzgCloudElsterTransferUser is the Schema for the elster-transfer 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 resource this
+              object represents. 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: Spec defines the desired state of Elster-Transfer User
+            type: object
+            x-kubernetes-preserve-unknown-fields: true
+          status:
+            description: Status defines the observed state of Elster-Transfer User
+            type: object
+            x-kubernetes-preserve-unknown-fields: true
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
diff --git a/src/main/helm/templates/deployment.yaml b/src/main/helm/templates/deployment.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..da5afb45286767cacd8a9b7d41c9e2476a8a52c9
--- /dev/null
+++ b/src/main/helm/templates/deployment.yaml
@@ -0,0 +1,77 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ .Release.Name }}
+  namespace: {{ include "app.namespace" . }}
+  labels:
+    app.kubernetes.io/instance: {{ .Release.Name }}
+    app.kubernetes.io/managed-by: {{ .Release.Service }}
+    app.kubernetes.io/name: {{ .Release.Name }}
+    app.kubernetes.io/namespace: {{ include "app.namespace" . }}
+    app.kubernetes.io/part-of: ozg
+    app.kubernetes.io/version: {{ .Chart.AppVersion }}
+    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+spec:
+  selector:
+    matchLabels:
+    {{- include "app.matchLabels" . | indent 6 }}
+  template:
+    metadata:
+      labels:
+      {{- include "app.matchLabels" . | indent 8 }}
+    spec:
+      serviceAccountName: ozgcloud-elster-transfer-operator-service-account
+      containers:
+      - name: ozgcloud-elster-transfer-operator
+        image: "{{ (.Values.image).repo }}/{{ (.Values.image).name }}:{{ coalesce (.Values.image).tag "latest" }}"
+        env:
+        - name: ETR_NAMESPACE
+          value: "{{ .Values.etrNamespace | default "elster-transfer" }}"
+        {{- with include "app.getCustomList" . }}
+{{ . | indent 8 }}
+        {{- end }}
+        imagePullPolicy: Always
+        resources:
+        {{- with .Values.resources }}
+{{ toYaml . | indent 10 }}
+        {{- end }}
+        securityContext:
+          allowPrivilegeEscalation: false
+          privileged: false
+          readOnlyRootFilesystem: false
+          runAsNonRoot: false
+        stdin: true
+        terminationMessagePath: /dev/termination-log
+        terminationMessagePolicy: File
+        tty: true
+      dnsConfig: {}
+      dnsPolicy: ClusterFirst
+      imagePullSecrets:
+      - name: {{ required "imagePullSecret must be set" .Values.imagePullSecret }}
+      restartPolicy: Always
+
diff --git a/src/main/helm/templates/rbacs/ozgcloud_elstertransfer_operator_user_read_write_role.yaml b/src/main/helm/templates/rbacs/ozgcloud_elstertransfer_operator_user_read_write_role.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c505a6aeebea252ddb11d71d46101331ec064a50
--- /dev/null
+++ b/src/main/helm/templates/rbacs/ozgcloud_elstertransfer_operator_user_read_write_role.yaml
@@ -0,0 +1,36 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: ozgcloud-elster-transfer-operator-user-read-write-role
+rules:
+- apiGroups: ["operator.ozgcloud.de"]
+  resources: 
+      - ozgcloudelstertransferusers
+      - ozgcloudelstertransferusers/status
+      - ozgcloudelstertransferusers/finalizers
+  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
diff --git a/src/main/helm/templates/rbacs/ozgcloud_elstertransfer_operator_user_read_write_role_binding.yaml b/src/main/helm/templates/rbacs/ozgcloud_elstertransfer_operator_user_read_write_role_binding.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d04cf44d3213a2b8fcc390c1ca484f10480f359f
--- /dev/null
+++ b/src/main/helm/templates/rbacs/ozgcloud_elstertransfer_operator_user_read_write_role_binding.yaml
@@ -0,0 +1,36 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: ozgcloud-elster-transfer-operator-user-read-write-role-binding
+subjects:
+- kind: ServiceAccount
+  name: ozgcloud-elster-transfer-operator-service-account
+  namespace: {{ include "app.namespace" . }}
+roleRef:
+  kind: ClusterRole
+  name: ozgcloud-elster-transfer-operator-user-read-write-role
+  apiGroup: rbac.authorization.k8s.io
diff --git a/src/main/helm/templates/rbacs/service_account.yaml b/src/main/helm/templates/rbacs/service_account.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6e3aab4ecf629cc7d123ffb33db19344bfb9d72b
--- /dev/null
+++ b/src/main/helm/templates/rbacs/service_account.yaml
@@ -0,0 +1,30 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: ozgcloud-elster-transfer-operator-service-account
+  namespace: {{ include "app.namespace" . }}
\ No newline at end of file
diff --git a/src/main/helm/values.yaml b/src/main/helm/values.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c466957f8d19351b5088307f96e2a329774045bd
--- /dev/null
+++ b/src/main/helm/values.yaml
@@ -0,0 +1,25 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
diff --git a/src/main/java/de/ozgcloud/operator/ElsterTransferOperatorApplication.java b/src/main/java/de/ozgcloud/operator/ElsterTransferOperatorApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3c06f158fcd2e087172051029ff67d18c16a87a
--- /dev/null
+++ b/src/main/java/de/ozgcloud/operator/ElsterTransferOperatorApplication.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ElsterTransferOperatorApplication {
+
+	public static void main(String[] args) {
+		SpringApplication.run(ElsterTransferOperatorApplication.class, args);
+	}
+}
diff --git a/src/main/java/de/ozgcloud/operator/ElsterTransferOperatorConfiguration.java b/src/main/java/de/ozgcloud/operator/ElsterTransferOperatorConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..541d8fb39639e1c4727860fbfcd989fcfca23e87
--- /dev/null
+++ b/src/main/java/de/ozgcloud/operator/ElsterTransferOperatorConfiguration.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator;
+
+import java.time.Duration;
+import java.util.List;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import io.javaoperatorsdk.operator.Operator;
+import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
+
+@Configuration
+public class ElsterTransferOperatorConfiguration {
+
+	public static final Duration RECONCILER_RETRY_SECONDS = Duration.ofSeconds(20);
+	public static final Duration RECONCILER_RETRY_SECONDS_ON_ERROR = Duration.ofSeconds(60);
+
+	@Bean(initMethod = "start", destroyMethod = "stop")
+	@SuppressWarnings("rawtypes")
+	Operator operator(List<Reconciler> controllers) {
+		var operator = new Operator();
+		controllers.forEach(operator::register);
+		return operator;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/de/ozgcloud/operator/elstertransfer/OzgCloudCustomResourceStatus.java b/src/main/java/de/ozgcloud/operator/elstertransfer/OzgCloudCustomResourceStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..6534dd0021eafc78c385421e12a5e9d7c51df995
--- /dev/null
+++ b/src/main/java/de/ozgcloud/operator/elstertransfer/OzgCloudCustomResourceStatus.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer;
+
+public enum OzgCloudCustomResourceStatus {
+	OK, IN_PROGRESS, ERROR;
+}
diff --git a/src/main/java/de/ozgcloud/operator/elstertransfer/user/Constants.java b/src/main/java/de/ozgcloud/operator/elstertransfer/user/Constants.java
new file mode 100644
index 0000000000000000000000000000000000000000..883044e402884e6b20b6c6ca332c507eab941573
--- /dev/null
+++ b/src/main/java/de/ozgcloud/operator/elstertransfer/user/Constants.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+public class Constants {
+
+	public static final String CONFIG_MAP_NAME = "etr-user-config";
+	public static final String USERS_KEY = "users.yaml";
+	public static final String ELSTER_TRANSFER_USER_LOGIN_KEY = "login";
+	public static final String ELSTER_TRANSFER_USER_PASSWORD_KEY = "password";
+	public static final String USER_ROLE = "USER";
+	public static final String ETR_DEPLOYMENT_NAME = "elster-transfer";
+	public static final String MUK_USER_SECRET_NAME = "muk-user-secret";
+
+}
diff --git a/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferConfigMapUserList.java b/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferConfigMapUserList.java
new file mode 100644
index 0000000000000000000000000000000000000000..01df63b7c88a8dcffa19bf58c21edd5c9154ca48
--- /dev/null
+++ b/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferConfigMapUserList.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import lombok.Builder;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+@Builder
+class OzgCloudElsterTransferConfigMapUserList {
+
+	private static final String ELSTER_TRANSFER_USER_LOGIN_KEY = "login";
+	private static final String ELSTER_TRANSFER_USER_ROLE_KEY = "rolle";
+	private static final String ELSTER_TRANSFER_USER_CREDENTIALS_KEY = "credentials";
+	private static final String ELSTER_TRANSFER_USER_GROUP_KEY = "gruppe";
+	private static final String ELSTER_TRANSFER_USER_PASSWORTHASH_KEY = "passwortHash";
+
+	private List<Map<String, Object>> usersList;
+
+	public OzgCloudElsterTransferConfigMapUserList(List<Map<String, Object>> usersList) {
+		this.usersList = new ArrayList<>(usersList);
+	}
+
+	boolean existsUser(String login) {
+		return usersList.stream()
+				.anyMatch(existingUser -> login.equals(existingUser.get(ELSTER_TRANSFER_USER_LOGIN_KEY)));
+	}
+
+	void addUserToList(String login, String passwordHash, String role) {
+		Map<String, Object> formattedUser = Map.of(
+			    ELSTER_TRANSFER_USER_LOGIN_KEY, login,
+				ELSTER_TRANSFER_USER_ROLE_KEY, role,
+				ELSTER_TRANSFER_USER_CREDENTIALS_KEY, Map.of(ELSTER_TRANSFER_USER_PASSWORTHASH_KEY, passwordHash),
+				ELSTER_TRANSFER_USER_GROUP_KEY, login);
+		usersList.add(formattedUser);
+	}
+
+	void removeDeleted(String userLogin) {
+		usersList.removeIf(userMap -> userLogin.equals(userMap.get(ELSTER_TRANSFER_USER_LOGIN_KEY)));
+	}
+
+	public List<Map<String, Object>> getUsersList() {
+		return usersList;
+	}
+
+}
diff --git a/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUser.java b/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUser.java
new file mode 100644
index 0000000000000000000000000000000000000000..a07284b1b91c41f6920100b94f8dcfca62a039db
--- /dev/null
+++ b/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUser.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+import io.fabric8.kubernetes.api.model.Namespaced;
+import io.fabric8.kubernetes.client.CustomResource;
+import io.fabric8.kubernetes.model.annotation.Group;
+import io.fabric8.kubernetes.model.annotation.Kind;
+import io.fabric8.kubernetes.model.annotation.Plural;
+import io.fabric8.kubernetes.model.annotation.Singular;
+import io.fabric8.kubernetes.model.annotation.Version;
+
+@Kind("OzgCloudElsterTransferUser")
+@Group("operator.ozgcloud.de")
+@Version("v1")
+@Singular("ozgcloudelstertransferuser")
+@Plural("ozgcloudelstertransferusers")
+@SuppressWarnings("serial")
+public class OzgCloudElsterTransferUser extends CustomResource<OzgCloudElsterTransferUserSpec, OzgCloudElsterTransferUserStatus>
+		implements Namespaced {
+}
diff --git a/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserReconciler.java b/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserReconciler.java
new file mode 100644
index 0000000000000000000000000000000000000000..8202ae41ee54d9033b6eb66c034f0ee175fe76ab
--- /dev/null
+++ b/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserReconciler.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+import org.springframework.stereotype.Component;
+
+import de.ozgcloud.operator.ElsterTransferOperatorConfiguration;
+import de.ozgcloud.operator.elstertransfer.OzgCloudCustomResourceStatus;
+import io.javaoperatorsdk.operator.api.reconciler.Cleaner;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
+import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
+import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
+import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@RequiredArgsConstructor
+@ControllerConfiguration
+@Component
+public class OzgCloudElsterTransferUserReconciler implements Reconciler<OzgCloudElsterTransferUser>, Cleaner<OzgCloudElsterTransferUser> {
+
+	private final OzgCloudElsterTransferUserService service;
+
+	@Override
+	public UpdateControl<OzgCloudElsterTransferUser> reconcile(OzgCloudElsterTransferUser elsterTransferUser,
+			Context<OzgCloudElsterTransferUser> context) {
+		try {
+			String namespace = elsterTransferUser.getMetadata().getNamespace();
+
+			service.updateConfigMapAndRestartDeploymentAndCreateSecret(namespace);
+
+			return UserUpdateControlBuilder.fromResource(elsterTransferUser)
+					.withStatus(OzgCloudCustomResourceStatus.OK)
+					.withMessage("Status: OK")
+					.withReschedule(ElsterTransferOperatorConfiguration.RECONCILER_RETRY_SECONDS)
+					.build();
+		} catch (Exception e) {
+			LOG.warn("{} could not reconcile in namespace {}: {}.", elsterTransferUser.getMetadata().getName(),
+					elsterTransferUser.getMetadata().getNamespace(), e);
+			return UserUpdateControlBuilder.fromResource(elsterTransferUser)
+					.withStatus(OzgCloudCustomResourceStatus.ERROR)
+					.withMessage(e.getMessage())
+					.withReschedule(ElsterTransferOperatorConfiguration.RECONCILER_RETRY_SECONDS_ON_ERROR)
+					.build();
+		}
+	}
+
+	@Override
+	public DeleteControl cleanup(OzgCloudElsterTransferUser user, Context<OzgCloudElsterTransferUser> context) {
+		LOG.info("{} cleanup...", user.getMetadata().getName());
+		String namespace = user.getMetadata().getNamespace();
+
+		if (user.getSpec().isKeepAfterDelete()) {
+			LOG.info("keep data");
+			return DeleteControl.defaultDelete();
+		}
+
+		try {
+			service.delete(user);
+			return DeleteControl.defaultDelete();
+		} catch (Exception e) {
+			LOG.warn("{} could not delete: {}", namespace, e);
+			return DeleteControl.noFinalizerRemoval().rescheduleAfter(ElsterTransferOperatorConfiguration.RECONCILER_RETRY_SECONDS_ON_ERROR);
+		}
+	}
+
+}
diff --git a/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserRemoteService.java b/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserRemoteService.java
new file mode 100644
index 0000000000000000000000000000000000000000..ffe7d43ec2191ca5a1ce03d6394b4cb480e6d836
--- /dev/null
+++ b/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserRemoteService.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+import java.util.Base64;
+import java.util.Objects;
+
+import org.springframework.stereotype.Service;
+
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
+import io.fabric8.kubernetes.api.model.ConfigMapList;
+import io.fabric8.kubernetes.api.model.Secret;
+import io.fabric8.kubernetes.api.model.SecretBuilder;
+import io.fabric8.kubernetes.api.model.apps.Deployment;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
+import io.fabric8.kubernetes.client.dsl.Resource;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+@Service
+class OzgCloudElsterTransferUserRemoteService {
+
+	private final KubernetesClient client;
+
+	public ConfigMap getConfigMap(String configmapNamespace, String configMapName) {
+		return getConfigMapNonNamespaceOperation(configmapNamespace).withName(configMapName).get();
+	}
+
+	public ConfigMap createConfigMap(String configmapNamespace, String configMapName) {
+		return getConfigMapNonNamespaceOperation(configmapNamespace).resource(buildConfigMap(configMapName)).create();
+	}
+
+	NonNamespaceOperation<ConfigMap, ConfigMapList, Resource<ConfigMap>> getConfigMapNonNamespaceOperation(String configmapNamespace) {
+		return client.configMaps().inNamespace(configmapNamespace);
+	}
+
+	ConfigMap buildConfigMap(String configMapName) {
+		return new ConfigMapBuilder()
+				.withNewMetadata()
+				.withName(configMapName)
+				.endMetadata()
+				.build();
+	}
+
+	public void updateConfigMapData(ConfigMap configMap, String key, String data) {
+		configMap.getData().put(key, data);
+		getConfigMapNonNamespaceOperation(configMap.getMetadata().getNamespace()).resource(configMap).update();
+	}
+
+	public void restartDeployment(String namespace, String deploymentName) {
+		Resource<Deployment> deploymentResource = getDeploymentResource(namespace, deploymentName);
+		Deployment deployment = deploymentResource.get();
+
+		if (Objects.nonNull(deployment)) {
+			setRestartAt(deployment);
+			client.resource(deployment).update();
+		}
+	}
+
+	Resource<Deployment> getDeploymentResource(String namespace, String deploymentName) {
+		return client.apps().deployments().inNamespace(namespace).withName(deploymentName);
+	}
+
+	void setRestartAt(Deployment deployment) {
+		deployment.getSpec().getTemplate().getMetadata().getAnnotations()
+				.put("kubectl.kubernetes.io/restartedAt", String.valueOf(System.currentTimeMillis()));
+	}
+
+	public void createOrUpdateSecret(String namespace, String userPassword, String secretName) {
+		client.secrets().inNamespace(namespace).resource(buildUserSecret(namespace, userPassword, secretName)).createOrReplace();
+	}
+
+	Secret buildUserSecret(String namespace, String userPassword, String secretName) {
+		return new SecretBuilder()
+				.withNewMetadata()
+				.withName(secretName)
+				.endMetadata()
+				.addToData(Constants.ELSTER_TRANSFER_USER_LOGIN_KEY, getEncodedString(namespace.getBytes()))
+				.addToData(Constants.ELSTER_TRANSFER_USER_PASSWORD_KEY, getEncodedString(userPassword.getBytes()))
+				.build();
+	}
+
+	private String getEncodedString(byte[] bytes) {
+		return Base64.getEncoder().encodeToString(bytes);
+	}
+
+}
diff --git a/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserService.java b/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserService.java
new file mode 100644
index 0000000000000000000000000000000000000000..656e07c16803817a8e4b33f96eec147641f9bcb1
--- /dev/null
+++ b/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserService.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.crypto.bcrypt.BCrypt;
+import org.springframework.stereotype.Service;
+import org.yaml.snakeyaml.Yaml;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@RequiredArgsConstructor
+@Service
+class OzgCloudElsterTransferUserService {
+
+	private final OzgCloudElsterTransferUserRemoteService remoteService;
+
+	@Value("${etr.namespace}")
+	private String etrNamespace;
+
+	public void updateConfigMapAndRestartDeploymentAndCreateSecret(String namespace) {
+		LOG.info("Updating/Creating Configmap");
+		if (!userExists(namespace, etrNamespace, Constants.CONFIG_MAP_NAME)) {
+			String userPassword = updateConfigMapAndGenerateUserPassword(namespace, etrNamespace, Constants.CONFIG_MAP_NAME);
+			LOG.info("Restarting Deployment");
+			restartDeployment(etrNamespace, Constants.ETR_DEPLOYMENT_NAME);
+			LOG.info("Creating Secret");
+			createOrUpdateSecret(namespace, userPassword, Constants.MUK_USER_SECRET_NAME);
+
+		}
+	}
+
+	public void delete(OzgCloudElsterTransferUser user) {
+		String namespace = user.getMetadata().getNamespace();
+		if (!userExists(namespace, etrNamespace, Constants.CONFIG_MAP_NAME)) {
+			LOG.info("User not exists");
+			return;
+		}
+		LOG.info("{} do cleanup...", user.getMetadata());
+		deleteUser(namespace, Constants.CONFIG_MAP_NAME);
+		restartDeployment(etrNamespace, Constants.ETR_DEPLOYMENT_NAME);
+	}
+
+	String updateConfigMapAndGenerateUserPassword(String namespace, String configmapNamespace, String configMapName) {
+		ConfigMap configMap = createOrGetConfigMap(configmapNamespace, configMapName);
+		String userPassword = generatePassword();
+		String usersYaml = addUserToUserYaml(getUsersFromConfigMap(configMap), namespace, userPassword);
+		updateConfigMap(configMap, usersYaml);
+		return userPassword;
+	}
+
+	ConfigMap createOrGetConfigMap(String configmapNamespace, String configMapName) {
+		ConfigMap configMap = getConfigMap(configmapNamespace, configMapName);
+		if (Objects.isNull(configMap)) {
+			LOG.debug("Creating ConfigMap '{}' in namespace '{}'", configMapName, configmapNamespace);
+			configMap = remoteService.createConfigMap(configmapNamespace, configMapName);
+		}
+		return configMap;
+	}
+
+	ConfigMap getConfigMap(String configmapNamespace, String configMapName) {
+		return remoteService.getConfigMap(configmapNamespace, configMapName);
+	}
+
+	String addUserToUserYaml(OzgCloudElsterTransferConfigMapUserList users, String namespace, String userPassword) {
+		// use namespace as user "login" and "group"
+		addNewUserToList(users, namespace, hashPassword(userPassword));
+		String usersYaml = constructYamlEntries(users);
+		return usersYaml;
+	}
+
+	void updateConfigMap(ConfigMap configMap, String usersYaml) {
+		remoteService.updateConfigMapData(configMap, Constants.USERS_KEY, usersYaml);
+	}
+
+	void restartDeployment(String etrNamespace, String deploymentName) {
+		remoteService.restartDeployment(etrNamespace, deploymentName);
+	}
+
+	boolean userExists(String userLogin, String configMapNamespace, String configMapName) {
+		ConfigMap configMap = getConfigMap(configMapNamespace, configMapName);
+		if (Objects.isNull(configMap)) {
+			return false;
+		}
+		OzgCloudElsterTransferConfigMapUserList users = getUsersFromConfigMap(configMap);
+		LOG.debug("userExists is '{}' in namespace '{}'", users.existsUser(userLogin), userLogin);
+		return users.existsUser(userLogin);
+
+	}
+
+	void deleteUser(String userLogin, String configMapName) {
+		ConfigMap configMap = getConfigMap(etrNamespace, configMapName);
+
+		OzgCloudElsterTransferConfigMapUserList usersList = getUsersFromConfigMap(configMap);
+		usersList.removeDeleted(userLogin);
+		String updatedUsersYaml = constructYamlEntries(usersList);
+
+		remoteService.updateConfigMapData(configMap, Constants.USERS_KEY, updatedUsersYaml);
+		LOG.info("User with login '{}' removed from configmap successfully", userLogin);
+	}
+
+	void createOrUpdateSecret(String namespace, String userPassword, String secretName) {
+		remoteService.createOrUpdateSecret(namespace, userPassword, secretName);
+		LOG.info("Secret for user in namespace '{}' created successfully", namespace);
+	}
+
+	OzgCloudElsterTransferConfigMapUserList getUsersFromConfigMap(ConfigMap configMap) {
+		String usersYaml = configMap.getData().get(Constants.USERS_KEY);
+		if (Objects.isNull(usersYaml) || usersYaml.isBlank()) {
+			LOG.debug("userYaml is empty");
+			return new OzgCloudElsterTransferConfigMapUserList(new ArrayList<>());
+		}
+		Map<String, Object> load = new Yaml().load(usersYaml);
+		List<Map<String, Object>> usersList = (List<Map<String, Object>>) load.get("users");
+		return new OzgCloudElsterTransferConfigMapUserList(usersList);
+	}
+
+	void addNewUserToList(OzgCloudElsterTransferConfigMapUserList usersList, String login, String passwordHash) {
+		usersList.addUserToList(login, passwordHash, Constants.USER_ROLE);
+	}
+
+	String constructYamlEntries(OzgCloudElsterTransferConfigMapUserList userList) {
+		LOG.info("constructYamlEntries");
+		List<Map<String, Object>> usersList = userList.getUsersList();
+		StringBuilder usersYaml = new StringBuilder();
+		usersYaml.append("fileFormat: 1\nusers:\n");
+
+		for (Map<String, Object> userEntry : usersList) {
+			usersYaml.append(getYamlForUser(userEntry));
+			usersYaml.append(System.lineSeparator());
+		}
+		return usersYaml.toString();
+	}
+
+	String getYamlForUser(Map<String, Object> userEntry) {
+		MustacheFactory mf = new DefaultMustacheFactory();
+		Mustache mustache = mf.compile("users.tmpl");
+
+		StringWriter writer = new StringWriter();
+		mustache.execute(writer, userEntry);
+		return writer.toString();
+	}
+
+	String generatePassword() {
+		return UUID.randomUUID().toString();
+	}
+
+	private String hashPassword(String password) {
+		return BCrypt.hashpw(password, BCrypt.gensalt());
+	}
+
+}
diff --git a/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserSpec.java b/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserSpec.java
new file mode 100644
index 0000000000000000000000000000000000000000..b546fd235d4617975cd6d87d130b8477eda175aa
--- /dev/null
+++ b/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserSpec.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class OzgCloudElsterTransferUserSpec {
+
+	@JsonProperty("keep_after_delete")
+	private boolean keepAfterDelete;
+}
diff --git a/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserStatus.java b/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..1147891a535702e6d048ed883c83fd6b0a945b6f
--- /dev/null
+++ b/src/main/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserStatus.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import de.ozgcloud.operator.elstertransfer.OzgCloudCustomResourceStatus;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class OzgCloudElsterTransferUserStatus {
+
+	private OzgCloudCustomResourceStatus status;
+
+	private String message;
+}
diff --git a/src/main/java/de/ozgcloud/operator/elstertransfer/user/UserUpdateControlBuilder.java b/src/main/java/de/ozgcloud/operator/elstertransfer/user/UserUpdateControlBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..00c4caab8b8b03a91a66795b58ed367545ced319
--- /dev/null
+++ b/src/main/java/de/ozgcloud/operator/elstertransfer/user/UserUpdateControlBuilder.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+import java.time.Duration;
+import java.util.Optional;
+
+import de.ozgcloud.operator.elstertransfer.OzgCloudCustomResourceStatus;
+import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
+
+class UserUpdateControlBuilder {
+
+	private OzgCloudElsterTransferUser resource;
+
+	private OzgCloudCustomResourceStatus status;
+	private Optional<String> message = Optional.empty();
+
+	private boolean reschedule = false;
+	private Duration scheduleDuration;
+
+	public UserUpdateControlBuilder(OzgCloudElsterTransferUser resource) {
+		this.resource = resource;
+	}
+
+	public static UserUpdateControlBuilder fromResource(OzgCloudElsterTransferUser resource) {
+		return new UserUpdateControlBuilder(resource);
+	}
+
+	public UserUpdateControlBuilder withStatus(OzgCloudCustomResourceStatus status) {
+		this.status = status;
+		return this;
+	}
+
+	public UserUpdateControlBuilder withMessage(String message) {
+		this.message = Optional.ofNullable(message);
+		return this;
+	}
+
+	public UserUpdateControlBuilder withReschedule(Duration duration) {
+		this.reschedule = true;
+		this.scheduleDuration = duration;
+		return this;
+	}
+
+	public UpdateControl<OzgCloudElsterTransferUser> build() {
+		resource.setStatus(buildOzgCloudElsterTransferUserStatus());
+
+		return buildUpdateControl();
+	}
+
+	private OzgCloudElsterTransferUserStatus buildOzgCloudElsterTransferUserStatus() {
+		var userStatus = OzgCloudElsterTransferUserStatus.builder().status(status);
+		message.ifPresent(userStatus::message);
+
+		return userStatus.build();
+	}
+
+	private UpdateControl<OzgCloudElsterTransferUser> buildUpdateControl() {
+		if (reschedule) {
+			return UpdateControl.updateStatus(resource).rescheduleAfter(scheduleDuration);
+		}
+		return UpdateControl.updateStatus(resource);
+	}
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7b2723abeeab0b79e11ad6b575d59721fc19bab4
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,25 @@
+
+management:
+  server:
+    port: 8081
+  health:
+    livenessState:
+      enabled: true
+    readinessState:
+      enabled: true
+  endpoint:
+    health:
+      group:
+        exploratory:
+          include: livenessState,readinessState,ping
+          show-details: always
+      probes:
+        enabled: true
+    prometheus:
+      enabled: true
+  endpoints:
+    web:
+      exposure:
+        include: "*"
+etr:
+  namespace: "elster-transfer"
\ No newline at end of file
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000000000000000000000000000000000..93213f612c66446c316d8c6adcfcec3a90b738a8
--- /dev/null
+++ b/src/main/resources/log4j2.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+
+	<Appenders>
+		<Console name="LogInJSON" target="SYSTEM_OUT">
+			<!-- <JsonLayout compact="true" properties="true" eventEol="true"></JsonLayout> -->
+			<JsonTemplateLayout
+				eventTemplateUri="classpath:EcsLayout.json" 
+				maxStringLength="65536" />
+		</Console>
+	</Appenders>
+
+	<Loggers>
+		<Root level="INFO">
+			<appender-ref ref="LogInJSON" />
+		</Root>
+	</Loggers>
+</configuration>
\ No newline at end of file
diff --git a/src/main/resources/users.tmpl b/src/main/resources/users.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..7e4b5c497b25ae2c44c523d0ed2a954bb8b17575
--- /dev/null
+++ b/src/main/resources/users.tmpl
@@ -0,0 +1 @@
+  - { login: "{{login}}", rolle: "{{rolle}}", credentials: { passwortHash: "{{credentials.passwortHash}}" }, gruppe: "{{gruppe}}" }
\ No newline at end of file
diff --git a/src/test/helm/crds/operator.ozgcloud.de_OzgElsterTransferUsers_test.yaml b/src/test/helm/crds/operator.ozgcloud.de_OzgElsterTransferUsers_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..87df7642fb6e93feba27169e6ac071f5f8a61664
--- /dev/null
+++ b/src/test/helm/crds/operator.ozgcloud.de_OzgElsterTransferUsers_test.yaml
@@ -0,0 +1,75 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+suite: operator.ozgcloud.de_OzgCloudElsterTransferUsers test
+templates:
+  - templates/crds/operator.ozgcloud.de_OzgElsterTransferUser.yaml 
+tests:
+  - it: should have apiVersion
+    asserts: 
+      - equal:
+          path: apiVersion
+          value: apiextensions.k8s.io/v1
+  - it: should have isKind of
+    asserts:
+      - isKind:
+          of: CustomResourceDefinition
+
+  - it: should have metadata name
+    asserts:
+      - equal:
+          path: metadata.name
+          value: ozgcloudelstertransferusers.operator.ozgcloud.de
+
+  - it: should have spec group
+    asserts:
+      - equal:
+          path: spec.group
+          value: operator.ozgcloud.de
+  - it: should have spec names kind
+    asserts: 
+      - equal:
+          path: spec.names.kind
+          value: OzgCloudElsterTransferUser
+  - it: should have spec names listKind
+    asserts: 
+      - equal:
+          path: spec.names.listKind
+          value: OzgCloudElsterTransferUserList
+  - it: should have spec names plural
+    asserts: 
+      - equal:
+          path: spec.names.plural
+          value: ozgcloudelstertransferusers
+  - it: should have spec names singular
+    asserts: 
+      - equal:
+          path: spec.names.singular
+          value: ozgcloudelstertransferuser
+
+  - it: should have spec scope
+    asserts: 
+      - equal:
+          path: spec.scope
+          value: Namespaced
\ No newline at end of file
diff --git a/src/test/helm/crds/operator.ozgcloud.de_OzgElsterTransferUsers_versions_test.yaml b/src/test/helm/crds/operator.ozgcloud.de_OzgElsterTransferUsers_versions_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..ed1ef8f5cea8288367ce67a7411980b87ef3b567
--- /dev/null
+++ b/src/test/helm/crds/operator.ozgcloud.de_OzgElsterTransferUsers_versions_test.yaml
@@ -0,0 +1,120 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+suite: operator.ozgcloud.de_OzgCloudElsterTransferUsers versions v1 test
+templates:
+  - templates/crds/operator.ozgcloud.de_OzgElsterTransferUser.yaml
+tests:
+  - it: should have versions name
+    asserts: 
+      - equal:
+          path: spec.versions[0].name
+          value: v1
+  - it: should have versions schema description
+    asserts: 
+      - equal:
+          path: spec.versions[0].schema.openAPIV3Schema.description
+          value: OzgCloudElsterTransferUser is the Schema for the elster-transfer API
+  - it: should have versions schema type
+    asserts: 
+      - equal:
+          path: spec.versions[0].schema.openAPIV3Schema.type
+          value: object
+          
+  - it: should have versions schema apiVersion property type
+    asserts: 
+      - equal:
+          path: spec.versions[0].schema.openAPIV3Schema.properties.apiVersion.type
+          value: string
+  - it: should have versions schema apiVersion property description
+    asserts: 
+      - equal:
+          path: spec.versions[0].schema.openAPIV3Schema.properties.apiVersion.description
+          value: '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'
+              
+  - it: should have versions schema kind property type
+    asserts: 
+      - equal:
+          path: spec.versions[0].schema.openAPIV3Schema.properties.kind.type
+          value: string
+  - it: should have versions schema kind property description
+    asserts: 
+      - equal:
+          path: spec.versions[0].schema.openAPIV3Schema.properties.kind.description
+          value: 'Kind is a string value representing the resource this object represents. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 
+  - it: should have versions schema metadata
+    asserts: 
+      - equal:
+          path: spec.versions[0].schema.openAPIV3Schema.properties.metadata.type
+          value: object
+          
+  - it: should have versions schema spec description
+    asserts: 
+      - equal:
+          path: spec.versions[0].schema.openAPIV3Schema.properties.spec.description
+          value: Spec defines the desired state of Elster-Transfer User
+  - it: should have versions schema spec type
+    asserts: 
+      - equal:
+          path: spec.versions[0].schema.openAPIV3Schema.properties.spec.type
+          value: object
+  - it: should have versions schema spec preserve unknown fields
+    asserts: 
+      - equal:
+          path: spec.versions[0].schema.openAPIV3Schema.properties.spec.x-kubernetes-preserve-unknown-fields
+          value: true
+          
+  - it: should have versions schema status description
+    asserts: 
+      - equal:
+          path: spec.versions[0].schema.openAPIV3Schema.properties.status.description
+          value: Status defines the observed state of Elster-Transfer User
+  - it: should have versions schema status type
+    asserts: 
+      - equal:
+          path: spec.versions[0].schema.openAPIV3Schema.properties.status.type
+          value: object
+  - it: should have versions schema status preserve unknown fields
+    asserts: 
+      - equal:
+          path: spec.versions[0].schema.openAPIV3Schema.properties.status.x-kubernetes-preserve-unknown-fields
+          value: true
+          
+  - it: should have versions served
+    asserts: 
+      - equal:
+          path: spec.versions[0].served
+          value: true
+  - it: should have versions storage
+    asserts: 
+      - equal:
+          path: spec.versions[0].storage
+          value: true
+  - it: should have versions subresources statis
+    asserts: 
+      - equal:
+          path: spec.versions[0].subresources.status
+          value: {}
\ No newline at end of file
diff --git a/src/test/helm/deployment_env_test.yaml b/src/test/helm/deployment_env_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..99c86d81964dd62c66c8896f1280b513c23c551b
--- /dev/null
+++ b/src/test/helm/deployment_env_test.yaml
@@ -0,0 +1,88 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
+
+suite: test environments
+templates:
+  - templates/deployment.yaml
+set:
+  imagePullSecret: "docker-secret"
+tests:
+  - it: check customList as list
+    set:
+      env.customList:
+        - name: my_test_environment_name
+          value: "A test value"
+        - name: test_environment
+          value: "B test value"
+    asserts:
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content:
+            name: my_test_environment_name
+            value: "A test value"
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content:
+            name: test_environment
+            value: "B test value"
+  - it: check customList as dict
+    set:
+      env.customList:
+        my_test_environment_name: "A test value"
+        test_environment: "B test value"
+      image:
+        name: hase
+        tag: latest
+      imagePullSecret: imagePullSecret
+    asserts:
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content:
+            name: my_test_environment_name
+            value: "A test value"
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content:
+            name: test_environment
+            value: "B test value"
+
+  - it: should contain env ETR_NAMESPACE with default value
+    asserts:
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content:
+            name: ETR_NAMESPACE
+            value: "elster-transfer"
+  
+  - it: should set env ETR_NAMESPACE
+    set:
+      etrNamespace: "elster-transfer2"
+    asserts:
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content:
+            name: ETR_NAMESPACE
+            value: "elster-transfer2"
\ No newline at end of file
diff --git a/src/test/helm/deployment_matchlabels_test.yaml b/src/test/helm/deployment_matchlabels_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..32c6a8ba44087bdbccd3673b3b82f36ce480f792
--- /dev/null
+++ b/src/test/helm/deployment_matchlabels_test.yaml
@@ -0,0 +1,45 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
+
+suite: deployment matchlabels
+release:
+  name: ozgcloud-elstertransfer-operator
+  namespace: by-helm-test
+templates:
+  - templates/deployment.yaml
+set:
+  imagePullSecret: "docker-secret"
+tests:
+  - it: should have set kubernetes name
+    asserts:
+      - equal:
+          path: spec.selector.matchLabels["app.kubernetes.io/name"]
+          value: ozgcloud-elstertransfer-operator
+  - it: should have set kubernetes namespace
+    asserts:
+      - equal:
+          path: spec.selector.matchLabels["app.kubernetes.io/namespace"]
+          value: by-helm-test
\ No newline at end of file
diff --git a/src/test/helm/deployment_metadata_labels_test.yaml b/src/test/helm/deployment_metadata_labels_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f9675a5f4ea3a1d8ae6fccae46a4ce382223cb7f
--- /dev/null
+++ b/src/test/helm/deployment_metadata_labels_test.yaml
@@ -0,0 +1,45 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
+
+suite: deployment test metadata labels
+release:
+  name: ozgcloud-elstertransfer-operator
+  namespace: by-helm-test
+templates:
+  - templates/deployment.yaml
+set:
+  imagePullSecret: "docker-secret"
+tests:
+  - it: check default labels namespace
+    asserts:
+      - equal:
+          path: metadata.labels["app.kubernetes.io/namespace"]
+          value: by-helm-test
+  - it: check default labels name
+    asserts:
+      - equal:
+          path: metadata.labels["app.kubernetes.io/name"]
+          value: ozgcloud-elstertransfer-operator
\ No newline at end of file
diff --git a/src/test/helm/deployment_pull_secret_test.yaml b/src/test/helm/deployment_pull_secret_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a4bcf059ac6018067ee3e5b3e39fd167406d9000
--- /dev/null
+++ b/src/test/helm/deployment_pull_secret_test.yaml
@@ -0,0 +1,45 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
+
+suite: deployment pull secret
+release:
+  name: ozgcloud-elstertransfer-operator
+  namespace: by-helm-test
+templates:
+  - deployment.yaml
+
+tests:
+  - it: should set imagePullSecret
+    set:
+      imagePullSecret: imagePullSecret
+    asserts:
+      - equal:
+          path: spec.template.spec.imagePullSecrets[0].name
+          value: imagePullSecret
+  - it: should fail tempalte with error message when not set
+    asserts:
+      - failedTemplate:
+          errorMessage: imagePullSecret must be set
\ No newline at end of file
diff --git a/src/test/helm/deployment_resources_test.yaml b/src/test/helm/deployment_resources_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..ee0c755bccbf77a50c8ceb7adcc8e5407cc52a89
--- /dev/null
+++ b/src/test/helm/deployment_resources_test.yaml
@@ -0,0 +1,66 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
+
+suite: deployment resources test
+release:
+  name: afm-adapter
+templates:
+  - templates/deployment.yaml
+set:
+  imagePullSecret: "docker-secret"
+tests:
+  - it: test resources
+    set:
+      resources:
+        limits:
+          cpu: "11m"
+          memory: "22Mi"
+        requests:
+          cpu: "33m"
+          memory: "44Mi"
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[0].resources.limits.cpu
+          value: 11m
+      - equal:
+          path: spec.template.spec.containers[0].resources.limits.memory
+          value: 22Mi
+      - equal:
+          path: spec.template.spec.containers[0].resources.requests.cpu
+          value: 33m
+      - equal:
+          path: spec.template.spec.containers[0].resources.requests.memory
+          value: 44Mi
+
+  - it: test empty resources
+    set:
+      image:
+        name: hase
+        tag: latest
+    asserts:
+      - isEmpty:
+          path: spec.template.spec.containers[0].resources
+
diff --git a/src/test/helm/deployment_test.yaml b/src/test/helm/deployment_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fe274fcfbc7d541eb0f657e989d4535be7f639f6
--- /dev/null
+++ b/src/test/helm/deployment_test.yaml
@@ -0,0 +1,49 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
+
+suite: deployment test
+release:
+  name: intelliform-adapter
+  namespace: by-helm-test
+templates:
+  - deployment.yaml
+
+tests:
+  - it: validate image type and container image
+    set:
+      imagePullSecret: "docker-secret"
+      image:
+        name: hase
+        tag: latest
+        repo: docker.ozg-sh.de
+    asserts:
+      - isKind:
+          of: Deployment
+      - isAPIVersion:
+          of: apps/v1
+      - equal:
+          path: spec.template.spec.containers[0].image
+          value: docker.ozg-sh.de/hase:latest
diff --git a/src/test/helm/helm-linter-values.yaml b/src/test/helm/helm-linter-values.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..facbeea76465f293365c89542e58e98d8f10459c
--- /dev/null
+++ b/src/test/helm/helm-linter-values.yaml
@@ -0,0 +1,28 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+elstertransfer:
+  namespace: elster-transfer
+
+imagePullSecret: image-pull-secret
\ No newline at end of file
diff --git a/src/test/helm/rbacs/ozgcloud_elstertransfer_operator_user_read_write_role_binding_test.yaml b/src/test/helm/rbacs/ozgcloud_elstertransfer_operator_user_read_write_role_binding_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..ffa6e4c8592665c18158d887220c91a682099d70
--- /dev/null
+++ b/src/test/helm/rbacs/ozgcloud_elstertransfer_operator_user_read_write_role_binding_test.yaml
@@ -0,0 +1,59 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
+suite: ElsterTransfer user rbac test
+release:
+  name: ozgcloud-elstertransfer-operator
+  namespace: test-namespace
+templates:
+  - templates/rbacs/ozgcloud_elstertransfer_operator_user_read_write_role_binding.yaml
+tests:
+  - it: test ClusterRoleBinding metadata
+    asserts:
+      - isKind:
+          of: ClusterRoleBinding
+      - isAPIVersion:
+          of: rbac.authorization.k8s.io/v1
+      - equal:
+          path: metadata.name
+          value: ozgcloud-elster-transfer-operator-user-read-write-role-binding
+ 
+  - it: test ClusterRoleBinding subject
+    asserts:
+      - contains:
+          path: subjects
+          content:
+            kind: ServiceAccount
+            name: ozgcloud-elster-transfer-operator-service-account
+            namespace: test-namespace
+  - it: test ClusterRoleBinding roleRef
+    asserts:
+      - equal:
+          path: roleRef
+          value:
+            kind: ClusterRole
+            name: ozgcloud-elster-transfer-operator-user-read-write-role
+            apiGroup: rbac.authorization.k8s.io
+
diff --git a/src/test/helm/rbacs/ozgcloud_elstertransfer_operator_user_read_write_role_test.yaml b/src/test/helm/rbacs/ozgcloud_elstertransfer_operator_user_read_write_role_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fb80c0af19343d6042c456b5c6dcff8ceb8437e4
--- /dev/null
+++ b/src/test/helm/rbacs/ozgcloud_elstertransfer_operator_user_read_write_role_test.yaml
@@ -0,0 +1,62 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
+suite: ElsterTransfer user rbac test
+release:
+  name: ozgcloud-elstertransfer-operator
+  namespace: test-namespace
+templates:
+  - templates/rbacs/ozgcloud_elstertransfer_operator_user_read_write_role.yaml
+tests:
+
+  - it: test ClusterRole metadata
+    asserts:
+      - isKind:
+          of: ClusterRole
+      - isAPIVersion:
+          of: rbac.authorization.k8s.io/v1
+      - equal:
+          path: metadata.name
+          value: ozgcloud-elster-transfer-operator-user-read-write-role
+    
+  - it: test ClusterRoleBinding rules
+    asserts:
+      - contains:
+          path: rules
+          content:
+              apiGroups:
+                - operator.ozgcloud.de
+              resources:
+                - ozgcloudelstertransferusers
+                - ozgcloudelstertransferusers/status
+                - ozgcloudelstertransferusers/finalizers
+              verbs:
+                - get
+                - list
+                - watch
+                - create
+                - update
+                - patch
+                - delete
\ No newline at end of file
diff --git a/src/test/helm/rbacs/service_account_test.yaml b/src/test/helm/rbacs/service_account_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5c6e43dc607a55f3d5fa674b1499034c541d61e5
--- /dev/null
+++ b/src/test/helm/rbacs/service_account_test.yaml
@@ -0,0 +1,43 @@
+#
+# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+
+
+suite: ServiceAccount test
+release:
+  name: ozgcloud-elstertransfer-operator
+  namespace: test-namespace
+templates:
+  - templates/rbacs/service_account.yaml
+tests:
+  - it: test metadata
+    asserts:
+      - isKind:
+          of: ServiceAccount
+      - equal:
+          path: metadata.name
+          value: ozgcloud-elster-transfer-operator-service-account
+      - equal:
+          path: metadata.namespace
+          value: test-namespace
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferConfigMapFactory.java b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferConfigMapFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..3475586e24bd233d1877e2bd86dc0ff9abe44840
--- /dev/null
+++ b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferConfigMapFactory.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.core.io.ClassPathResource;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
+
+public class OzgCloudElsterTransferConfigMapFactory {
+
+	public static ConfigMap create() {
+		Map<String, String> userData = Map.of(
+			"login", "admin",
+			"role", "ADMIN",
+			"passwordHash", "$2a$12$cqKZMcwTUe/tju7PIFGhperWdV2Xa9o4fVw5eClbzatRhvxZphE1a",
+			"group", "Administratoren"
+		);
+		Map<String, String> data = new HashMap<>();
+		data.put(Constants.USERS_KEY, getYamlContent(userData));
+		return createConfigMap(data);
+	}
+
+	static ConfigMap createConfigMap(Map<String, String> data) {
+		return new ConfigMapBuilder()
+				.withNewMetadata()
+				.withName(Constants.CONFIG_MAP_NAME)
+				.endMetadata()
+				.withData(data)
+				.build();
+	}
+
+	static String getYamlContent(Map<String, String> userData) {
+		MustacheFactory mf = new DefaultMustacheFactory();
+		Mustache mustache = mf.compile("users.yaml");
+		StringWriter writer = new StringWriter();
+		mustache.execute(writer, userData);
+		return writer.toString();
+	}
+
+}
diff --git a/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferConfigMapUserListFactory.java b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferConfigMapUserListFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb271feae5f6336832ff1bffd6c2c455598bd177
--- /dev/null
+++ b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferConfigMapUserListFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+public class OzgCloudElsterTransferConfigMapUserListFactory {
+
+	public static List<Map<String, Object>> create() {
+		Map<String, Object> user1 = Map.of(
+				"login", "user1",
+				"rolle", "USER",
+				"gruppe", "user1",
+				"credentials", Map.of(
+						"passwortHash", "$2a$12$cqKZMcwTUe/tju7PIFGhperWdV2Xa9o4fVw5eClbzatRhvxZphE1a"));
+		List<Map<String, Object>> usersList = Arrays.asList(user1);
+		return usersList;
+	}
+
+}
diff --git a/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferConfigMapUserListTest.java b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferConfigMapUserListTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..2aadbaf64576892ce874dceccf0051b661ce0deb
--- /dev/null
+++ b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferConfigMapUserListTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class OzgCloudElsterTransferConfigMapUserListTest {
+
+	private OzgCloudElsterTransferConfigMapUserList configMapUserList;
+
+	@BeforeEach
+	void setUp() {
+		configMapUserList = new OzgCloudElsterTransferConfigMapUserList(OzgCloudElsterTransferConfigMapUserListFactory.create());
+	}
+
+	@Nested
+	class TestExistsUser {
+		@Test
+		void shouldReturnTrueWhenUserExists() {
+			assertThat(configMapUserList.existsUser("user1")).isTrue();
+		}
+
+		@Test
+		void shouldReturnFalseWhenUserDoesNotExist() {
+			assertThat(configMapUserList.existsUser("nonExistentUser")).isFalse();
+		}
+	}
+
+	@Nested
+	class TestAddUser {
+		@Test
+		void shouldAddUser() {
+			configMapUserList.addUserToList("newUser", "$2a$12$cqKZMcwTUe/tju7PIFGhperWdV2Xa9o4fVw5eClbzatRhvxZphE1a", "USER");
+
+			assertThat(configMapUserList.existsUser("newUser")).isTrue();
+		}
+
+		@Test
+		void shouldAddUserWithCorrectInput() {
+			configMapUserList.addUserToList("newUser", "$2a$12$cqKZMcwTUe/tju7PIFGhperWdV2Xa9o4fVw5eClbzatRhvxZphE1a", "USER");
+			Map<String, Object> addedUser = configMapUserList.getUsersList()
+					.stream()
+					.filter(user -> "newUser".equals(user.get("login")))
+					.findFirst()
+					.orElseThrow();
+
+			assertThat(addedUser.get("rolle")).isEqualTo("USER");
+			assertThat(Map.of("passwortHash", "$2a$12$cqKZMcwTUe/tju7PIFGhperWdV2Xa9o4fVw5eClbzatRhvxZphE1a")).isEqualTo(addedUser.get("credentials"));
+			assertThat("newUser").isEqualTo(addedUser.get("gruppe"));
+		}
+	}
+
+	@Nested
+	class TestRemoveUser {
+		@Test
+		void shouldRemoveUserCorrectly() {
+			configMapUserList.removeDeleted("user1");
+
+			assertThat(configMapUserList.existsUser("user1")).isFalse();
+		}
+	}
+
+	@Nested
+	class TestGetUsersList {
+
+		@Test
+		void shouldReturnCurrentUsersList() {
+			List<Map<String, Object>> userList = configMapUserList.getUsersList();
+			Map<String, Object> user = userList.get(0);
+
+			assertThat(userList).hasSize(1);
+			assertThat(user.get("gruppe")).isEqualTo("user1");
+			assertThat(user.get("login")).isEqualTo("user1");
+			assertThat(user.get("rolle")).isEqualTo("USER");
+			assertThat(user.get("credentials")).isEqualTo(Map.of("passwortHash", "$2a$12$cqKZMcwTUe/tju7PIFGhperWdV2Xa9o4fVw5eClbzatRhvxZphE1a"));
+			assertThat(user.get("gruppe")).isEqualTo("user1");
+		}
+
+		@Test
+		void shouldReturnEmptyListIfNoUsers() {
+			OzgCloudElsterTransferConfigMapUserList emptyConfigMapUserList = new OzgCloudElsterTransferConfigMapUserList(new ArrayList<>());
+
+			assertThat(emptyConfigMapUserList.getUsersList()).isEmpty();
+		}
+	}
+}
diff --git a/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferStatusTestFactory.java b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferStatusTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..35f3d810d7e5d1c588d192ba90345b84ba338399
--- /dev/null
+++ b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferStatusTestFactory.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+public class OzgCloudElsterTransferStatusTestFactory {
+
+	public static OzgCloudElsterTransferUserStatus create() {
+		return createBuilder().build();
+	}
+
+	public static OzgCloudElsterTransferUserStatus.OzgCloudElsterTransferUserStatusBuilder createBuilder() {
+		return OzgCloudElsterTransferUserStatus.builder();
+	}
+}
diff --git a/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserReconcilerTest.java b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserReconcilerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c8a513a81f97fea978f2caeef82fa9f502552ab
--- /dev/null
+++ b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserReconcilerTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.ozgcloud.operator.ElsterTransferOperatorConfiguration;
+import de.ozgcloud.operator.elstertransfer.OzgCloudCustomResourceStatus;
+import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
+
+class OzgCloudElsterTransferUserReconcilerTest {
+
+	@Spy
+	@InjectMocks
+	private OzgCloudElsterTransferUserReconciler reconciler;
+
+	@Mock
+	private OzgCloudElsterTransferUserService service;
+
+	@DisplayName("Reconcile")
+	@Nested
+	class TestReconcile {
+		private final OzgCloudElsterTransferUser user = OzgCloudElsterTransferUserTestFactory.create();
+
+		@Test
+		void shouldCallUpdateConfigMap() {
+			reconciler.reconcile(user, null);
+
+			verify(service).updateConfigMapAndRestartDeploymentAndCreateSecret(OzgCloudElsterTransferUserTestFactory.METADATA_NAMESPACE);
+		}
+
+		@Test
+		void shouldReturnUpdateControl() {
+			var response = reconciler.reconcile(OzgCloudElsterTransferUserTestFactory.create(), null);
+
+			assertThat(response.getResource()).isNotNull();
+		}
+
+
+		@Test
+		void shouldSetStatusOk() {
+			var response = reconciler.reconcile(OzgCloudElsterTransferUserTestFactory.create(), null);
+
+			assertThat(response.getResource().getStatus().getStatus()).isEqualTo(OzgCloudCustomResourceStatus.OK);
+		}
+
+		@Test
+		void shouldRescheduleOnError() {
+			doThrow(RuntimeException.class).when(service)
+					.updateConfigMapAndRestartDeploymentAndCreateSecret(OzgCloudElsterTransferUserTestFactory.METADATA_NAMESPACE);
+
+			var response = reconciler.reconcile(user, null);
+
+		    assertThat(response.getResource().getStatus().getStatus()).isEqualTo(OzgCloudCustomResourceStatus.ERROR);
+		}
+
+		
+
+	}
+
+	@DisplayName("Cleanup")
+	@Nested
+	class TestReconcilerCleanup {
+
+		private final OzgCloudElsterTransferUser user = OzgCloudElsterTransferUserTestFactory.create();
+
+		@Test
+		void shouldCallServiceDelete() {
+			reconciler.cleanup(user, null);
+
+			verify(service).delete(user);
+		}
+
+		@Test
+		void shouldReturnDeleteControlIfErrorNotThrow() {
+			var control = reconciler.cleanup(user, null);
+
+			assertThat(control).usingRecursiveComparison().isEqualTo(DeleteControl.defaultDelete());
+		}
+
+		@Test
+		void shouldRescheduleOnError() {
+			doThrow(RuntimeException.class).when(service)
+					.delete(user);
+
+			var control = reconciler.cleanup(user, null);
+
+			assertThat(control).usingRecursiveComparison()
+					.isEqualTo(DeleteControl.noFinalizerRemoval().rescheduleAfter(ElsterTransferOperatorConfiguration.RECONCILER_RETRY_SECONDS_ON_ERROR));
+		}
+
+	}
+
+}
diff --git a/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserRemoteServiceTest.java b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserRemoteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7e8f0aa875def2ade364546d440901b541441476
--- /dev/null
+++ b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserRemoteServiceTest.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
+import io.fabric8.kubernetes.api.model.ConfigMapList;
+import io.fabric8.kubernetes.api.model.ObjectMeta;
+import io.fabric8.kubernetes.api.model.PodTemplateSpec;
+import io.fabric8.kubernetes.api.model.Secret;
+import io.fabric8.kubernetes.api.model.SecretBuilder;
+import io.fabric8.kubernetes.api.model.SecretList;
+import io.fabric8.kubernetes.api.model.apps.Deployment;
+import io.fabric8.kubernetes.api.model.apps.DeploymentList;
+import io.fabric8.kubernetes.api.model.apps.DeploymentSpec;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.dsl.AppsAPIGroupDSL;
+import io.fabric8.kubernetes.client.dsl.MixedOperation;
+import io.fabric8.kubernetes.client.dsl.NamespaceableResource;
+import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
+import io.fabric8.kubernetes.client.dsl.Resource;
+import io.fabric8.kubernetes.client.dsl.RollableScalableResource;
+
+class OzgCloudElsterTransferUserRemoteServiceTest {
+
+	@Mock
+	private KubernetesClient client;
+
+	@Mock
+	private AppsAPIGroupDSL appsAPIGroupDSL;
+
+	@Spy
+	@InjectMocks
+	private OzgCloudElsterTransferUserRemoteService remoteService;
+
+	@Mock
+	private MixedOperation<Deployment, DeploymentList, RollableScalableResource<Deployment>> deploymentOperation;
+
+	@Mock
+	private RollableScalableResource<Deployment> deploymentResource;
+
+	@Mock
+	private MixedOperation<ConfigMap, ConfigMapList, Resource<ConfigMap>> configMapOperation;
+
+	@Mock
+	private NonNamespaceOperation<ConfigMap, ConfigMapList, Resource<ConfigMap>> nonNamespaceOperation;
+
+	@Mock
+	private Resource<ConfigMap> configMapResource;
+
+	@Mock
+	private MixedOperation<Secret, SecretList, Resource<Secret>> secretOperation;
+
+	@Mock
+	private Resource<Secret> secretResource;
+
+	@Mock
+	private NamespaceableResource<Secret> namespaceableSecret;
+
+	@Mock
+	private NamespaceableResource<Deployment> namespaceableDeployment;
+
+	private static final String namespace = OzgCloudElsterTransferUserTestFactory.METADATA_NAMESPACE;
+	private static final String configMapName = Constants.CONFIG_MAP_NAME;
+	private static final String deploymentName = Constants.ETR_DEPLOYMENT_NAME;
+	private static final String secretName = Constants.MUK_USER_SECRET_NAME;
+	private static final String configmapNamespace = "elster-transfer";
+	private static final String userPassword = "userPassword";
+
+	@DisplayName("test ConfigMap")
+	@Nested
+	class TestConfigMap {
+		@BeforeEach
+		void init() {
+			lenient().when(client.configMaps()).thenReturn(configMapOperation);
+			lenient().when(configMapOperation.inNamespace(configmapNamespace)).thenReturn(nonNamespaceOperation);
+		}
+
+		@DisplayName("get configMap")
+		@Nested
+		class TestGetConfigMap {
+			@Test
+			void shouldCallGetConfigMapNonNamespaceOperation() {
+				when(nonNamespaceOperation.withName(configMapName)).thenReturn(configMapResource);
+				ConfigMap configMap = new ConfigMapBuilder()
+						.withNewMetadata()
+						.withName(configMapName)
+						.endMetadata()
+						.build();
+				when(configMapResource.get()).thenReturn(configMap);
+
+				remoteService.getConfigMap(configmapNamespace, configMapName);
+
+				verify(remoteService).getConfigMapNonNamespaceOperation(configmapNamespace);
+			}
+
+			@Test
+			void shouldCallWithNameGet() {
+				when(nonNamespaceOperation.withName(configMapName)).thenReturn(configMapResource);
+				ConfigMap configMap = new ConfigMapBuilder()
+						.withNewMetadata()
+						.withName(configMapName)
+						.endMetadata()
+						.build();
+				when(configMapResource.get()).thenReturn(configMap);
+
+				remoteService.getConfigMap(configmapNamespace, configMapName);
+
+				verify(nonNamespaceOperation.withName(configMapName)).get();
+			}
+
+			@Test
+			void shouldGetConfigMap() {
+				when(nonNamespaceOperation.withName(configMapName)).thenReturn(configMapResource);
+				ConfigMap configMap = new ConfigMapBuilder()
+						.withNewMetadata()
+						.withName(configMapName)
+						.endMetadata()
+						.build();
+				when(configMapResource.get()).thenReturn(configMap);
+
+				ConfigMap result = remoteService.getConfigMap(configmapNamespace, configMapName);
+
+				assertThat(result.getMetadata().getName()).isEqualTo(configMapName);
+			}
+
+			@DisplayName("getConfigMapNonNamespaceOperation")
+			@Nested
+			class TestgetConfigMapNonNamespaceOperation {
+
+				@Test
+				void shouldCallInNamespace() {
+					when(client.configMaps().inNamespace(configmapNamespace)).thenReturn(nonNamespaceOperation);
+
+					remoteService.getConfigMapNonNamespaceOperation(configmapNamespace);
+
+					verify(client.configMaps()).inNamespace(configmapNamespace);
+				}
+
+				@Test
+				void shouldReturnConfigMapNonNamespaceOperation() {
+					when(client.configMaps().inNamespace(configmapNamespace)).thenReturn(nonNamespaceOperation);
+
+					NonNamespaceOperation<ConfigMap, ConfigMapList, Resource<ConfigMap>> result = remoteService
+							.getConfigMapNonNamespaceOperation(configmapNamespace);
+
+					assertThat(nonNamespaceOperation).isEqualTo(result);
+				}
+			}
+		}
+
+		@DisplayName("create configmap")
+		@Nested
+		class TestCreateConfigMap {
+
+			ConfigMap configMap;
+
+			@BeforeEach
+			void init() {
+				lenient().when(nonNamespaceOperation.resource(any(ConfigMap.class))).thenReturn(configMapResource);
+				configMap = new ConfigMapBuilder()
+						.withNewMetadata()
+						.withName(configMapName)
+						.endMetadata()
+						.build();
+			}
+
+			@Test
+			void shouldCreateConfigMap() {
+				when(configMapResource.create()).thenReturn(configMap);
+				when(configMapResource.create()).thenReturn(configMap);
+
+				ConfigMap result = remoteService.createConfigMap(configmapNamespace, configMapName);
+
+				assertThat(result.getMetadata().getName()).isEqualTo(configMapName);
+			}
+
+			@Test
+			void shouldCallGetConfigMapNonNamespaceOperation() {
+				remoteService.createConfigMap(configmapNamespace, configMapName);
+
+				verify(remoteService).getConfigMapNonNamespaceOperation(configmapNamespace);
+			}
+
+			@Test
+			void shouldCallResourceCreate() {
+				remoteService.createConfigMap(configmapNamespace, configMapName);
+
+				verify(nonNamespaceOperation.resource(configMap)).create();
+			}
+
+			@DisplayName("ConfigMapBuilderTest")
+			@Nested
+			class TestConfigMapBuilder {
+
+				@Test
+				void shouldBuildConfigMap() {
+					ConfigMap result = remoteService.buildConfigMap(configMapName);
+
+					assertThat(result.getMetadata()).isNotNull();
+					assertThat(configMapName).isEqualTo(result.getMetadata().getName());
+				}
+			}
+		}
+
+		@DisplayName("update configmap")
+		@Nested
+		class TestUpdateConfigMap {
+			ConfigMap configMap = new ConfigMap();
+			String key = "test-key";
+			String data = "new-data";
+
+			@BeforeEach
+			void init() {
+				when(nonNamespaceOperation.resource(any(ConfigMap.class))).thenReturn(configMapResource);
+				when(configMapResource.update()).thenReturn(configMap);
+
+				configMap.setMetadata(new io.fabric8.kubernetes.api.model.ObjectMeta());
+				configMap.getMetadata().setNamespace(configmapNamespace);
+			}
+
+			@Test
+			void shouldCallGetConfigMapNonNamespaceOperation() {
+				remoteService.updateConfigMapData(configMap, key, data);
+
+				verify(remoteService).getConfigMapNonNamespaceOperation(configmapNamespace);
+			}
+
+			@Test
+			void shouldCallResourceUpdate() {
+				remoteService.updateConfigMapData(configMap, key, data);
+
+				verify(nonNamespaceOperation.resource(configMap)).update();
+			}
+
+			@Test
+			void shouldUpdateConfigMapData() {
+				remoteService.updateConfigMapData(configMap, key, data);
+
+				assertThat(configMap.getData()).containsKey(key);
+				assertThat(configMap.getData().get(key)).isEqualTo(data);
+			}
+		}
+
+	}
+
+	@DisplayName("restart deployment")
+	@Nested
+	class TestRestartDeployment {
+		Deployment deployment = new Deployment();
+
+		@BeforeEach
+		void setUp() {
+			deployment.setSpec(new DeploymentSpec());
+			deployment.getSpec().setTemplate(new PodTemplateSpec());
+			deployment.getSpec().getTemplate().setMetadata(new ObjectMeta());
+		}
+
+		@DisplayName("update deployment")
+		@Nested
+		class TestUpdateDeployment {
+
+			@BeforeEach
+			void setUp() {
+				when(client.apps()).thenReturn(appsAPIGroupDSL);
+				when(appsAPIGroupDSL.deployments()).thenReturn(deploymentOperation);
+				when(deploymentOperation.inNamespace(anyString())).thenReturn(deploymentOperation);
+				when(deploymentOperation.withName(anyString())).thenReturn(deploymentResource);
+			}
+
+			@Test
+			void shouldCallUpdateDeployment() {
+				when(deploymentResource.get()).thenReturn(deployment);
+				when(client.resource(deployment)).thenReturn(namespaceableDeployment);
+
+				remoteService.restartDeployment(namespace, deploymentName);
+
+				verify(client.resource(deployment)).update();
+
+			}
+
+			@Test
+			void shouldCallSetRestartAt() {
+				when(deploymentResource.get()).thenReturn(deployment);
+				when(client.resource(deployment)).thenReturn(namespaceableDeployment);
+
+				remoteService.restartDeployment(namespace, deploymentName);
+
+				verify(remoteService).setRestartAt(deployment);
+			}
+
+			@Test
+			void shouldCallGetDeployment() {
+				remoteService.restartDeployment(namespace, deploymentName);
+
+				verify(deploymentResource).get();
+			}
+
+			@Test
+			void shouldCallGetDeploymentResource() {
+				remoteService.restartDeployment(namespace, deploymentName);
+
+				verify(remoteService).getDeploymentResource(namespace, deploymentName);
+			}
+
+		}
+
+		@DisplayName("set restart flag")
+		@Nested
+		class TestsetRestartAt {
+			@Test
+			void shouldSetRestartedAtAnnotation() {
+				Map<String, String> annotations = new HashMap<>();
+				deployment.getSpec().getTemplate().getMetadata().setAnnotations(annotations);
+
+				remoteService.setRestartAt(deployment);
+
+				assertThat(annotations).containsKey("kubectl.kubernetes.io/restartedAt");
+				assertThat(annotations.get("kubectl.kubernetes.io/restartedAt")).isNotNull();
+			}
+		}
+
+		@DisplayName("getDeploymentResource")
+		@Nested
+		class TestGetDeploymentResource {
+			@Test
+			void shouldGetDeploymentResource() {
+				when(client.apps()).thenReturn(appsAPIGroupDSL);
+				when(appsAPIGroupDSL.deployments()).thenReturn(deploymentOperation);
+				when(deploymentOperation.inNamespace(anyString())).thenReturn(deploymentOperation);
+				when(deploymentOperation.withName(anyString())).thenReturn(deploymentResource);
+
+				Resource<Deployment> result = remoteService.getDeploymentResource(namespace, deploymentName);
+
+				assertThat(result).isEqualTo(deploymentResource);
+			}
+
+			@Test
+			void shouldCallAppsDeploymentsInNamespaceWithName() {
+				when(client.apps()).thenReturn(appsAPIGroupDSL);
+				when(appsAPIGroupDSL.deployments()).thenReturn(deploymentOperation);
+				when(deploymentOperation.inNamespace(anyString())).thenReturn(deploymentOperation);
+				when(deploymentOperation.withName(anyString())).thenReturn(deploymentResource);
+
+				remoteService.getDeploymentResource(namespace, deploymentName);
+
+				verify(client).apps();
+				verify(client.apps()).deployments();
+				verify(client.apps().deployments()).inNamespace(namespace);
+				verify(client.apps().deployments().inNamespace(namespace)).withName(deploymentName);
+			}
+
+		}
+	}
+
+	@DisplayName("createOrUpdateSecret")
+	@Nested
+	class TestCreateOrUpdateSecret {
+		Secret secret;
+
+		@BeforeEach
+		void setUp() {
+			secret = new SecretBuilder()
+					.withNewMetadata()
+					.withName(secretName)
+					.endMetadata()
+					.addToData("login", Base64.getEncoder().encodeToString(namespace.getBytes()))
+					.addToData("password", Base64.getEncoder().encodeToString(userPassword.getBytes()))
+					.build();
+		}
+
+		@Test
+		void shouldCallBuildUserSecret() {
+			when(client.secrets()).thenReturn(secretOperation);
+			when(secretOperation.inNamespace(anyString())).thenReturn(secretOperation);
+			when(secretOperation.resource(any(Secret.class))).thenReturn(secretResource);
+
+			remoteService.createOrUpdateSecret(namespace, userPassword, secretName);
+
+			verify(remoteService).buildUserSecret(namespace, userPassword, secretName);
+		}
+
+		@Test
+		void shouldCallServerSideApply() {
+			when(client.secrets()).thenReturn(secretOperation);
+			when(secretOperation.inNamespace(anyString())).thenReturn(secretOperation);
+			when(secretOperation.resource(any(Secret.class))).thenReturn(secretResource);
+
+			remoteService.createOrUpdateSecret(namespace, userPassword, secretName);
+
+			verify(client.secrets().inNamespace(namespace).resource(secret)).createOrReplace();
+		}
+
+		@DisplayName("buildUserSecret")
+		@Nested
+		class TestBuildUserSecret {
+
+			@Test
+			void shouldCallCreateOrUpdateSecret() {
+				Secret secret = remoteService.buildUserSecret(namespace, userPassword, secretName);
+
+				assertThat(secret.getMetadata()).isNotNull();
+				assertThat(secretName).isEqualTo(secret.getMetadata().getName());
+				assertThat(secret.getData().containsKey(Constants.ELSTER_TRANSFER_USER_LOGIN_KEY)).isTrue();
+				assertThat(secret.getData().containsKey(Constants.ELSTER_TRANSFER_USER_PASSWORD_KEY)).isTrue();
+				assertThat(secret.getData().get(Constants.ELSTER_TRANSFER_USER_LOGIN_KEY))
+						.isEqualTo(Base64.getEncoder().encodeToString(namespace.getBytes()));
+				assertThat(secret.getData().get(Constants.ELSTER_TRANSFER_USER_PASSWORD_KEY))
+						.isEqualTo(Base64.getEncoder().encodeToString(userPassword.getBytes()));
+			}
+		}
+	}
+
+}
diff --git a/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserServiceTest.java b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..1439d44af50df50cfed74c0e1c66cb66bc0d43f4
--- /dev/null
+++ b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserServiceTest.java
@@ -0,0 +1,614 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import io.fabric8.kubernetes.api.model.ConfigMap;
+
+class OzgCloudElsterTransferUserServiceTest {
+
+	@Mock
+	private OzgCloudElsterTransferUserRemoteService remoteService;
+
+	@Spy
+	@InjectMocks
+	private OzgCloudElsterTransferUserService service;
+
+	@Mock
+	private ConfigMap configMap;
+
+	@Mock
+	private OzgCloudElsterTransferConfigMapUserList users;
+
+	private static final String namespace = OzgCloudElsterTransferUserTestFactory.METADATA_NAMESPACE;
+	private static final String configMapName = Constants.CONFIG_MAP_NAME;
+	private static final String deploymentName = Constants.ETR_DEPLOYMENT_NAME;
+	private static final String secretName = Constants.MUK_USER_SECRET_NAME;
+	private static final String configMapNamespace = "elster-transfer";
+	private static final String usersKey = Constants.USERS_KEY;
+	private static final String userPassword = "userPassword";
+
+	@Nested
+	class TestUpdateConfigMapAndRestartDeploymentAndCreateSecret {
+
+		@BeforeEach
+		void setUp() {
+			configMap = OzgCloudElsterTransferConfigMapFactory.create();
+			lenient().when(remoteService.getConfigMap(anyString(),
+					anyString())).thenReturn(configMap);
+			ReflectionTestUtils.setField(service, "etrNamespace", "elster-transfer");
+		}
+
+		@Test
+		void shouldCallUserExists() {
+			service.updateConfigMapAndRestartDeploymentAndCreateSecret(namespace);
+
+			verify(service).userExists(namespace, configMapNamespace, configMapName);
+		}
+
+		@Test
+		void shouldCallupdateConfigMapAndGenerateUserPasswordIfUserNotExists() {
+			service.updateConfigMapAndRestartDeploymentAndCreateSecret(namespace);
+
+			verify(service).updateConfigMapAndGenerateUserPassword(namespace,
+					configMapNamespace, configMapName);
+		}
+
+		@Test
+		void shouldCallRestartDeploymentIfUserNotExists() {
+			service.updateConfigMapAndRestartDeploymentAndCreateSecret(namespace);
+
+			verify(service).restartDeployment(configMapNamespace, deploymentName);
+		}
+
+		@Test
+		void shouldCallCreateOrUpdateSecretIfUserNotExists() {
+			when(service.updateConfigMapAndGenerateUserPassword(namespace,
+					configMapNamespace, configMapName)).thenReturn(userPassword);
+
+			service.updateConfigMapAndRestartDeploymentAndCreateSecret(namespace);
+
+			verify(service).createOrUpdateSecret(namespace, userPassword, secretName);
+		}
+
+		@Test
+		void shouldNotCallRestartDeploymentIfUserExists() {
+			when(service.userExists(namespace, configMapNamespace, configMapName)).thenReturn(true);
+
+			service.updateConfigMapAndRestartDeploymentAndCreateSecret(namespace);
+
+			verify(service, never()).restartDeployment(any(), any());
+		}
+
+		@Test
+		void shouldNotCallUpdateConfigMapAndGenerateUserPasswordIfUserExists() {
+			when(service.userExists(namespace, configMapNamespace, configMapName)).thenReturn(true);
+
+			service.updateConfigMapAndRestartDeploymentAndCreateSecret(namespace);
+
+			verify(service, never()).updateConfigMapAndGenerateUserPassword(any(), any(), any());
+		}
+
+		@Test
+		void shouldNotCallCreateOrUpdateSecretIfUserExists() {
+			when(service.userExists(namespace, configMapNamespace, configMapName)).thenReturn(true);
+
+			service.updateConfigMapAndRestartDeploymentAndCreateSecret(namespace);
+
+			verify(service, never()).createOrUpdateSecret(any(), any(), any());
+		}
+
+		@Nested
+		class TestUpdateConfigMapAndGenerateUserPassword {
+
+			@Test
+			void shouldCallCreateOrGetConfigMap() {
+				service.updateConfigMapAndGenerateUserPassword(namespace, configMapNamespace, configMapName);
+
+				verify(service).createOrGetConfigMap(configMapNamespace, configMapName);
+			}
+
+			@Test
+			void shouldCallGeneratePassword() {
+				service.updateConfigMapAndGenerateUserPassword(namespace, configMapNamespace, configMapName);
+
+				verify(service).generatePassword();
+			}
+
+			@Test
+			void shouldCallAddUserToUserYaml() {
+				service.updateConfigMapAndGenerateUserPassword(namespace, configMapNamespace, configMapName);
+
+				verify(service).addUserToUserYaml(any(), any(), any());
+			}
+
+			@Test
+			void shouldCallGetUsersFromConfigMap() {
+				when(service.createOrGetConfigMap(configMapNamespace, configMapName)).thenReturn(configMap);
+
+				service.updateConfigMapAndGenerateUserPassword(namespace, configMapNamespace, configMapName);
+
+				verify(service).getUsersFromConfigMap(configMap);
+			}
+
+			@Test
+			void shouldCallUpdateConfigMap() {
+				String usersYaml = "usersYaml";
+				when(service.createOrGetConfigMap(configMapNamespace, configMapName)).thenReturn(configMap);
+				when(service.generatePassword()).thenReturn(userPassword);
+				when(service.addUserToUserYaml(users, namespace, userPassword)).thenReturn(usersYaml);
+				when(service.getUsersFromConfigMap(configMap)).thenReturn(users);
+
+				service.updateConfigMapAndGenerateUserPassword(namespace, configMapNamespace, configMapName);
+
+				verify(service).updateConfigMap(configMap, usersYaml);
+			}
+
+			@Test
+			void shouldReturnUserPassword() {
+				when(service.generatePassword()).thenReturn(userPassword);
+
+				String result = service.updateConfigMapAndGenerateUserPassword(namespace, configMapNamespace, configMapName);
+
+				assertThat(result).isEqualTo(userPassword);
+			}
+
+			@Nested
+			class TestCreateOrGetConfigMap {
+
+				@Test
+				void shouldCallGetConfigMap() {
+					service.createOrGetConfigMap(configMapNamespace, configMapName);
+
+					verify(service).getConfigMap(configMapNamespace, configMapName);
+				}
+
+				@Test
+				void shouldCallCreateConfigMap() {
+					when(service.getConfigMap(configMapNamespace, configMapName)).thenReturn(null);
+
+					service.createOrGetConfigMap(configMapNamespace, configMapName);
+
+					verify(remoteService).createConfigMap(configMapNamespace, configMapName);
+				}
+
+				@Test
+				void shouldCreateConfigMap() {
+					when(service.getConfigMap(configMapNamespace, configMapName)).thenReturn(null);
+					when(remoteService.createConfigMap(configMapNamespace, configMapName)).thenReturn(configMap);
+
+					ConfigMap result = service.createOrGetConfigMap(configMapNamespace, configMapName);
+
+					assertThat(result).isEqualTo(configMap);
+
+				}
+
+				@Test
+				void shouldNotCallCreateConfigMap() {
+					when(service.getConfigMap(configMapNamespace, configMapName)).thenReturn(configMap);
+
+					service.createOrGetConfigMap(configMapNamespace, configMapName);
+
+					verify(remoteService, never()).createConfigMap(any(), any());
+				}
+
+				@Nested
+				class TestGetConfigMap {
+
+					@Test
+					void shouldCallGetConfigMap() {
+						service.getConfigMap(configMapNamespace, configMapName);
+
+						verify(remoteService).getConfigMap(configMapNamespace, configMapName);
+					}
+
+					@Test
+					void shouldReturnConfigMap() {
+						when(remoteService.getConfigMap(configMapNamespace, configMapName)).thenReturn(configMap);
+
+						ConfigMap result = service.getConfigMap(configMapNamespace, configMapName);
+
+						assertThat(result).isEqualTo(configMap);
+					}
+
+				}
+
+			}
+
+			@Nested
+			class TestAddUserToUserYaml {
+				@Test
+				void shouldCallAddNewUserToList() {
+					service.addUserToUserYaml(users, namespace, userPassword);
+
+					verify(service).addNewUserToList(eq(users), eq(namespace), anyString());
+				}
+
+				@Test
+				void shouldCallConstructYamlEntries() {
+					service.addUserToUserYaml(users, namespace, userPassword);
+
+					verify(service).constructYamlEntries(users);
+				}
+
+				@Test
+				void shouldReturnUsersYaml() {
+					when(service.constructYamlEntries(users)).thenReturn("usersYaml");
+
+					String result = service.addUserToUserYaml(users, namespace, userPassword);
+
+					assertThat(result).isEqualTo("usersYaml");
+				}
+
+				@Nested
+				class TestAddNewUserToList {
+					@Test
+					void shouldCallAddUserToList() {
+						service.addNewUserToList(users, namespace, "passwordHash");
+
+						verify(users).addUserToList(namespace, "passwordHash", Constants.USER_ROLE);
+
+					}
+				}
+
+				@Nested
+				class TestConstructYamlEntries {
+					@Test
+					void shouldCallGetUsersList() {
+						service.constructYamlEntries(users);
+
+						verify(users).getUsersList();
+
+					}
+
+					@Test
+					void shouldHaveResultWithCorrectContentStart() {
+						String result = service.constructYamlEntries(users);
+
+						assertTrue(result.startsWith("fileFormat: 1\nusers:\n"));
+					}
+
+					@Test
+					void shouldHaveResultWithCorrectContent() {
+						when(users.getUsersList()).thenReturn(
+								OzgCloudElsterTransferConfigMapUserListFactory.create());
+						String expectedYaml = "fileFormat: 1\n" + "users:\n" +
+								"  - { login: \"user1\", rolle: \"USER\", credentials: { passwortHash: \"$2a$12$cqKZMcwTUe/tju7PIFGhperWdV2Xa9o4fVw5eClbzatRhvxZphE1a\" }, gruppe: \"user1\" }\n";
+
+						String result = service.constructYamlEntries(users);
+
+						assertThat(expectedYaml).isEqualTo(result);
+					}
+
+					@Nested
+					class TestGetYamlForUser {
+						@Test
+						void shouldReturnExpectedYamlForUser() {
+							Map<String, Object> userEntry = new HashMap<>();
+							userEntry.put("login", "user1");
+							userEntry.put("rolle",
+									"USER");
+							userEntry.put("gruppe", "user1");
+							Map<String, String> credentials = new HashMap<>();
+							credentials.put("passwortHash",
+									"$2a$12$cqKZMcwTUe/tju7PIFGhperWdV2Xa9o4fVw5eClbzatRhvxZphE1a");
+							userEntry.put("credentials", credentials);
+							String expectedYaml = "  - { login: \"user1\", rolle: \"USER\", credentials: { passwortHash: \"$2a$12$cqKZMcwTUe/tju7PIFGhperWdV2Xa9o4fVw5eClbzatRhvxZphE1a\" }, gruppe: \"user1\" }";
+
+							String result = service.getYamlForUser(userEntry);
+
+							assertThat(expectedYaml).isEqualTo(result);
+						}
+
+					}
+
+				}
+
+			}
+
+			@Nested
+			class TestUpdateConfigMap {
+				@Test
+				void shouldCallUpdateConfigMapData() {
+					service.updateConfigMap(configMap, "usersYaml");
+
+					verify(remoteService).updateConfigMapData(configMap, usersKey, "usersYaml");
+				}
+			}
+
+			@Nested
+			class TestGetUsersFromConfigMap {
+				@Test
+				void shouldReturnEmptyUserList() {
+					ConfigMap configMap = new ConfigMap();
+					configMap.setData(new HashMap<>());
+
+					OzgCloudElsterTransferConfigMapUserList result = service.getUsersFromConfigMap(configMap);
+
+					assertThat(result.getUsersList()).isEmpty();
+				}
+
+				@Test
+				void shouldReturnEmptyUserListWhenUsersYamlIsEmpty() {
+					ConfigMap configMap = new ConfigMap();
+					Map<String, String> data = new HashMap<>();
+					data.put(usersKey, "");
+					configMap.setData(data);
+
+					OzgCloudElsterTransferConfigMapUserList result = service.getUsersFromConfigMap(configMap);
+
+					assertNotNull(result);
+					assertThat(result.getUsersList()).isEmpty();
+				}
+
+				void shouldReturnParsedUserListWhenUsersYamlIsValid() {
+					ConfigMap configMap = OzgCloudElsterTransferConfigMapFactory.create();
+
+					OzgCloudElsterTransferConfigMapUserList result = service.getUsersFromConfigMap(configMap);
+					Map<String, Object> user1 = result.getUsersList().get(0);
+
+					assertThat(result.getUsersList()).hasSize(1);
+					assertThat(user1.get("login")).isEqualTo("admin");
+					assertThat(user1.get("role")).isEqualTo("ADMIN");
+					assertThat(user1.get("passwordHash")).isEqualTo("$2a$12$cqKZMcwTUe/tju7PIFGhperWdV2Xa9o4fVw5eClbzatRhvxZphE1a");
+					assertThat(user1.get("group")).isEqualTo("Administratoren");
+				}
+
+			}
+
+		}
+
+		@Nested
+		class TestRestartDeployment {
+
+			@Test
+			void shouldCallRestartDeployment() {
+				service.restartDeployment(configMapNamespace, deploymentName);
+
+				verify(remoteService).restartDeployment(configMapNamespace, deploymentName);
+			}
+
+		}
+
+		@Nested
+		class TestCreateOrUpdateSecret {
+
+			@Test
+			void shouldCallRestartDeployment() {
+				service.createOrUpdateSecret(namespace, userPassword, secretName);
+
+				verify(remoteService).createOrUpdateSecret(namespace, userPassword, secretName);
+			}
+
+		}
+
+	}
+
+	@Nested
+	class TestDelete {
+
+		@BeforeEach
+		void setUp() {
+			ReflectionTestUtils.setField(service, "etrNamespace", "elster-transfer");
+		}
+
+		@Test
+		void shouldCallUserExists() {
+			service.delete(OzgCloudElsterTransferUserTestFactory.create());
+
+			verify(service).userExists(namespace, configMapNamespace, configMapName);
+		}
+
+		@Test
+		void shouldCallDeleteUser() {
+			when(service.userExists(namespace, configMapNamespace, configMapName)).thenReturn(true);
+			configMap = OzgCloudElsterTransferConfigMapFactory.create();
+			when(remoteService.getConfigMap(anyString(),
+					anyString())).thenReturn(configMap);
+
+			service.delete(OzgCloudElsterTransferUserTestFactory.create());
+
+			verify(service).deleteUser(namespace, configMapName);
+		}
+
+		@Test
+		void shouldCallRestartDeployment() {
+			when(service.userExists(namespace, configMapNamespace, configMapName)).thenReturn(true);
+			configMap = OzgCloudElsterTransferConfigMapFactory.create();
+			when(remoteService.getConfigMap(anyString(),
+					anyString())).thenReturn(configMap);
+
+			service.delete(OzgCloudElsterTransferUserTestFactory.create());
+
+			verify(service).restartDeployment(configMapNamespace, deploymentName);
+		}
+
+		@Test
+		void shouldNotCallRestartDeploymentIfUserNotExists() {
+			service.delete(OzgCloudElsterTransferUserTestFactory.create());
+
+			verify(service, never()).restartDeployment(any(), any());
+		}
+
+		@Test
+		void shouldNotCallDeleteUserIfUserNotExists() {
+			service.delete(OzgCloudElsterTransferUserTestFactory.create());
+
+			verify(service, never()).deleteUser(any(), any());
+		}
+
+		@Nested
+		class TestUserExists {
+
+			@Test
+			void shouldCallGetConfigMap() {
+				service.userExists(namespace, configMapNamespace, configMapName);
+
+				verify(service).getConfigMap(configMapNamespace, configMapName);
+			}
+
+			@Test
+			void shouldCallGetUsersFromConfigMap() {
+				when(service.getConfigMap(configMapNamespace, configMapName)).thenReturn(configMap);
+
+				service.userExists(namespace, configMapNamespace, configMapName);
+
+				verify(service).getUsersFromConfigMap(configMap);
+			}
+
+			@Test
+			void shouldNotCallGetUsersFromConfigMap() {
+				when(service.getConfigMap(configMapNamespace, configMapName)).thenReturn(null);
+
+				service.userExists(namespace, configMapNamespace, configMapName);
+
+				verify(service, never()).getUsersFromConfigMap(any());
+			}
+
+			@Test
+			void shouldCallExistsUser() {
+				when(service.getConfigMap(configMapNamespace, configMapName)).thenReturn(configMap);
+				when(service.getUsersFromConfigMap(configMap)).thenReturn(users);
+
+				service.userExists(namespace, configMapNamespace, configMapName);
+
+				verify(users, times(2)).existsUser(namespace);
+			}
+
+			@Test
+			void shouldNotCallExistsUser() {
+				when(service.getConfigMap(configMapNamespace, configMapName)).thenReturn(null);
+
+				service.userExists(namespace, configMapNamespace, configMapName);
+
+				verify(users, never()).existsUser(any());
+			}
+
+			@Test
+			void shouldReturnFalseWhenConfigMapNotExists() {
+				when(service.getConfigMap(configMapNamespace, configMapName)).thenReturn(null);
+
+				boolean result = service.userExists(namespace, configMapNamespace, configMapName);
+
+				assertThat(result).isFalse();
+			}
+
+			@Test
+			void shouldReturnTrueWhenConfigMapExistsAndUserExists() {
+				when(service.getConfigMap(configMapNamespace, configMapName)).thenReturn(configMap);
+				when(service.getUsersFromConfigMap(configMap)).thenReturn(users);
+				when(users.existsUser(namespace)).thenReturn(true);
+
+				boolean result = service.userExists(namespace, configMapNamespace, configMapName);
+
+				assertThat(result).isTrue();
+			}
+
+			@Test
+			void shouldReturnFalseWhenConfigMapExistsButUserNotExists() {
+				when(service.getConfigMap(configMapNamespace, configMapName)).thenReturn(configMap);
+				when(service.getUsersFromConfigMap(configMap)).thenReturn(users);
+				when(users.existsUser(namespace)).thenReturn(false);
+
+				boolean result = service.userExists(namespace, configMapNamespace, configMapName);
+
+				assertThat(result).isFalse();
+			}
+
+		}
+
+		@Nested
+		class TestDeleteUser {
+
+			@BeforeEach
+			void setUp() {
+				configMap = OzgCloudElsterTransferConfigMapFactory.create();
+				when(remoteService.getConfigMap(anyString(),
+						anyString())).thenReturn(configMap);
+			}
+
+			@Test
+			void shouldCallGetConfigMap() {
+				service.deleteUser(namespace, configMapName);
+
+				verify(service).getConfigMap(configMapNamespace, configMapName);
+			}
+
+			@Test
+			void shouldCallGetUsersFromConfigMap() {
+				service.deleteUser(namespace, configMapName);
+
+				verify(service).getUsersFromConfigMap(configMap);
+			}
+
+			@Test
+			void shouldCallRemoveDeleted() {
+				when(service.getConfigMap(configMapNamespace, configMapName)).thenReturn(configMap);
+				when(service.getUsersFromConfigMap(configMap)).thenReturn(users);
+
+				service.deleteUser(namespace, configMapName);
+
+				verify(users).removeDeleted(namespace);
+			}
+
+			@Test
+			void shouldCallConstructYamlEntries() {
+				when(service.getConfigMap(configMapNamespace, configMapName)).thenReturn(configMap);
+				when(service.getUsersFromConfigMap(configMap)).thenReturn(users);
+
+				service.deleteUser(namespace, configMapName);
+
+				verify(service).constructYamlEntries(users);
+			}
+
+			@Test
+			void shouldCallUpdateConfigMapData() {
+				when(service.getConfigMap(configMapNamespace, configMapName)).thenReturn(configMap);
+				when(service.getUsersFromConfigMap(configMap)).thenReturn(users);
+				when(service.constructYamlEntries(users)).thenReturn("updatedUsersYaml");
+
+				service.deleteUser(namespace, configMapName);
+
+				verify(remoteService).updateConfigMapData(configMap, usersKey, "updatedUsersYaml");
+			}
+
+		}
+
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserSpecTestFactory.java b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserSpecTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d107170aaec4ab0bcf6f10a5d582dee03da6f4b
--- /dev/null
+++ b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserSpecTestFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+public class OzgCloudElsterTransferUserSpecTestFactory {
+
+	public static final boolean KEEP_AFTER_DELETE = false;
+
+	public static OzgCloudElsterTransferUserSpec create() {
+		return createBuilder().build();
+	}
+
+	public static OzgCloudElsterTransferUserSpec.OzgCloudElsterTransferUserSpecBuilder createBuilder() {
+		return OzgCloudElsterTransferUserSpec.builder()
+				.keepAfterDelete(KEEP_AFTER_DELETE);
+	}
+}
diff --git a/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserTestFactory.java b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..0edc232f885b6ed00702ff3270a37e75faea9434
--- /dev/null
+++ b/src/test/java/de/ozgcloud/operator/elstertransfer/user/OzgCloudElsterTransferUserTestFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.operator.elstertransfer.user;
+
+public class OzgCloudElsterTransferUserTestFactory {
+
+	public static final String METADATA_NAMESPACE = "TestNamespace";
+
+	public static OzgCloudElsterTransferUser create() {
+		return createWithSpec(OzgCloudElsterTransferUserSpecTestFactory.create());
+	}
+
+	public static OzgCloudElsterTransferUser create(OzgCloudElsterTransferUserSpec spec) {
+		return createWithSpec(spec);
+	}
+
+	private static OzgCloudElsterTransferUser createWithSpec(OzgCloudElsterTransferUserSpec spec) {
+		var user = new OzgCloudElsterTransferUser();
+		user.setSpec(spec);
+		user.setStatus(OzgCloudElsterTransferStatusTestFactory.create());
+		user.getMetadata().setNamespace(METADATA_NAMESPACE);
+
+		return user;
+	}
+}
diff --git a/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
new file mode 100644
index 0000000000000000000000000000000000000000..79b126e6cdb86bec1f4f08c205de8961bde1934a
--- /dev/null
+++ b/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
@@ -0,0 +1 @@
+org.mockito.junit.jupiter.MockitoExtension
\ No newline at end of file
diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties
new file mode 100644
index 0000000000000000000000000000000000000000..b059a65dc46792ea5494de02f888ab2ad08cd420
--- /dev/null
+++ b/src/test/resources/junit-platform.properties
@@ -0,0 +1 @@
+junit.jupiter.extensions.autodetection.enabled=true
\ No newline at end of file
diff --git a/src/test/resources/users.yaml b/src/test/resources/users.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..7f8941cd240f0ad1a3e65706f3281dc0d7a156e6
--- /dev/null
+++ b/src/test/resources/users.yaml
@@ -0,0 +1,7 @@
+fileFormat: 1
+users:
+  - login: "{{login}}"
+    rolle: "{{role}}"
+    credentials:
+      passwortHash: "{{passwordHash}}"
+    gruppe: "{{group}}"