From 9b78e6096e6df5f334d9c2e34d3edf37fffc82b7 Mon Sep 17 00:00:00 2001 From: Marco Droll Date: Fri, 10 Oct 2025 15:40:26 +0200 Subject: [PATCH 1/7] add mechanism for feature-specific validation and move validation logic into respective classes --- .../groovy/com/cloudogu/gitops/Feature.groovy | 15 ++- .../gitops/cli/GitopsPlaygroundCli.groovy | 13 ++- .../config/ApplicationConfigurator.groovy | 96 +------------------ .../gitops/config/ConfigValidator.groovy | 44 +++++++++ .../cloudogu/gitops/features/Content.groovy | 42 ++++++++ .../gitops/features/argocd/ArgoCD.groovy | 21 ++++ .../cloudogu/gitops/utils/FeatureUtils.groovy | 19 ++++ 7 files changed, 149 insertions(+), 101 deletions(-) create mode 100644 src/main/groovy/com/cloudogu/gitops/config/ConfigValidator.groovy create mode 100644 src/main/groovy/com/cloudogu/gitops/utils/FeatureUtils.groovy diff --git a/src/main/groovy/com/cloudogu/gitops/Feature.groovy b/src/main/groovy/com/cloudogu/gitops/Feature.groovy index cd1aa4495..3a5b10b84 100644 --- a/src/main/groovy/com/cloudogu/gitops/Feature.groovy +++ b/src/main/groovy/com/cloudogu/gitops/Feature.groovy @@ -1,12 +1,10 @@ package com.cloudogu.gitops -import com.cloudogu.gitops.utils.MapUtils +import com.cloudogu.gitops.config.Config import com.cloudogu.gitops.utils.TemplatingEngine import groovy.util.logging.Slf4j import groovy.yaml.YamlSlurper -import java.nio.file.Path - /** * A single tool to be deployed by GOP. * @@ -86,4 +84,15 @@ abstract class Feature { */ protected void validate() { } + /** + * Hook for preConfigValidation. Optional. + * Feature should throw RuntimeException to stop immediately. + */ + void preConfigValidation(Config configToSet) { } + + /** + * Hook for postConfigValidation. Optional. + * Feature should throw RuntimeException to stop immediately. + */ + void postConfigValidation(Config configToSet) { } } \ No newline at end of file diff --git a/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy b/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy index 1d55b4695..405bcb49c 100644 --- a/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy +++ b/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy @@ -12,6 +12,7 @@ import com.cloudogu.gitops.config.Config import com.cloudogu.gitops.config.schema.JsonSchemaValidator import com.cloudogu.gitops.destroy.Destroyer import com.cloudogu.gitops.utils.CommandExecutor +import com.cloudogu.gitops.utils.FeatureUtils import com.cloudogu.gitops.utils.FileSystemUtils import com.cloudogu.gitops.utils.K8sClient import groovy.util.logging.Slf4j @@ -20,6 +21,7 @@ import io.micronaut.context.ApplicationContext import org.slf4j.LoggerFactory import picocli.CommandLine + import static com.cloudogu.gitops.config.ConfigConstants.APP_NAME import static com.cloudogu.gitops.utils.MapUtils.deepMerge /** @@ -35,7 +37,7 @@ class GitopsPlaygroundCli { ApplicationConfigurator applicationConfigurator GitopsPlaygroundCli(K8sClient k8sClient = new K8sClient(new CommandExecutor(), new FileSystemUtils(), null), - ApplicationConfigurator applicationConfigurator = new ApplicationConfigurator()) { + ApplicationConfigurator applicationConfigurator = new ApplicationConfigurator(null)) { this.k8sClient = k8sClient this.applicationConfigurator = applicationConfigurator } @@ -59,6 +61,9 @@ class GitopsPlaygroundCli { return ReturnCode.SUCCESS } + def context = createApplicationContext() + Application app = context.getBean(Application) + def config = readConfigs(args) if (config.application.outputConfigFile) { println(config.toYaml(false)) @@ -70,7 +75,7 @@ class GitopsPlaygroundCli { config = applicationConfigurator.initConfig(config) log.debug("Actual config: ${config.toYaml(true)}") - def context = createApplicationContext() + FeatureUtils.runHook(app, 'postConfigValidation', config) register(config, context) if (config.application.destroy) { @@ -86,7 +91,6 @@ class GitopsPlaygroundCli { if (!confirm("Applying gitops playground to kubernetes cluster '${k8sClient.currentContext}'.", config)) { return ReturnCode.NOT_CONFIRMED } - Application app = context.getBean(Application) app.start() printWelcomeScreen() @@ -208,7 +212,8 @@ class GitopsPlaygroundCli { Config mergedConfig = Config.fromMap(mergedConfigs) new CommandLine(mergedConfig).parseArgs(args) - applicationConfigurator.validateConfig(mergedConfig) + def app = ApplicationContext.run().getBean(Application) + FeatureUtils.runHook(app, 'preConfigValidation', mergedConfig) return mergedConfig } diff --git a/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy b/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy index 8d2cee50a..d532fa6dd 100644 --- a/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy +++ b/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy @@ -1,11 +1,9 @@ package com.cloudogu.gitops.config + import com.cloudogu.gitops.utils.FileSystemUtils import groovy.util.logging.Slf4j -import static com.cloudogu.gitops.config.Config.ContentRepoType -import static com.cloudogu.gitops.config.Config.ContentSchema.ContentRepositorySchema - @Slf4j class ApplicationConfigurator { @@ -21,6 +19,7 @@ class ApplicationConfigurator { Config initConfig(Config newConfig) { addAdditionalApplicationConfig(newConfig) + addNamePrefix(newConfig) addScmmConfig(newConfig) @@ -31,8 +30,6 @@ class ApplicationConfigurator { addFeatureConfig(newConfig) - validateEnvConfigForArgoCDOperator(newConfig) - evaluateBaseUrl(newConfig) setResourceInclusionsCluster(newConfig) @@ -283,95 +280,6 @@ class ApplicationConfigurator { return newUrl } - /** - * Make sure that config does not contain contradictory values. - * Throws RuntimeException which meaningful message, if invalid. - */ - void validateConfig(Config configToSet) { - validateScmmAndJenkinsAreBothSet(configToSet) - validateMirrorReposHelmChartFolderSet(configToSet) - validateContent(configToSet) - } - - private void validateMirrorReposHelmChartFolderSet(Config configToSet) { - if (configToSet.application.mirrorRepos && !configToSet.application.localHelmChartFolder) { - // This should only happen when run outside the image, i.e. during development - throw new RuntimeException("Missing config for localHelmChartFolder.\n" + - "Either run inside the official container image or setting env var " + - "LOCAL_HELM_CHART_FOLDER='charts' after running 'scripts/downloadHelmCharts.sh' from the repo") - } - } - - static void validateContent(Config config) { - config.content.repos.each { repo -> - - if (!repo.url) { - throw new RuntimeException("content.repos requires a url parameter.") - } - if (repo.target) { - if (repo.target.count('/') == 0) { - throw new RuntimeException("content.target needs / to separate namespace/group from repo name. Repo: ${repo.url}") - } - } - - switch (repo.type) { - case ContentRepoType.COPY: - if (!repo.target) { - throw new RuntimeException("content.repos.type ${ContentRepoType.COPY} requires content.repos.target to be set. Repo: ${repo.url}") - } - break - case ContentRepoType.FOLDER_BASED: - if (repo.target) { - throw new RuntimeException("content.repos.type ${ContentRepoType.FOLDER_BASED} does not support target parameter. Repo: ${repo.url}") - } - if (repo.targetRef) { - throw new RuntimeException("content.repos.type ${ContentRepoType.FOLDER_BASED} does not support targetRef parameter. Repo: ${repo.url}") - } - break - case ContentRepoType.MIRROR: - if (!repo.target) { - throw new RuntimeException("content.repos.type ${ContentRepoType.MIRROR} requires content.repos.target to be set. Repo: ${repo.url}") - } - if (repo.path != ContentRepositorySchema.DEFAULT_PATH) { - throw new RuntimeException("content.repos.type ${ContentRepoType.MIRROR} does not support path. Current path: ${repo.path}. Repo: ${repo.url}") - } - if (repo.templating) { - throw new RuntimeException("content.repos.type ${ContentRepoType.MIRROR} does not support templating. Repo: ${repo.url}") - } - break - } - } - } - - private void validateScmmAndJenkinsAreBothSet(Config configToSet) { - if (configToSet.jenkins.active && - (configToSet.scmm.url && !configToSet.jenkins.url || - !configToSet.scmm.url && configToSet.jenkins.url)) { - throw new RuntimeException('When setting jenkins URL, scmm URL must also be set and the other way round') - } - } - - // Validate that the env list has proper maps with 'name' and 'value' - private static void validateEnvConfigForArgoCDOperator(Config configToSet) { - // Exit early if not in operator mode or if env list is empty - if (!configToSet.features.argocd.operator || !configToSet.features.argocd.env) { - log.debug("Skipping features.argocd.env validation: operator mode is disabled or env list is empty.") - return - } - - List env = configToSet.features.argocd.env as List> - - log.info("Validating env list in features.argocd.env with {} entries.", env.size()) - - env.each { map -> - if (!(map instanceof Map) || !map.containsKey('name') || !map.containsKey('value')) { - throw new IllegalArgumentException("Each env variable in features.argocd.env must be a map with 'name' and 'value'. Invalid entry found: $map") - } - } - - log.info("Env list validation for features.argocd.env completed successfully.") - } - private void setResourceInclusionsCluster(Config configToSet) { // Return early if NOT deploying via operator if (!configToSet.features.argocd.operator) { diff --git a/src/main/groovy/com/cloudogu/gitops/config/ConfigValidator.groovy b/src/main/groovy/com/cloudogu/gitops/config/ConfigValidator.groovy new file mode 100644 index 000000000..642d6d800 --- /dev/null +++ b/src/main/groovy/com/cloudogu/gitops/config/ConfigValidator.groovy @@ -0,0 +1,44 @@ +package com.cloudogu.gitops.config + +import com.cloudogu.gitops.Feature +import groovy.util.logging.Slf4j +import io.micronaut.core.annotation.Order +import jakarta.inject.Singleton + +@Slf4j +@Singleton +@Order(40) +class ConfigValidator extends Feature { + + ConfigValidator() { + log.debug("Doing common pre/post config validation...") + } + + @Override + void preConfigValidation(Config configToSet) { + validateScmmAndJenkinsAreBothSet(configToSet) + validateMirrorReposHelmChartFolderSet(configToSet) + } + + private void validateScmmAndJenkinsAreBothSet(Config configToSet) { + if (configToSet.jenkins.active && + (configToSet.scmm.url && !configToSet.jenkins.url || + !configToSet.scmm.url && configToSet.jenkins.url)) { + throw new RuntimeException('When setting jenkins URL, scmm URL must also be set and the other way round') + } + } + + private void validateMirrorReposHelmChartFolderSet(Config configToSet) { + if (configToSet.application.mirrorRepos && !configToSet.application.localHelmChartFolder) { + // This should only happen when run outside the image, i.e. during development + throw new RuntimeException("Missing config for localHelmChartFolder.\n" + + "Either run inside the official container image or setting env var " + + "LOCAL_HELM_CHART_FOLDER='charts' after running 'scripts/downloadHelmCharts.sh' from the repo") + } + } + + @Override + boolean isEnabled() { + return false + } +} diff --git a/src/main/groovy/com/cloudogu/gitops/features/Content.groovy b/src/main/groovy/com/cloudogu/gitops/features/Content.groovy index 8920b9329..bb71f9603 100644 --- a/src/main/groovy/com/cloudogu/gitops/features/Content.groovy +++ b/src/main/groovy/com/cloudogu/gitops/features/Content.groovy @@ -78,6 +78,48 @@ class Content extends Feature { } + @Override + void preConfigValidation(Config configToSet) { + config.content.repos.each { repo -> + + if (!repo.url) { + throw new RuntimeException("content.repos requires a url parameter.") + } + if (repo.target) { + if (repo.target.count('/') == 0) { + throw new RuntimeException("content.target needs / to separate namespace/group from repo name. Repo: ${repo.url}") + } + } + + switch (repo.type) { + case ContentRepoType.COPY: + if (!repo.target) { + throw new RuntimeException("content.repos.type ${ContentRepoType.COPY} requires content.repos.target to be set. Repo: ${repo.url}") + } + break + case ContentRepoType.FOLDER_BASED: + if (repo.target) { + throw new RuntimeException("content.repos.type ${ContentRepoType.FOLDER_BASED} does not support target parameter. Repo: ${repo.url}") + } + if (repo.targetRef) { + throw new RuntimeException("content.repos.type ${ContentRepoType.FOLDER_BASED} does not support targetRef parameter. Repo: ${repo.url}") + } + break + case ContentRepoType.MIRROR: + if (!repo.target) { + throw new RuntimeException("content.repos.type ${ContentRepoType.MIRROR} requires content.repos.target to be set. Repo: ${repo.url}") + } + if (repo.path != ContentRepositorySchema.DEFAULT_PATH) { + throw new RuntimeException("content.repos.type ${ContentRepoType.MIRROR} does not support path. Current path: ${repo.path}. Repo: ${repo.url}") + } + if (repo.templating) { + throw new RuntimeException("content.repos.type ${ContentRepoType.MIRROR} does not support templating. Repo: ${repo.url}") + } + break + } + } + } + void createImagePullSecrets() { if (config.registry.createImagePullSecrets) { String registryUsername = config.registry.readOnlyUsername ?: config.registry.username diff --git a/src/main/groovy/com/cloudogu/gitops/features/argocd/ArgoCD.groovy b/src/main/groovy/com/cloudogu/gitops/features/argocd/ArgoCD.groovy index 47c813199..efe64bf13 100644 --- a/src/main/groovy/com/cloudogu/gitops/features/argocd/ArgoCD.groovy +++ b/src/main/groovy/com/cloudogu/gitops/features/argocd/ArgoCD.groovy @@ -76,6 +76,27 @@ class ArgoCD extends Feature { config.features.argocd.active } + @Override + void postConfigValidation(Config configToSet) { + // Exit early if not in operator mode or if env list is empty + if (!configToSet.features.argocd.operator || !configToSet.features.argocd.env) { + log.debug("Skipping features.argocd.env validation: operator mode is disabled or env list is empty.") + return + } + + List env = configToSet.features.argocd.env as List> + + log.info("Validating env list in features.argocd.env with {} entries.", env.size()) + + env.each { map -> + if (!(map instanceof Map) || !map.containsKey('name') || !map.containsKey('value')) { + throw new IllegalArgumentException("Each env variable in features.argocd.env must be a map with 'name' and 'value'. Invalid entry found: $map") + } + } + + log.info("Env list validation for features.argocd.env completed successfully.") + } + @Override void enable() { initRepos() diff --git a/src/main/groovy/com/cloudogu/gitops/utils/FeatureUtils.groovy b/src/main/groovy/com/cloudogu/gitops/utils/FeatureUtils.groovy new file mode 100644 index 000000000..7b489c149 --- /dev/null +++ b/src/main/groovy/com/cloudogu/gitops/utils/FeatureUtils.groovy @@ -0,0 +1,19 @@ +package com.cloudogu.gitops.utils + +import com.cloudogu.gitops.Application +import groovy.util.logging.Slf4j +import com.cloudogu.gitops.Feature + +@Slf4j +class FeatureUtils { + static void runHook(Application app, String methodName, def config) { + app.features.each { feature -> + // Executing only the method if the derived feature class has implemented the passed specific hook method + def mm = feature.metaClass.getMetaMethod(methodName, config) + if (mm && mm.declaringClass.theClass != Feature) { + log.debug("Executing ${methodName} hook on feature ${feature.class.name}") + mm.invoke(feature, config) + } + } + } +} From dce15ce178ba2bc6f0acfaaaf1a11dfc41e05255 Mon Sep 17 00:00:00 2001 From: Marco Droll Date: Mon, 13 Oct 2025 17:20:58 +0200 Subject: [PATCH 2/7] move hook mechanism to gop cli and rename validation to a proper name --- .../gitops/cli/GitopsPlaygroundCli.groovy | 23 +++++++++++++++---- ...ator.groovy => CommonFeatureConfig.groovy} | 8 ++----- .../cloudogu/gitops/utils/FeatureUtils.groovy | 19 --------------- 3 files changed, 20 insertions(+), 30 deletions(-) rename src/main/groovy/com/cloudogu/gitops/config/{ConfigValidator.groovy => CommonFeatureConfig.groovy} (91%) delete mode 100644 src/main/groovy/com/cloudogu/gitops/utils/FeatureUtils.groovy diff --git a/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy b/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy index 405bcb49c..5403a0787 100644 --- a/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy +++ b/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy @@ -7,12 +7,12 @@ import ch.qos.logback.classic.encoder.PatternLayoutEncoder import ch.qos.logback.classic.spi.ILoggingEvent import ch.qos.logback.core.ConsoleAppender import com.cloudogu.gitops.Application +import com.cloudogu.gitops.Feature import com.cloudogu.gitops.config.ApplicationConfigurator import com.cloudogu.gitops.config.Config import com.cloudogu.gitops.config.schema.JsonSchemaValidator import com.cloudogu.gitops.destroy.Destroyer import com.cloudogu.gitops.utils.CommandExecutor -import com.cloudogu.gitops.utils.FeatureUtils import com.cloudogu.gitops.utils.FileSystemUtils import com.cloudogu.gitops.utils.K8sClient import groovy.util.logging.Slf4j @@ -21,9 +21,8 @@ import io.micronaut.context.ApplicationContext import org.slf4j.LoggerFactory import picocli.CommandLine - import static com.cloudogu.gitops.config.ConfigConstants.APP_NAME -import static com.cloudogu.gitops.utils.MapUtils.deepMerge +import static com.cloudogu.gitops.utils.MapUtils.deepMerge /** * Provides the entrypoint to the application as well as all config parameters. * When changing parameters, make sure to update the Config for the config file as well @@ -75,7 +74,7 @@ class GitopsPlaygroundCli { config = applicationConfigurator.initConfig(config) log.debug("Actual config: ${config.toYaml(true)}") - FeatureUtils.runHook(app, 'postConfigValidation', config) + runHook(app, 'postConfigValidation', config) register(config, context) if (config.application.destroy) { @@ -213,7 +212,7 @@ class GitopsPlaygroundCli { new CommandLine(mergedConfig).parseArgs(args) def app = ApplicationContext.run().getBean(Application) - FeatureUtils.runHook(app, 'preConfigValidation', mergedConfig) + runHook(app, 'preConfigValidation', mergedConfig) return mergedConfig } @@ -245,4 +244,18 @@ class GitopsPlaygroundCli { |----------------------------------------------------------------------------------------------| ''' } + + static void runHook(Application app, String methodName, def config) { + + + + app.features.each { feature -> + // Executing only the method if the derived feature class has implemented the passed specific hook method + def mm = feature.metaClass.getMetaMethod(methodName, config) + if (mm && mm.declaringClass.theClass != Feature) { + log.debug("Executing ${methodName} hook on feature ${feature.class.name}") + mm.invoke(feature, config) + } + } + } } \ No newline at end of file diff --git a/src/main/groovy/com/cloudogu/gitops/config/ConfigValidator.groovy b/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy similarity index 91% rename from src/main/groovy/com/cloudogu/gitops/config/ConfigValidator.groovy rename to src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy index 642d6d800..0a3f38e52 100644 --- a/src/main/groovy/com/cloudogu/gitops/config/ConfigValidator.groovy +++ b/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy @@ -8,14 +8,10 @@ import jakarta.inject.Singleton @Slf4j @Singleton @Order(40) -class ConfigValidator extends Feature { - - ConfigValidator() { - log.debug("Doing common pre/post config validation...") - } - +class CommonFeatureConfig extends Feature { @Override void preConfigValidation(Config configToSet) { + log.debug("Doing common preConfigValidation") validateScmmAndJenkinsAreBothSet(configToSet) validateMirrorReposHelmChartFolderSet(configToSet) } diff --git a/src/main/groovy/com/cloudogu/gitops/utils/FeatureUtils.groovy b/src/main/groovy/com/cloudogu/gitops/utils/FeatureUtils.groovy deleted file mode 100644 index 7b489c149..000000000 --- a/src/main/groovy/com/cloudogu/gitops/utils/FeatureUtils.groovy +++ /dev/null @@ -1,19 +0,0 @@ -package com.cloudogu.gitops.utils - -import com.cloudogu.gitops.Application -import groovy.util.logging.Slf4j -import com.cloudogu.gitops.Feature - -@Slf4j -class FeatureUtils { - static void runHook(Application app, String methodName, def config) { - app.features.each { feature -> - // Executing only the method if the derived feature class has implemented the passed specific hook method - def mm = feature.metaClass.getMetaMethod(methodName, config) - if (mm && mm.declaringClass.theClass != Feature) { - log.debug("Executing ${methodName} hook on feature ${feature.class.name}") - mm.invoke(feature, config) - } - } - } -} From e5fee1f90ebe7d5b692964499a3890c1120d6546 Mon Sep 17 00:00:00 2001 From: Marco Droll Date: Tue, 14 Oct 2025 13:06:43 +0200 Subject: [PATCH 3/7] decouple common feature config from the feature list --- .../com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy | 8 +++----- .../com/cloudogu/gitops/config/CommonFeatureConfig.groovy | 6 +----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy b/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy index 5403a0787..fc6ed5212 100644 --- a/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy +++ b/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy @@ -9,6 +9,7 @@ import ch.qos.logback.core.ConsoleAppender import com.cloudogu.gitops.Application import com.cloudogu.gitops.Feature import com.cloudogu.gitops.config.ApplicationConfigurator +import com.cloudogu.gitops.config.CommonFeatureConfig import com.cloudogu.gitops.config.Config import com.cloudogu.gitops.config.schema.JsonSchemaValidator import com.cloudogu.gitops.destroy.Destroyer @@ -246,11 +247,8 @@ class GitopsPlaygroundCli { } static void runHook(Application app, String methodName, def config) { - - - - app.features.each { feature -> - // Executing only the method if the derived feature class has implemented the passed specific hook method + ([new CommonFeatureConfig(), *app.features]).each { feature -> + // Executing only the method if the derived feature class has implemented the passed methodName def mm = feature.metaClass.getMetaMethod(methodName, config) if (mm && mm.declaringClass.theClass != Feature) { log.debug("Executing ${methodName} hook on feature ${feature.class.name}") diff --git a/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy b/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy index 0a3f38e52..cedd91d6e 100644 --- a/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy +++ b/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy @@ -2,16 +2,12 @@ package com.cloudogu.gitops.config import com.cloudogu.gitops.Feature import groovy.util.logging.Slf4j -import io.micronaut.core.annotation.Order -import jakarta.inject.Singleton @Slf4j -@Singleton -@Order(40) class CommonFeatureConfig extends Feature { @Override void preConfigValidation(Config configToSet) { - log.debug("Doing common preConfigValidation") + log.debug("Common preConfigValidation") validateScmmAndJenkinsAreBothSet(configToSet) validateMirrorReposHelmChartFolderSet(configToSet) } From 11a8c16c8fbb462c65280d90ddc627efa8c2940e Mon Sep 17 00:00:00 2001 From: Marco Droll Date: Tue, 14 Oct 2025 16:52:40 +0200 Subject: [PATCH 4/7] decouple common feature config from the feature list --- src/main/groovy/com/cloudogu/gitops/Feature.groovy | 8 ++++---- .../cloudogu/gitops/cli/GitopsPlaygroundCli.groovy | 13 ++++++++----- .../gitops/config/CommonFeatureConfig.groovy | 3 +-- .../com/cloudogu/gitops/features/Content.groovy | 2 +- .../cloudogu/gitops/features/argocd/ArgoCD.groovy | 2 +- .../cli/GitopsPlaygroundCliMainScriptedTest.groovy | 2 +- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/main/groovy/com/cloudogu/gitops/Feature.groovy b/src/main/groovy/com/cloudogu/gitops/Feature.groovy index 3a5b10b84..545998aa6 100644 --- a/src/main/groovy/com/cloudogu/gitops/Feature.groovy +++ b/src/main/groovy/com/cloudogu/gitops/Feature.groovy @@ -85,14 +85,14 @@ abstract class Feature { protected void validate() { } /** - * Hook for preConfigValidation. Optional. + * Hook for preConfigInit. Optional. * Feature should throw RuntimeException to stop immediately. */ - void preConfigValidation(Config configToSet) { } + void preConfigInit(Config configToSet) { } /** - * Hook for postConfigValidation. Optional. + * Hook for postConfigInit. Optional. * Feature should throw RuntimeException to stop immediately. */ - void postConfigValidation(Config configToSet) { } + void postConfigInit(Config configToSet) { } } \ No newline at end of file diff --git a/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy b/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy index fc6ed5212..5a8d32a5a 100644 --- a/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy +++ b/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy @@ -24,6 +24,8 @@ import picocli.CommandLine import static com.cloudogu.gitops.config.ConfigConstants.APP_NAME import static com.cloudogu.gitops.utils.MapUtils.deepMerge + + /** * Provides the entrypoint to the application as well as all config parameters. * When changing parameters, make sure to update the Config for the config file as well @@ -37,7 +39,7 @@ class GitopsPlaygroundCli { ApplicationConfigurator applicationConfigurator GitopsPlaygroundCli(K8sClient k8sClient = new K8sClient(new CommandExecutor(), new FileSystemUtils(), null), - ApplicationConfigurator applicationConfigurator = new ApplicationConfigurator(null)) { + ApplicationConfigurator applicationConfigurator = new ApplicationConfigurator()) { this.k8sClient = k8sClient this.applicationConfigurator = applicationConfigurator } @@ -65,6 +67,8 @@ class GitopsPlaygroundCli { Application app = context.getBean(Application) def config = readConfigs(args) + runHook(app, 'preConfigInit', config) + if (config.application.outputConfigFile) { println(config.toYaml(false)) return ReturnCode.SUCCESS @@ -74,8 +78,9 @@ class GitopsPlaygroundCli { // eg a simple docker run .. --help should not fail with connection refused config = applicationConfigurator.initConfig(config) log.debug("Actual config: ${config.toYaml(true)}") + runHook(app, 'postConfigInit', config) - runHook(app, 'postConfigValidation', config) + context = createApplicationContext() register(config, context) if (config.application.destroy) { @@ -91,6 +96,7 @@ class GitopsPlaygroundCli { if (!confirm("Applying gitops playground to kubernetes cluster '${k8sClient.currentContext}'.", config)) { return ReturnCode.NOT_CONFIRMED } + app = context.getBean(Application) app.start() printWelcomeScreen() @@ -211,9 +217,6 @@ class GitopsPlaygroundCli { log.debug("Writing CLI params into config") Config mergedConfig = Config.fromMap(mergedConfigs) new CommandLine(mergedConfig).parseArgs(args) - - def app = ApplicationContext.run().getBean(Application) - runHook(app, 'preConfigValidation', mergedConfig) return mergedConfig } diff --git a/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy b/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy index cedd91d6e..a079ba4ce 100644 --- a/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy +++ b/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy @@ -6,8 +6,7 @@ import groovy.util.logging.Slf4j @Slf4j class CommonFeatureConfig extends Feature { @Override - void preConfigValidation(Config configToSet) { - log.debug("Common preConfigValidation") + void preConfigInit(Config configToSet) { validateScmmAndJenkinsAreBothSet(configToSet) validateMirrorReposHelmChartFolderSet(configToSet) } diff --git a/src/main/groovy/com/cloudogu/gitops/features/Content.groovy b/src/main/groovy/com/cloudogu/gitops/features/Content.groovy index bb71f9603..940836c9a 100644 --- a/src/main/groovy/com/cloudogu/gitops/features/Content.groovy +++ b/src/main/groovy/com/cloudogu/gitops/features/Content.groovy @@ -79,7 +79,7 @@ class Content extends Feature { } @Override - void preConfigValidation(Config configToSet) { + void preConfigInit(Config configToSet) { config.content.repos.each { repo -> if (!repo.url) { diff --git a/src/main/groovy/com/cloudogu/gitops/features/argocd/ArgoCD.groovy b/src/main/groovy/com/cloudogu/gitops/features/argocd/ArgoCD.groovy index efe64bf13..e5618891d 100644 --- a/src/main/groovy/com/cloudogu/gitops/features/argocd/ArgoCD.groovy +++ b/src/main/groovy/com/cloudogu/gitops/features/argocd/ArgoCD.groovy @@ -77,7 +77,7 @@ class ArgoCD extends Feature { } @Override - void postConfigValidation(Config configToSet) { + void postConfigInit(Config configToSet) { // Exit early if not in operator mode or if env list is empty if (!configToSet.features.argocd.operator || !configToSet.features.argocd.env) { log.debug("Skipping features.argocd.env validation: operator mode is disabled or env list is empty.") diff --git a/src/test/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScriptedTest.groovy b/src/test/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScriptedTest.groovy index 34c9a54c6..1cbc59e8c 100644 --- a/src/test/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScriptedTest.groovy +++ b/src/test/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScriptedTest.groovy @@ -74,7 +74,7 @@ class GitopsPlaygroundCliMainScriptedTest { .enableAnnotationInfo() .scan().withCloseable { scanResult -> scanResult.getAllClasses().each { ClassInfo classInfo -> - if (classInfo.name.endsWith("Test") || classInfo.isAbstract()) { + if (classInfo.name.endsWith("Test") || classInfo.isAbstract() || !classInfo.hasAnnotation(jakarta.inject.Singleton)) { return } From 922f1a9bc300010d2e96df9d636764a248a46946 Mon Sep 17 00:00:00 2001 From: Marco Droll Date: Mon, 20 Oct 2025 09:41:29 +0200 Subject: [PATCH 5/7] add unittests --- .../gitops/config/CommonFeatureConfig.groovy | 8 +++ .../gitops/ApplicationConfiguratorTest.groovy | 49 ++++++++++++++----- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy b/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy index a079ba4ce..536d5d3b6 100644 --- a/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy +++ b/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy @@ -7,6 +7,14 @@ import groovy.util.logging.Slf4j class CommonFeatureConfig extends Feature { @Override void preConfigInit(Config configToSet) { + validateConfig(configToSet) + } + + /** + * Make sure that config does not contain contradictory values. + * Throws RuntimeException which meaningful message, if invalid. + */ + void validateConfig(Config configToSet) { validateScmmAndJenkinsAreBothSet(configToSet) validateMirrorReposHelmChartFolderSet(configToSet) } diff --git a/src/test/groovy/com/cloudogu/gitops/ApplicationConfiguratorTest.groovy b/src/test/groovy/com/cloudogu/gitops/ApplicationConfiguratorTest.groovy index e2a603a95..98a3fe279 100644 --- a/src/test/groovy/com/cloudogu/gitops/ApplicationConfiguratorTest.groovy +++ b/src/test/groovy/com/cloudogu/gitops/ApplicationConfiguratorTest.groovy @@ -1,11 +1,20 @@ package com.cloudogu.gitops import com.cloudogu.gitops.config.ApplicationConfigurator +import com.cloudogu.gitops.config.CommonFeatureConfig import com.cloudogu.gitops.config.Config +import com.cloudogu.gitops.features.Content +import com.cloudogu.gitops.features.Jenkins +import com.cloudogu.gitops.features.argocd.ArgoCD +import com.cloudogu.gitops.scmm.ScmmRepoProvider +import com.cloudogu.gitops.scmm.api.ScmmApiClient import com.cloudogu.gitops.utils.FileSystemUtils +import com.cloudogu.gitops.utils.HelmClient +import com.cloudogu.gitops.utils.K8sClient import com.cloudogu.gitops.utils.TestLogger import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.mockito.Mockito import static com.github.stefanbirkner.systemlambda.SystemLambda.withEnvironmentVariable import static groovy.test.GroovyAssert.shouldFail @@ -22,6 +31,10 @@ class ApplicationConfiguratorTest { private ApplicationConfigurator applicationConfigurator private FileSystemUtils fileSystemUtils private TestLogger testLogger + private CommonFeatureConfig commonFeatureConfig + private Content featureContent + private ArgoCD featureArgoCd + Config testConfig = Config.fromMap([ application: [ localHelmChartFolder : 'someValue', @@ -61,6 +74,14 @@ class ApplicationConfiguratorTest { fileSystemUtils = new FileSystemUtils() applicationConfigurator = new ApplicationConfigurator(fileSystemUtils) testLogger = new TestLogger(applicationConfigurator.getClass()) + commonFeatureConfig = new CommonFeatureConfig() + + K8sClient k8sClient = Mockito.mock(K8sClient) + HelmClient helmClient = Mockito.mock(HelmClient) + ScmmRepoProvider scmmRepoProvider = Mockito.mock(ScmmRepoProvider) + + featureContent = Mockito.spy(new Content(testConfig, k8sClient, scmmRepoProvider, Mockito.mock(ScmmApiClient), Mockito.mock(Jenkins))) + featureArgoCd = Mockito.spy(new ArgoCD(testConfig, k8sClient, helmClient, fileSystemUtils, scmmRepoProvider)) } @Test @@ -107,7 +128,7 @@ class ApplicationConfiguratorTest { testConfig.scmm.url = '' def exception = shouldFail(RuntimeException) { - applicationConfigurator.validateConfig(testConfig) + commonFeatureConfig.validateConfig(testConfig) } assertThat(exception.message).isEqualTo('When setting jenkins URL, scmm URL must also be set and the other way round') @@ -115,13 +136,13 @@ class ApplicationConfiguratorTest { testConfig.scmm.url = 'external' exception = shouldFail(RuntimeException) { - applicationConfigurator.validateConfig(testConfig) + commonFeatureConfig.validateConfig(testConfig) } assertThat(exception.message).isEqualTo('When setting jenkins URL, scmm URL must also be set and the other way round') testConfig.jenkins.active = false - applicationConfigurator.validateConfig(testConfig) + commonFeatureConfig.validateConfig(testConfig) // no exception when jenkins is not active } @@ -131,7 +152,7 @@ class ApplicationConfiguratorTest { testConfig.application.localHelmChartFolder = '' def exception = shouldFail(RuntimeException) { - applicationConfigurator.validateConfig(testConfig) + commonFeatureConfig.validateConfig(testConfig) } assertThat(exception.message).isEqualTo('Missing config for localHelmChartFolder.\n' + 'Either run inside the official container image or setting env var LOCAL_HELM_CHART_FOLDER=\'charts\' ' + @@ -150,11 +171,12 @@ class ApplicationConfiguratorTest { @Test void 'Fails if content repo is set without mandatory params'() { + testConfig.content.repos = [ new Config.ContentSchema.ContentRepositorySchema(url: ''), ] def exception = shouldFail(RuntimeException) { - applicationConfigurator.validateConfig(testConfig) + featureContent.preConfigInit(testConfig) } assertThat(exception.message).isEqualTo('content.repos requires a url parameter.') @@ -163,7 +185,7 @@ class ApplicationConfiguratorTest { new Config.ContentSchema.ContentRepositorySchema(url: 'abc', type: Config.ContentRepoType.COPY, target: "missing_slash"), ] exception = shouldFail(RuntimeException) { - applicationConfigurator.validateConfig(testConfig) + featureContent.preConfigInit(testConfig) } assertThat(exception.message).isEqualTo('content.target needs / to separate namespace/group from repo name. Repo: abc') } @@ -174,7 +196,7 @@ class ApplicationConfiguratorTest { new Config.ContentSchema.ContentRepositorySchema(url: 'abc', type: Config.ContentRepoType.COPY), ] def exception = shouldFail(RuntimeException) { - applicationConfigurator.validateConfig(testConfig) + featureContent.preConfigInit(testConfig) } assertThat(exception.message).isEqualTo('content.repos.type COPY requires content.repos.target to be set. Repo: abc') } @@ -185,7 +207,7 @@ class ApplicationConfiguratorTest { new Config.ContentSchema.ContentRepositorySchema(url: 'abc', type: Config.ContentRepoType.FOLDER_BASED, target: 'namespace/repo'), ] def exception = shouldFail(RuntimeException) { - applicationConfigurator.validateConfig(testConfig) + featureContent.preConfigInit(testConfig) } assertThat(exception.message).isEqualTo('content.repos.type FOLDER_BASED does not support target parameter. Repo: abc') @@ -194,7 +216,7 @@ class ApplicationConfiguratorTest { new Config.ContentSchema.ContentRepositorySchema(url: 'abc', type: Config.ContentRepoType.FOLDER_BASED, targetRef: 'someRef'), ] exception = shouldFail(RuntimeException) { - applicationConfigurator.validateConfig(testConfig) + featureContent.preConfigInit(testConfig) } assertThat(exception.message).isEqualTo('content.repos.type FOLDER_BASED does not support targetRef parameter. Repo: abc') } @@ -206,7 +228,7 @@ class ApplicationConfiguratorTest { new Config.ContentSchema.ContentRepositorySchema(url: 'abc', type: Config.ContentRepoType.MIRROR), ] def exception = shouldFail(RuntimeException) { - applicationConfigurator.validateConfig(testConfig) + featureContent.preConfigInit(testConfig) } assertThat(exception.message).isEqualTo('content.repos.type MIRROR requires content.repos.target to be set. Repo: abc') @@ -216,7 +238,7 @@ class ApplicationConfiguratorTest { target: 'namespace/repo', path: 'non-default-path'), ] exception = shouldFail(RuntimeException) { - applicationConfigurator.validateConfig(testConfig) + featureContent.preConfigInit(testConfig) } assertThat(exception.message).isEqualTo("content.repos.type MIRROR does not support path. Current path: non-default-path. Repo: abc") @@ -226,7 +248,7 @@ class ApplicationConfiguratorTest { target: 'namespace/repo', templating: true), ] exception = shouldFail(RuntimeException) { - applicationConfigurator.validateConfig(testConfig) + featureContent.preConfigInit(testConfig) } assertThat(exception.message).isEqualTo('content.repos.type MIRROR does not support templating. Repo: abc') } @@ -466,6 +488,7 @@ class ApplicationConfiguratorTest { def exception = shouldFail(IllegalArgumentException) { applicationConfigurator.initConfig(testConfig) + featureArgoCd.postConfigInit(testConfig) } assertThat(exception.message).contains("Each env variable in features.argocd.env must be a map with 'name' and 'value'. Invalid entry found: [value:value2]") @@ -482,6 +505,7 @@ class ApplicationConfiguratorTest { def exception = shouldFail(IllegalArgumentException) { applicationConfigurator.initConfig(testConfig) + featureArgoCd.postConfigInit(testConfig) } assertThat(exception.message).contains("Each env variable in features.argocd.env must be a map with 'name' and 'value'. Invalid entry found: [name:ENV_VAR_2]") @@ -498,6 +522,7 @@ class ApplicationConfiguratorTest { def exception = shouldFail(IllegalArgumentException) { applicationConfigurator.initConfig(testConfig) + featureArgoCd.postConfigInit(testConfig) } assertThat(exception.message).contains("Each env variable in features.argocd.env must be a map with 'name' and 'value'. Invalid entry found: invalid_entry") From bdbd011335985e889bd75a367b8691c8ba381148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20Hu=C3=9Fmann?= Date: Thu, 13 Nov 2025 14:54:59 +0100 Subject: [PATCH 6/7] removed validation for scmm and jenkins --- .../gitops/config/ApplicationConfigurator.groovy | 3 --- .../cloudogu/gitops/config/CommonFeatureConfig.groovy | 11 +---------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy b/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy index f0ad993ae..9b6f6157b 100644 --- a/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy +++ b/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy @@ -4,9 +4,6 @@ package com.cloudogu.gitops.config import com.cloudogu.gitops.utils.FileSystemUtils import groovy.util.logging.Slf4j -import static com.cloudogu.gitops.config.Config.ContentRepoType -import static com.cloudogu.gitops.config.Config.ContentSchema.ContentRepositorySchema - @Slf4j class ApplicationConfigurator { diff --git a/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy b/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy index 536d5d3b6..fb8841974 100644 --- a/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy +++ b/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy @@ -15,18 +15,9 @@ class CommonFeatureConfig extends Feature { * Throws RuntimeException which meaningful message, if invalid. */ void validateConfig(Config configToSet) { - validateScmmAndJenkinsAreBothSet(configToSet) validateMirrorReposHelmChartFolderSet(configToSet) } - private void validateScmmAndJenkinsAreBothSet(Config configToSet) { - if (configToSet.jenkins.active && - (configToSet.scmm.url && !configToSet.jenkins.url || - !configToSet.scmm.url && configToSet.jenkins.url)) { - throw new RuntimeException('When setting jenkins URL, scmm URL must also be set and the other way round') - } - } - private void validateMirrorReposHelmChartFolderSet(Config configToSet) { if (configToSet.application.mirrorRepos && !configToSet.application.localHelmChartFolder) { // This should only happen when run outside the image, i.e. during development @@ -40,4 +31,4 @@ class CommonFeatureConfig extends Feature { boolean isEnabled() { return false } -} +} \ No newline at end of file From 5c11ebafaf8a9efa4421e983cdf0bbca2c6ce116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20Hu=C3=9Fmann?= Date: Thu, 13 Nov 2025 14:54:59 +0100 Subject: [PATCH 7/7] removed validation for scmm and jenkins --- .../config/ApplicationConfigurator.groovy | 3 -- .../gitops/config/CommonFeatureConfig.groovy | 11 +---- .../gitops/ApplicationConfiguratorTest.groovy | 49 ++++++------------- 3 files changed, 17 insertions(+), 46 deletions(-) diff --git a/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy b/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy index f0ad993ae..9b6f6157b 100644 --- a/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy +++ b/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy @@ -4,9 +4,6 @@ package com.cloudogu.gitops.config import com.cloudogu.gitops.utils.FileSystemUtils import groovy.util.logging.Slf4j -import static com.cloudogu.gitops.config.Config.ContentRepoType -import static com.cloudogu.gitops.config.Config.ContentSchema.ContentRepositorySchema - @Slf4j class ApplicationConfigurator { diff --git a/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy b/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy index 536d5d3b6..fb8841974 100644 --- a/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy +++ b/src/main/groovy/com/cloudogu/gitops/config/CommonFeatureConfig.groovy @@ -15,18 +15,9 @@ class CommonFeatureConfig extends Feature { * Throws RuntimeException which meaningful message, if invalid. */ void validateConfig(Config configToSet) { - validateScmmAndJenkinsAreBothSet(configToSet) validateMirrorReposHelmChartFolderSet(configToSet) } - private void validateScmmAndJenkinsAreBothSet(Config configToSet) { - if (configToSet.jenkins.active && - (configToSet.scmm.url && !configToSet.jenkins.url || - !configToSet.scmm.url && configToSet.jenkins.url)) { - throw new RuntimeException('When setting jenkins URL, scmm URL must also be set and the other way round') - } - } - private void validateMirrorReposHelmChartFolderSet(Config configToSet) { if (configToSet.application.mirrorRepos && !configToSet.application.localHelmChartFolder) { // This should only happen when run outside the image, i.e. during development @@ -40,4 +31,4 @@ class CommonFeatureConfig extends Feature { boolean isEnabled() { return false } -} +} \ No newline at end of file diff --git a/src/test/groovy/com/cloudogu/gitops/ApplicationConfiguratorTest.groovy b/src/test/groovy/com/cloudogu/gitops/ApplicationConfiguratorTest.groovy index 8c013df96..91cbf9fe2 100644 --- a/src/test/groovy/com/cloudogu/gitops/ApplicationConfiguratorTest.groovy +++ b/src/test/groovy/com/cloudogu/gitops/ApplicationConfiguratorTest.groovy @@ -3,18 +3,21 @@ package com.cloudogu.gitops import com.cloudogu.gitops.config.ApplicationConfigurator import com.cloudogu.gitops.config.CommonFeatureConfig import com.cloudogu.gitops.config.Config -import com.cloudogu.gitops.features.git.config.ScmTenantSchema -import com.cloudogu.gitops.features.Content +import com.cloudogu.gitops.features.ContentLoader import com.cloudogu.gitops.features.Jenkins import com.cloudogu.gitops.features.argocd.ArgoCD -import com.cloudogu.gitops.scmm.ScmmRepoProvider -import com.cloudogu.gitops.scmm.api.ScmmApiClient +import com.cloudogu.gitops.features.git.GitHandler +import com.cloudogu.gitops.features.git.config.ScmTenantSchema +import com.cloudogu.gitops.git.GitRepoFactory import com.cloudogu.gitops.utils.FileSystemUtils import com.cloudogu.gitops.utils.HelmClient import com.cloudogu.gitops.utils.K8sClient import com.cloudogu.gitops.utils.TestLogger +import com.cloudogu.gitops.utils.git.GitHandlerForTests +import com.cloudogu.gitops.utils.git.ScmManagerMock import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.mockito.Mock import org.mockito.Mockito import static com.github.stefanbirkner.systemlambda.SystemLambda.withEnvironmentVariable @@ -33,9 +36,12 @@ class ApplicationConfiguratorTest { private FileSystemUtils fileSystemUtils private TestLogger testLogger private CommonFeatureConfig commonFeatureConfig - private Content featureContent + private ContentLoader featureContent private ArgoCD featureArgoCd + @Mock + ScmManagerMock scmManagerMock = new ScmManagerMock() + Config testConfig = Config.fromMap([ application: [ localHelmChartFolder: 'someValue', @@ -86,10 +92,12 @@ class ApplicationConfiguratorTest { K8sClient k8sClient = Mockito.mock(K8sClient) HelmClient helmClient = Mockito.mock(HelmClient) - ScmmRepoProvider scmmRepoProvider = Mockito.mock(ScmmRepoProvider) + GitRepoFactory gitRepoFactory = Mockito.mock(GitRepoFactory) - featureContent = Mockito.spy(new Content(testConfig, k8sClient, scmmRepoProvider, Mockito.mock(ScmmApiClient), Mockito.mock(Jenkins))) - featureArgoCd = Mockito.spy(new ArgoCD(testConfig, k8sClient, helmClient, fileSystemUtils, scmmRepoProvider)) + + GitHandler gitHandler = new GitHandlerForTests(testConfig, scmManagerMock) + featureContent = Mockito.spy(new ContentLoader(testConfig, k8sClient, gitRepoFactory, Mockito.mock(Jenkins), gitHandler)) + featureArgoCd = Mockito.spy(new ArgoCD(testConfig, k8sClient, helmClient, fileSystemUtils, gitRepoFactory, gitHandler)) } @Test @@ -129,31 +137,6 @@ class ApplicationConfiguratorTest { assertThat(actualConfig.jenkins.urlForScm).isEmpty() } - @Test - void 'Fails if jenkins is external and scmm is internal or the other way round'() { - testConfig.jenkins.active = true - testConfig.jenkins.url = 'external' - testConfig.scm.scmManager.url = '' - - def exception = shouldFail(RuntimeException) { - commonFeatureConfig.validateConfig(testConfig) - } - assertThat(exception.message).isEqualTo('When setting jenkins URL, scmm URL must also be set and the other way round') - - testConfig.jenkins.url = '' - testConfig.scm.scmManager.url = 'external' - - exception = shouldFail(RuntimeException) { - commonFeatureConfig.validateConfig(testConfig) - } - assertThat(exception.message).isEqualTo('When setting jenkins URL, scmm URL must also be set and the other way round') - - - testConfig.jenkins.active = false - commonFeatureConfig.validateConfig(testConfig) - // no exception when jenkins is not active - } - @Test void 'Fails if monitoring local is not set'() { testConfig.application.mirrorRepos = true