diff --git a/scripts/scm-manager/init-scmm.sh b/scripts/scm-manager/init-scmm.sh deleted file mode 100755 index de958a691..000000000 --- a/scripts/scm-manager/init-scmm.sh +++ /dev/null @@ -1,389 +0,0 @@ -#!/usr/bin/env bash -set -o errexit -o nounset -o pipefail -set -x - -ABSOLUTE_BASEDIR="$(cd "$(dirname $0)" && pwd)" -source ${ABSOLUTE_BASEDIR}/../utils.sh - -if [[ $TRACE == true ]]; then - set -x -fi - -SCMM_PROTOCOL=http - -PLAYGROUND_DIR="$(cd ${ABSOLUTE_BASEDIR} && cd ../.. && pwd)" - -if [[ $INSECURE == true ]]; then - CURL_HOME="${PLAYGROUND_DIR}" - export CURL_HOME - export GIT_SSL_NO_VERIFY=1 -fi - -function initSCMM() { - - SCMM_HOST=$(getHost "${SCMM_URL}") - SCMM_PROTOCOL=$(getProtocol "${SCMM_URL}") - - echo "SCM provider: ${SCM_PROVIDER}" - if [[ ${INTERNAL_SCMM} == true ]]; then - setExternalHostnameIfNecessary 'SCMM' 'scmm' "${NAME_PREFIX}scm-manager" - fi - - [[ "${SCMM_URL}" != *scm ]] && SCMM_URL=${SCMM_URL}/scm - - if [[ ${SCM_PROVIDER} == "scm-manager" ]]; then - configureScmmManager - fi -} - -function pushHelmChartRepo() { - TARGET_REPO_SCMM="$1" - - TMP_REPO=$(mktemp -d) - git clone -n "${SPRING_BOOT_HELM_CHART_REPO}" "${TMP_REPO}" --quiet - ( - cd "${TMP_REPO}" - # Checkout a defined commit in order to get a deterministic result - git checkout ${SPRING_BOOT_HELM_CHART_COMMIT} --quiet - - # Create a defined version to use in demo applications - git tag 1.0.0 - - git branch --quiet -d main - git checkout --quiet -b main - - waitForScmManager - - local remote_url - - if [[ ${SCM_PROVIDER} == "scm-manager" ]]; then - remote_url="${SCMM_PROTOCOL}://${SCMM_USERNAME}:${SCMM_PASSWORD}@${SCMM_HOST}/${SCM_ROOT_PATH}/${TARGET_REPO_SCMM}" - elif [[ ${SCM_PROVIDER} == "gitlab" ]]; then - remote_url="${SCMM_PROTOCOL}://oauth2:${SCMM_PASSWORD}@${SCMM_HOST}/${SCM_ROOT_PATH}/${TARGET_REPO_SCMM}.git" - else - echo "Unsupported SCM provider: ${SCM_PROVIDER}" - return 1 - fi - - git push "${remote_url}" HEAD:main --force --quiet - git push "${remote_url}" refs/tags/1.0.0 --quiet --force - ) - - rm -rf "${TMP_REPO}" - - setDefaultBranch "${TARGET_REPO_SCMM}" -} - -function pushHelmChartRepoWithDependency() { - TARGET_REPO_SCMM="$1" - - TMP_REPO=$(mktemp -d) - git clone -n "${SPRING_BOOT_HELM_CHART_REPO}" "${TMP_REPO}" --quiet - ( - cd "${TMP_REPO}" - # Checkout a defined commit in order to get a deterministic result - git checkout ${SPRING_BOOT_HELM_CHART_COMMIT} --quiet - - # Create a defined version to use in demo applications - git tag 1.0.0 - - git branch --quiet -d main - git checkout --quiet -b main - - echo "dependencies: -- name: podinfo - version: \"5.2.0\" - repository: \"https://stefanprodan.github.io/podinfo\"" >>./Chart.yaml - - git commit -a -m "Added dependency" --quiet - - waitForScmManager - - local remote_url - - if [[ ${SCM_PROVIDER} == "scm-manager" ]]; then - remote_url="${SCMM_PROTOCOL}://${SCMM_USERNAME}:${SCMM_PASSWORD}@${SCMM_HOST}/${SCM_ROOT_PATH}/${TARGET_REPO_SCMM}" - elif [[ ${SCM_PROVIDER} == "gitlab" ]]; then - remote_url="${SCMM_PROTOCOL}://oauth2:${SCMM_PASSWORD}@${SCMM_HOST}/${SCM_ROOT_PATH}/${TARGET_REPO_SCMM}.git" - else - echo "Unsupported SCM provider: ${SCM_PROVIDER}" - return 1 - fi - - git push "${remote_url}" HEAD:main --force --quiet - git push "${remote_url}" refs/tags/1.0.0 --quiet --force - ) - - rm -rf "${TMP_REPO}" - - setDefaultBranch "${TARGET_REPO_SCMM}" -} - -function pushRepoMirror() { - SOURCE_REPO_URL="$1" - TARGET_REPO_SCMM="$2" - DEFAULT_BRANCH="${3:-main}" - - TMP_REPO=$(mktemp -d) - git clone --bare "${SOURCE_REPO_URL}" "${TMP_REPO}" --quiet - ( - cd "${TMP_REPO}" - waitForScmManager - - local remote_url - - if [[ ${SCM_PROVIDER} == "scm-manager" ]]; then - remote_url="${SCMM_PROTOCOL}://${SCMM_USERNAME}:${SCMM_PASSWORD}@${SCMM_HOST}/${SCM_ROOT_PATH}/${TARGET_REPO_SCMM}" - elif [[ ${SCM_PROVIDER} == "gitlab" ]]; then - remote_url="${SCMM_PROTOCOL}://oauth2:${SCMM_PASSWORD}@${SCMM_HOST}/${SCM_ROOT_PATH}/${TARGET_REPO_SCMM}.git" - else - echo "Unsupported SCM provider: ${SCM_PROVIDER}" - return 1 - fi - git push --mirror "${remote_url}" --force --quiet - ) - - rm -rf "${TMP_REPO}" - - setDefaultBranch "${TARGET_REPO_SCMM}" "${DEFAULT_BRANCH}" -} - -function setDefaultBranch() { - TARGET_REPO_SCMM="$1" - DEFAULT_BRANCH="${2:-main}" - - curl -s -L -X PUT -H 'Content-Type: application/vnd.scmm-gitConfig+json' \ - --data-raw "{\"defaultBranch\":\"${DEFAULT_BRANCH}\"}" \ - "${SCMM_PROTOCOL}://${SCMM_USERNAME}:${SCMM_PASSWORD}@${SCMM_HOST}/api/v2/config/git/${TARGET_REPO_SCMM}" -} - -function configureScmmManager() { - GITOPS_PASSWORD=${SCMM_PASSWORD} - - METRICS_USERNAME="${NAME_PREFIX}metrics" - METRICS_PASSWORD=${SCMM_PASSWORD} - - waitForScmManager - - setConfig - - addUser "${GITOPS_USERNAME}" "${GITOPS_PASSWORD}" "changeme@test.local" - addUser "${METRICS_USERNAME}" "${METRICS_PASSWORD}" "changeme@test.local" - setPermissionForUser "${METRICS_USERNAME}" "metrics:read" - - # Install necessary plugins - installScmmPlugins - - configJenkins -} - -function installScmmPlugins() { - if [[ "${SKIP_PLUGINS:-false}" == "true" ]]; then - echo "Skipping SCM plugin installation due to SKIP_PLUGINS=true" - return - fi - - if [ -n "${JENKINS_URL_FOR_SCMM}" ]; then - installScmmPlugin "scm-jenkins-plugin" "false" - fi - - local restart_flag="true" - [[ "${SKIP_RESTART}" == "true" ]] && { - echo "Skipping SCMM restart due to SKIP_RESTART=true" - restart_flag="false" - } - - installScmmPlugin "scm-mail-plugin" "false" - installScmmPlugin "scm-review-plugin" "false" - installScmmPlugin "scm-code-editor-plugin" "false" - installScmmPlugin "scm-editor-plugin" "false" - installScmmPlugin "scm-landingpage-plugin" "false" - installScmmPlugin "scm-el-plugin" "false" - installScmmPlugin "scm-readme-plugin" "false" - installScmmPlugin "scm-webhook-plugin" "false" - installScmmPlugin "scm-ci-plugin" "false" - # Last plugin usually triggers restart - installScmmPlugin "scm-metrics-prometheus-plugin" "$restart_flag" - # Wait for SCM-Manager to restart - if [[ "$restart_flag" == "true" ]]; then - sleep 1 - waitForScmManager - fi -} - -function addRepo() { - NAMESPACE="${1}" - NAME="${2}" - DESCRIPTION="${3:-}" - local PARAM="${4:-false}" - if [[ "${PARAM,,}" == "true" ]]; then - HOST=$(getHost "${CENTRAL_SCM_URL%/}") # Remove trailing slash if present, we already got this in the api requests: /api - USERNAME="${CENTRAL_SCM_USERNAME}" - PASSWORD="${CENTRAL_SCM_PASSWORD}" - else - HOST="${SCMM_HOST}" - USERNAME="${SCMM_USERNAME}" - PASSWORD="${SCMM_PASSWORD}" - fi - - printf 'Adding Repo %s/%s ... ' "${NAMESPACE}" "${NAME}" - - STATUS=$(curl -i -s -L -o /dev/null --write-out '%{http_code}' -X POST \ - -H "Content-Type: application/vnd.scmm-repository+json;v=2" \ - --data "{\"name\":\"${NAME}\",\"namespace\":\"${NAMESPACE}\",\"type\":\"git\",\"description\":\"${DESCRIPTION}\",\"contextEntries\":{},\"_links\":{}}" \ - "${SCMM_PROTOCOL}://${USERNAME}:${PASSWORD}@${HOST}/api/v2/repositories/?initialize=true") && EXIT_STATUS=$? || EXIT_STATUS=$? - - if [ $EXIT_STATUS -ne 0 ]; then - echo "Adding Repo failed with exit code: curl: ${EXIT_STATUS}, HTTP Status: ${STATUS}" - exit $EXIT_STATUS - fi - - printStatus "${STATUS}" -} - -function setConfig() { - printf 'Setting config' - - STATUS=$(curl -i -s -L -o /dev/null --write-out '%{http_code}' -X PUT -H "Content-Type: application/vnd.scmm-config+json;v=2" \ - --data "{\"proxyPassword\":null,\"proxyPort\":8080,\"proxyServer\":\"proxy.mydomain.com\",\"proxyUser\":null,\"enableProxy\":false,\"realmDescription\":\"SONIA :: SCM Manager\",\"disableGroupingGrid\":false,\"dateFormat\":\"YYYY-MM-DD HH:mm:ss\",\"anonymousAccessEnabled\":false,\"anonymousMode\":\"OFF\",\"baseUrl\":\"${SCMM_URL_FOR_JENKINS}\",\"forceBaseUrl\":false,\"loginAttemptLimit\":-1,\"proxyExcludes\":[],\"skipFailedAuthenticators\":false,\"pluginUrl\":\"https://plugin-center-api.scm-manager.org/api/v1/plugins/{version}?os={os}&arch={arch}\",\"loginAttemptLimitTimeout\":300,\"enabledXsrfProtection\":true,\"namespaceStrategy\":\"CustomNamespaceStrategy\",\"loginInfoUrl\":\"https://login-info.scm-manager.org/api/v1/login-info\",\"releaseFeedUrl\":\"https://scm-manager.org/download/rss.xml\",\"mailDomainName\":\"scm-manager.local\",\"adminGroups\":[],\"adminUsers\":[]}" \ - "${SCMM_PROTOCOL}://${SCMM_USERNAME}:${SCMM_PASSWORD}@${SCMM_HOST}/api/v2/config") && EXIT_STATUS=$? || EXIT_STATUS=$? - if [ $EXIT_STATUS != 0 ]; then - echo "Setting config failed with exit code: curl: ${EXIT_STATUS}, HTTP Status: ${STATUS}" - exit $EXIT_STATUS - fi - - printStatus "${STATUS}" -} - -function addUser() { - printf 'Adding User %s ... ' "${1}" - - STATUS=$(curl -i -s -L -o /dev/null --write-out '%{http_code}' -X POST -H "Content-Type: application/vnd.scmm-user+json;v=2" \ - --data "{\"name\":\"${1}\",\"displayName\":\"${1}\",\"mail\":\"${3}\",\"external\":false,\"password\":\"${2}\",\"active\":true,\"_links\":{}}" \ - "${SCMM_PROTOCOL}://${SCMM_USERNAME}:${SCMM_PASSWORD}@${SCMM_HOST}/api/v2/users") && EXIT_STATUS=$? || EXIT_STATUS=$? - if [ $EXIT_STATUS != 0 ]; then - echo "Adding User failed with exit code: curl: ${EXIT_STATUS}, HTTP Status: ${STATUS}" - exit $EXIT_STATUS - fi - - printStatus "${STATUS}" -} - -function setPermission() { - printf 'Setting permission on Repo %s/%s for %s... ' "${1}" "${2}" "${3}" - - STATUS=$(curl -i -s -L -o /dev/null --write-out '%{http_code}' -X POST -H "Content-Type: application/vnd.scmm-repositoryPermission+json" \ - --data "{\"name\":\"${3}\",\"role\":\"${4}\",\"verbs\":[],\"groupPermission\":false}" \ - "${SCMM_PROTOCOL}://${SCMM_USERNAME}:${SCMM_PASSWORD}@${SCMM_HOST}/api/v2/repositories/${1}/${2}/permissions/") && EXIT_STATUS=$? || EXIT_STATUS=$? - if [ $EXIT_STATUS != 0 ]; then - echo "Setting Permission failed with exit code: curl: ${EXIT_STATUS}, HTTP Status: ${STATUS}" - exit $EXIT_STATUS - fi - - printStatus "${STATUS}" -} - -function setPermissionForNamespace() { - printf 'Setting permission %s on Namespace %s for %s... ' "${3}" "${1}" "${2}" - - STATUS=$(curl -i -s -L -o /dev/null --write-out '%{http_code}' -X POST -H "Content-Type: application/vnd.scmm-repositoryPermission+json;v=2" \ - --data "{\"name\":\"${2}\",\"role\":\"${3}\",\"verbs\":[],\"groupPermission\":false}" \ - "${SCMM_PROTOCOL}://${SCMM_USERNAME}:${SCMM_PASSWORD}@${SCMM_HOST}/api/v2/namespaces/${1}/permissions/") && EXIT_STATUS=$? || EXIT_STATUS=$? - if [ $EXIT_STATUS != 0 ]; then - echo "Setting Permission failed with exit code: curl: ${EXIT_STATUS}, HTTP Status: ${STATUS}" - exit $EXIT_STATUS - fi - - printStatus "${STATUS}" -} - -function setPermissionForUser() { - printf 'Setting permission %s for %s... ' "${2}" "${1}" - - STATUS=$(curl -i -s -L -o /dev/null --write-out '%{http_code}' -X PUT -H "Content-Type: application/vnd.scmm-permissionCollection+json;v=2" \ - --data "{\"permissions\":[\"${2}\"]}" \ - "${SCMM_PROTOCOL}://${SCMM_USERNAME}:${SCMM_PASSWORD}@${SCMM_HOST}/api/v2/users/${1}/permissions") && EXIT_STATUS=$? || EXIT_STATUS=$? - if [ $EXIT_STATUS != 0 ]; then - echo "Setting Permission failed with exit code: curl: ${EXIT_STATUS}, HTTP Status: ${STATUS}" - exit $EXIT_STATUS - fi - - printStatus "${STATUS}" -} - -function installScmmPlugin() { - DO_RESTART="?restart=false" - if [[ "${2}" == true ]]; then - DO_RESTART="?restart=true" - fi - - printf 'Installing Plugin %s ... ' "${1}" - - STATUS=$(curl -i -s -L -o /dev/null --write-out '%{http_code}' -X POST -H "accept: */*" --data "" \ - "${SCMM_PROTOCOL}://${SCMM_USERNAME}:${SCMM_PASSWORD}@${SCMM_HOST}/api/v2/plugins/available/${1}/install${DO_RESTART}") && EXIT_STATUS=$? || EXIT_STATUS=$? - if [ $EXIT_STATUS != 0 ]; then - echo "Installing Plugin failed with exit code: curl: ${EXIT_STATUS}, HTTP Status: ${STATUS}" - exit $EXIT_STATUS - fi - - printStatus "${STATUS}" -} - -function configJenkins() { - - if [ -n "${JENKINS_URL_FOR_SCMM}" ]; then - printf 'Configuring Jenkins plugin in SCM-Manager ... ' - - STATUS=$(curl -i -s -L -o /dev/null --write-out '%{http_code}' -X PUT -H 'Content-Type: application/json' \ - --data-raw "{\"disableRepositoryConfiguration\":false,\"disableMercurialTrigger\":false,\"disableGitTrigger\":false,\"disableEventTrigger\":false,\"url\":\"${JENKINS_URL_FOR_SCMM}\"}" \ - "${SCMM_PROTOCOL}://${SCMM_USERNAME}:${SCMM_PASSWORD}@${SCMM_HOST}/api/v2/config/jenkins/") && EXIT_STATUS=$? || EXIT_STATUS=$? - if [ $EXIT_STATUS != 0 ]; then - echo "Configuring Jenkins failed with exit code: curl: ${EXIT_STATUS}, HTTP Status: ${STATUS}" - exit $EXIT_STATUS - fi - - printStatus "${STATUS}" - fi -} - -function waitForScmManager() { - - echo -n "Waiting for Scmm to become available at ${SCMM_PROTOCOL}://${SCMM_HOST}/api/v2" - - HTTP_CODE="0" - while [[ "${HTTP_CODE}" -ne "200" ]]; do - HTTP_CODE="$(curl -s -L -o /dev/null --max-time 10 -w ''%{http_code}'' "${SCMM_PROTOCOL}://${SCMM_HOST}/api/v2")" || true - echo -n "." - sleep 2 - done - echo "" -} - -function getHost() { - local SCMM_URL="$1" - - local CLEANED_URL="${SCMM_URL#http://}" - CLEANED_URL="${CLEANED_URL#https://}" - - echo "${CLEANED_URL}" -} - -function getProtocol() { - local SCMM_URL="$1" - if [[ "${SCMM_URL}" == https://* ]]; then - echo "https" - elif [[ "${SCMM_URL}" == http://* ]]; then - echo "http" - fi -} - -function printStatus() { - STATUS_CODE=${1} - if [ "${STATUS_CODE}" -eq 200 ] || [ "${STATUS_CODE}" -eq 201 ] || [ "${STATUS_CODE}" -eq 302 ] || [ "${STATUS_CODE}" -eq 204 ] || [ "${STATUS_CODE}" -eq 409 ]; then - echo -e ' \u2705' - else - echo -e ' \u274c ' "(status code: $STATUS_CODE)" - fi -} - -initSCMM "$@" \ No newline at end of file diff --git a/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScripted.groovy b/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScripted.groovy index 03751179f..814011491 100644 --- a/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScripted.groovy +++ b/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScripted.groovy @@ -54,7 +54,7 @@ class GitopsPlaygroundCliMainScripted { def httpClientFactory = new HttpClientFactory() - def scmmRepoProvider = new GitRepoFactory(config, fileSystemUtils) + def gitRepoFactory = new GitRepoFactory(config, fileSystemUtils) def helmStrategy = new HelmStrategy(config, helmClient) @@ -67,14 +67,14 @@ class GitopsPlaygroundCliMainScripted { if (config.application.destroy) { context.registerSingleton(new Destroyer([ - new ArgoCDDestructionHandler(config, k8sClient, scmmRepoProvider, helmClient, fileSystemUtils, gitHandler), + new ArgoCDDestructionHandler(config, k8sClient, gitRepoFactory, helmClient, fileSystemUtils, gitHandler), new ScmmDestructionHandler(config), new JenkinsDestructionHandler(new JobManager(jenkinsApiClient), config, new GlobalPropertyManager(jenkinsApiClient)) ])) } else { - def deployer = new Deployer(config, new ArgoCdApplicationStrategy(config, fileSystemUtils, scmmRepoProvider, gitHandler), helmStrategy) + def deployer = new Deployer(config, new ArgoCdApplicationStrategy(config, fileSystemUtils, gitRepoFactory, gitHandler), helmStrategy) - def airGappedUtils = new AirGappedUtils(config, scmmRepoProvider, fileSystemUtils, helmClient, gitHandler) + def airGappedUtils = new AirGappedUtils(config, gitRepoFactory, fileSystemUtils, helmClient, gitHandler) def jenkins = new Jenkins(config, executor, fileSystemUtils, new GlobalPropertyManager(jenkinsApiClient), new JobManager(jenkinsApiClient), new UserManager(jenkinsApiClient), @@ -83,17 +83,16 @@ class GitopsPlaygroundCliMainScripted { // make sure the order of features is in same order as the @Order values context.registerSingleton(new Application(config, [ new Registry(config, fileSystemUtils, k8sClient, helmStrategy), - new ScmManagerSetup(config, executor, fileSystemUtils, helmStrategy, k8sClient, networkingUtils), gitHandler, jenkins, - new ArgoCD(config, k8sClient, helmClient, fileSystemUtils, scmmRepoProvider, gitHandler), + new ArgoCD(config, k8sClient, helmClient, fileSystemUtils, gitRepoFactory, gitHandler), new IngressNginx(config, fileSystemUtils, deployer, k8sClient, airGappedUtils, gitHandler), new CertManager(config, fileSystemUtils, deployer, k8sClient, airGappedUtils, gitHandler), new Mailhog(config, fileSystemUtils, deployer, k8sClient, airGappedUtils, gitHandler), - new PrometheusStack(config, fileSystemUtils, deployer, k8sClient, airGappedUtils, scmmRepoProvider, gitHandler), + new PrometheusStack(config, fileSystemUtils, deployer, k8sClient, airGappedUtils, gitRepoFactory, gitHandler), new ExternalSecretsOperator(config, fileSystemUtils, deployer, k8sClient, airGappedUtils, gitHandler), new Vault(config, fileSystemUtils, k8sClient, deployer, airGappedUtils, gitHandler), - new ContentLoader(config, k8sClient, scmmRepoProvider, jenkins, gitHandler), + new ContentLoader(config, k8sClient, gitRepoFactory, jenkins, gitHandler), ])) } } diff --git a/src/main/groovy/com/cloudogu/gitops/config/Config.groovy b/src/main/groovy/com/cloudogu/gitops/config/Config.groovy index d1060ec4f..1cb6cd5a5 100644 --- a/src/main/groovy/com/cloudogu/gitops/config/Config.groovy +++ b/src/main/groovy/com/cloudogu/gitops/config/Config.groovy @@ -713,7 +713,6 @@ class Config { INIT, RESET, UPGRADE } - private static final ObjectMapper objectMapper = new ObjectMapper() .registerModule(new SimpleModule().addSerializer(GString, new JsonSerializer() { @Override diff --git a/src/main/groovy/com/cloudogu/gitops/features/ScmManagerSetup.groovy b/src/main/groovy/com/cloudogu/gitops/features/ScmManagerSetup.groovy index 43db3ff5b..e69de29bb 100644 --- a/src/main/groovy/com/cloudogu/gitops/features/ScmManagerSetup.groovy +++ b/src/main/groovy/com/cloudogu/gitops/features/ScmManagerSetup.groovy @@ -1,140 +0,0 @@ -package com.cloudogu.gitops.features - -import com.cloudogu.gitops.Feature -import com.cloudogu.gitops.config.Config -import com.cloudogu.gitops.features.deployment.DeploymentStrategy -import com.cloudogu.gitops.features.deployment.HelmStrategy -import com.cloudogu.gitops.features.git.config.util.ScmProviderType -import com.cloudogu.gitops.utils.* -import groovy.util.logging.Slf4j -import io.micronaut.core.annotation.Order -import jakarta.inject.Singleton - -@Slf4j -@Singleton -@Order(50) -class ScmManagerSetup extends Feature { - - static final String HELM_VALUES_PATH = "scm-manager/values.ftl.yaml" - - String namespace - private Config config - private CommandExecutor commandExecutor - private FileSystemUtils fileSystemUtils - private DeploymentStrategy deployer - private K8sClient k8sClient - private NetworkingUtils networkingUtils - String centralSCMUrl - - ScmManagerSetup( - Config config, - CommandExecutor commandExecutor, - FileSystemUtils fileSystemUtils, - // For now we deploy imperatively using helm to avoid order problems. In future we could deploy via argocd. - HelmStrategy deployer, - K8sClient k8sClient, - NetworkingUtils networkingUtils - ) { - this.config = config - this.commandExecutor = commandExecutor - this.fileSystemUtils = fileSystemUtils - this.deployer = deployer - this.k8sClient = k8sClient - this.networkingUtils = networkingUtils - - if (config.scm.internal) { - this.namespace = "${config.application.namePrefix}scm-manager" - } - } - - @Override - boolean isEnabled() { - return config.scm.scmProviderType == ScmProviderType.SCM_MANAGER -// false - } - - @Override - void enable() { - if (config.scm.scmManager.internal) { - String releaseName = 'scmm' - - k8sClient.createNamespace(namespace) - - def helmConfig = config.scm.scmManager.helm - - def templatedMap = templateToMap(HELM_VALUES_PATH, [ - host : config.scm.scmManager.ingress, - remote : config.application.remote, - username : config.scm.scmManager.username, - password : config.scm.scmManager.password, - helm : config.scm.scmManager.helm, - releaseName: releaseName - ]) - - def mergedMap = MapUtils.deepMerge(helmConfig.values, templatedMap) - def tempValuesPath = fileSystemUtils.writeTempFile(mergedMap) - - deployer.deployFeature( - helmConfig.repoURL, - 'scm-manager', - helmConfig.chart, - helmConfig.version, - namespace, - 'scmm', - tempValuesPath - ) - - // Update scmm.url after it is deployed (and ports are known) - // Defined here: https://github.com/scm-manager/scm-manager/blob/3.2.1/scm-packaging/helm/src/main/chart/templates/_helpers.tpl#L14-L25 - String contentPath = "/scm" - - - if (config.application.runningInsideK8s) { - log.debug("Setting scmm url to k8s service, since installation is running inside k8s") - config.scm.scmManager.url = networkingUtils.createUrl("${releaseName}.${namespace}.svc.cluster.local", "80", contentPath) - } else { - log.debug("Setting internal configs for local single node cluster with internal scmm. Waiting for NodePort...") - def port = k8sClient.waitForNodePort(releaseName, namespace) - String clusterBindAddress = networkingUtils.findClusterBindAddress() - config.scm.scmManager.url = networkingUtils.createUrl(clusterBindAddress, port, contentPath) - - if (config.multiTenant.useDedicatedInstance && config.multiTenant.scmProviderType == ScmProviderType.SCM_MANAGER) { - log.debug("Setting internal configs for local single node cluster with internal central scmm. Waiting for NodePort...") - def portCentralScm = k8sClient.waitForNodePort(releaseName, config.multiTenant.scmManager.namespace) - centralSCMUrl = networkingUtils.createUrl(clusterBindAddress, portCentralScm, contentPath) - } - } - } - - //disable setup for faster testing - commandExecutor.execute("${fileSystemUtils.rootDir}/scripts/scm-manager/init-scmm.sh", [ - - GIT_COMMITTER_NAME : config.application.gitName, - GIT_COMMITTER_EMAIL : config.application.gitEmail, - GIT_AUTHOR_NAME : config.application.gitName, - GIT_AUTHOR_EMAIL : config.application.gitEmail, - GITOPS_USERNAME : config.scm.scmManager.gitOpsUsername, - TRACE : config.application.trace, - SCMM_URL : config.scm.scmManager.url, - SCMM_USERNAME : config.scm.scmManager.username, - SCMM_PASSWORD : config.scm.scmManager.password, - JENKINS_URL : config.jenkins.url, - INTERNAL_SCMM : config.scm.scmManager.internal, - JENKINS_URL_FOR_SCMM : config.jenkins.urlForScm, - SCMM_URL_FOR_JENKINS : config.scm.scmManager.urlForJenkins, - // Used indirectly in utils.sh 😬 - REMOTE_CLUSTER : config.application.remote, - INSTALL_ARGOCD : config.features.argocd.active, - NAME_PREFIX : config.application.namePrefix, - INSECURE : config.application.insecure, - SCM_ROOT_PATH : config.scm.scmManager.rootPath, - SCM_PROVIDER : 'scm-manager', - CONTENT_EXAMPLES : false, - SKIP_RESTART : config.scm.scmManager.skipRestart, - SKIP_PLUGINS : config.scm.scmManager.skipPlugins, - CENTRAL_SCM_URL : config.multiTenant.scmManager.url, - CENTRAL_SCM_USERNAME : config.multiTenant.scmManager.username, - CENTRAL_SCM_PASSWORD : config.multiTenant.scmManager.password - ]) - } -} \ No newline at end of file diff --git a/src/main/groovy/com/cloudogu/gitops/features/git/GitHandler.groovy b/src/main/groovy/com/cloudogu/gitops/features/git/GitHandler.groovy index 4c5543d29..472961bc7 100644 --- a/src/main/groovy/com/cloudogu/gitops/features/git/GitHandler.groovy +++ b/src/main/groovy/com/cloudogu/gitops/features/git/GitHandler.groovy @@ -92,7 +92,7 @@ class GitHandler extends Feature { case ScmProviderType.SCM_MANAGER: def prefixedNamespace = "${config.application.namePrefix}scm-manager".toString() config.scm.scmManager.namespace = prefixedNamespace - this.tenant = new ScmManager(this.config, config.scm.scmManager, k8sClient, networkingUtils) + this.tenant = new ScmManager(this.config, config.scm.scmManager, helmStrategy,k8sClient, networkingUtils) // this.tenant.setup() setup will be here in future break default: @@ -105,7 +105,7 @@ class GitHandler extends Feature { this.central = new Gitlab(this.config, this.config.multiTenant.gitlab) break case ScmProviderType.SCM_MANAGER: - this.central = new ScmManager(this.config, config.multiTenant.scmManager, k8sClient, networkingUtils) + this.central = new ScmManager(this.config, config.multiTenant.scmManager, helmStrategy,k8sClient, networkingUtils) break default: throw new IllegalArgumentException("Unsupported SCM-Central provider: ${config.scm.scmProviderType}") diff --git a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManager.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManager.groovy index 69ebb87cc..bf4765c60 100644 --- a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManager.groovy +++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManager.groovy @@ -2,6 +2,7 @@ package com.cloudogu.gitops.git.providers.scmmanager import com.cloudogu.gitops.config.Config import com.cloudogu.gitops.config.Credentials +import com.cloudogu.gitops.features.deployment.HelmStrategy import com.cloudogu.gitops.features.git.config.util.ScmManagerConfig import com.cloudogu.gitops.git.providers.AccessRole import com.cloudogu.gitops.git.providers.GitProvider @@ -17,17 +18,40 @@ import retrofit2.Response @Slf4j class ScmManager implements GitProvider { - private final ScmManagerUrlResolver urls - private final ScmManagerApiClient apiClient - private final ScmManagerConfig scmmConfig + ScmManagerUrlResolver urls + ScmManagerApiClient apiClient + ScmManagerConfig scmmConfig - ScmManager(Config config, ScmManagerConfig scmmConfig, K8sClient k8sClient, NetworkingUtils networkingUtils) { + NetworkingUtils networkingUtils + HelmStrategy helmStrategy + K8sClient k8sClient + Config config + ScmManagerSetup scmManagerSetup + + ScmManager(Config config, ScmManagerConfig scmmConfig, HelmStrategy helmStrategy, K8sClient k8sClient, NetworkingUtils networkingUtils) { this.scmmConfig = scmmConfig - this.urls = new ScmManagerUrlResolver(config, scmmConfig, k8sClient, networkingUtils) - this.apiClient = new ScmManagerApiClient(urls.clientApiBase().toString(), scmmConfig.credentials, config.application.insecure) + this.config = config + this.helmStrategy = helmStrategy + this.k8sClient = k8sClient + this.networkingUtils = networkingUtils + init() + } + + void init() { + // --- Init Setup --- + if (this.scmmConfig.internal) { + this.scmManagerSetup = new ScmManagerSetup(this) + this.scmManagerSetup.setupHelm() + this.urls = new ScmManagerUrlResolver(this.config, this.scmmConfig, this.k8sClient, this.networkingUtils) + this.apiClient = new ScmManagerApiClient(this.urls.clientApiBase().toString(), this.scmmConfig.credentials, this.config.application.insecure) + this.scmManagerSetup.waitForScmmAvailable() + this.scmManagerSetup.configure() + } else { + this.urls = new ScmManagerUrlResolver(this.config, this.scmmConfig, this.k8sClient, this.networkingUtils) + this.apiClient = new ScmManagerApiClient(this.urls.clientApiBase().toString(), this.scmmConfig.credentials, this.config.application.insecure) + } } - // --- Git operations --- @Override boolean createRepository(String repoTarget, String description, boolean initialize) { diff --git a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerSetup.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerSetup.groovy new file mode 100644 index 000000000..4877fa7d2 --- /dev/null +++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerSetup.groovy @@ -0,0 +1,187 @@ +package com.cloudogu.gitops.git.providers.scmmanager + +import com.cloudogu.gitops.git.providers.scmmanager.api.ScmManagerApiClient +import com.cloudogu.gitops.git.providers.scmmanager.api.ScmManagerUser +import com.cloudogu.gitops.utils.FileSystemUtils +import com.cloudogu.gitops.utils.MapUtils +import com.cloudogu.gitops.utils.TemplatingEngine +import groovy.util.logging.Slf4j + +@Slf4j +class ScmManagerSetup { + + private ScmManager scmManager + + static final String HELM_VALUES_PATH = "scm-manager/values.ftl.yaml" + + ScmManagerSetup(ScmManager scmManager) { + this.scmManager = scmManager + } + + void waitForScmmAvailable(int timeoutSeconds = 120, int intervalMillis = 5000, int startDelay = 0) { + long startTime = System.currentTimeMillis() + long timeoutMillis = timeoutSeconds * 1000L + sleep(startDelay) + while (System.currentTimeMillis() - startTime < timeoutMillis) { + try { + def call = scmManager.apiClient.generalApi().checkScmmAvailable() + def response = call.execute() + + if (response.successful) { + return + } + } catch (Exception e) { + println "Waiting for SCM-Manager... Error: ${e.message}" + } + + + sleep(intervalMillis) + } + throw new RuntimeException("Timeout: SCM-Manager did not respond with 200 OK within ${timeoutSeconds} seconds") + } + + void configure() { + installScmmPlugins() + setSetupConfigs() + configureJenkinsPlugin() + addDefaultUsers() + log.info("ScmManager Setup finished!") + } + + void setupHelm() { + def releaseName = 'scmm' + + def templatedMap = TemplatingEngine.templateToMap(HELM_VALUES_PATH, [ + host : this.scmManager.scmmConfig.ingress, + remote : this.scmManager.config.application.remote, + username : this.scmManager.scmmConfig.credentials.username, + password : this.scmManager.scmmConfig.credentials.password, + helm : this.scmManager.scmmConfig.helm, + releaseName: releaseName + ]) + + def helmConfig = this.scmManager.scmmConfig.helm + def mergedMap = MapUtils.deepMerge(helmConfig.values, templatedMap) + def tempValuesPath = new FileSystemUtils().writeTempFile(mergedMap) + this.scmManager.helmStrategy.deployFeature( + helmConfig.repoURL, + 'scm-manager', + helmConfig.chart, + helmConfig.version, + this.scmManager.scmmConfig.namespace, + releaseName, + tempValuesPath + ) + } + + def installScmmPlugins() { + + if (this.scmManager.config.scm.scmManager.skipPlugins) { + log.info("Skipping SCM plugin installation") + return + } + + def pluginNames = [ + "scm-mail-plugin", + "scm-review-plugin", + "scm-code-editor-plugin", + "scm-editor-plugin", + "scm-landingpage-plugin", + "scm-el-plugin", + "scm-readme-plugin", + "scm-webhook-plugin", + "scm-ci-plugin", + "scm-metrics-prometheus-plugin" + ] + + if (this.scmManager.config.jenkins.active) { + pluginNames.add("scm-jenkins-plugin") + } + Boolean restartForThisPlugin = false + pluginNames.each { String pluginName -> + log.info("Installing Plugin ${pluginName} ...") + restartForThisPlugin = !this.scmManager.config.scm.scmManager.skipRestart && pluginName == pluginNames.last() + ScmManagerApiClient.handleApiResponse(scmManager.apiClient.pluginApi().install(pluginName, restartForThisPlugin)) + } + + log.info("SCM-Manager plugin installation finished successfully!") + if (restartForThisPlugin) { + waitForScmmAvailable(60,2000,100) + } + } + + void setSetupConfigs() { + def setupConfigs = [ + enableProxy : false, + proxyPort : 8080, + proxyServer : "proxy.mydomain.com", + proxyUser : null, + proxyPassword : null, + realmDescription : "SONIA :: SCM Manager", + disableGroupingGrid : false, + dateFormat : "YYYY-MM-DD HH:mm:ss", + anonymousAccessEnabled : false, + anonymousMode : "OFF", + baseUrl : this.scmManager.url, + forceBaseUrl : false, + loginAttemptLimit : -1, + proxyExcludes : [], + skipFailedAuthenticators: false, + pluginUrl : "https://plugin-center-api.scm-manager.org/api/v1/plugins/{version}?os={os}&arch={arch}", + loginAttemptLimitTimeout: 300, + enabledXsrfProtection : true, + namespaceStrategy : "CustomNamespaceStrategy", + loginInfoUrl : "https://login-info.scm-manager.org/api/v1/login-info", + releaseFeedUrl : "https://scm-manager.org/download/rss.xml", + mailDomainName : "scm-manager.local", + adminGroups : [], + adminUsers : [] + ] + + ScmManagerApiClient.handleApiResponse(scmManager.apiClient.generalApi().setConfig(setupConfigs)) + log.debug("Successfully added SCMM Setup Configs") + } + + void configureJenkinsPlugin() { + + def jenkinsPluginConfig = [ + disableRepositoryConfiguration: false, + disableMercurialTrigger : false, + disableGitTrigger : false, + disableEventTrigger : false, + url : this.scmManager.config.jenkins.urlForScm + ] as Map + + ScmManagerApiClient.handleApiResponse(this.scmManager.apiClient.pluginApi().configureJenkinsPlugin(jenkinsPluginConfig)) + log.debug("Successfully configured JenkinsPlugin in SCM-Manager.") + } + + void addDefaultUsers() { + def metricsUsername = "${this.scmManager.config.application.namePrefix}metrics" + addUser(this.scmManager.scmmConfig.gitOpsUsername, this.scmManager.scmmConfig.password) + addUser(metricsUsername, this.scmManager.scmmConfig.password) + grantUserPermissions(metricsUsername, ["metrics:read"]) + } + + void addUser(String username, String password, String email = 'changeme@test.local') { + ScmManagerUser userRequest = [ + name : username, + displayName: username, + mail : email, + external : false, + password : password, + active : true, + _links : [:] + ] + ScmManagerApiClient.handleApiResponse(scmManager.apiClient.usersApi().addUser(userRequest)) + log.debug("Successfully created SCM-Manager User.") + } + + void grantUserPermissions(String username, List permissions) { + def permissionBody = [ + permissions: permissions + ] + ScmManagerApiClient.handleApiResponse(scmManager.apiClient.usersApi().setPermissionForUser(username, permissionBody)) + log.debug("Granted permissions ${permissions} to user ${username}.") + } +} \ No newline at end of file diff --git a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerUrlResolver.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerUrlResolver.groovy index ba894827a..3ad218f85 100644 --- a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerUrlResolver.groovy +++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerUrlResolver.groovy @@ -24,7 +24,6 @@ class ScmManagerUrlResolver { this.net = net } - // ---------- Public API used by ScmManager ---------- /** Client base …/scm (no trailing slash) */ diff --git a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/PluginApi.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/PluginApi.groovy new file mode 100644 index 000000000..5e17d1a12 --- /dev/null +++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/PluginApi.groovy @@ -0,0 +1,18 @@ +package com.cloudogu.gitops.git.providers.scmmanager.api + +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.Headers +import retrofit2.http.POST +import retrofit2.http.PUT +import retrofit2.http.Path +import retrofit2.http.Query + +interface PluginApi { + @POST("v2/plugins/available/{name}/install") + Call install(@Path("name") String name, @Query("restart") Boolean restart) + + @PUT("v2/config/jenkins/") + @Headers("Content-Type: application/json") + Call configureJenkinsPlugin(@Body Map config) +} \ No newline at end of file diff --git a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/RepositoryApi.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/RepositoryApi.groovy index 59871b48c..4893da921 100644 --- a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/RepositoryApi.groovy +++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/RepositoryApi.groovy @@ -7,7 +7,7 @@ import retrofit2.http.* interface RepositoryApi { @DELETE("v2/repositories/{namespace}/{name}") - Call delete(@Path("namespace") String namespace, @Path("name") String name) + Call delete(@Path("namespace") String namespace, @Path("name") String name) @POST("v2/repositories/") @Headers("Content-Type: application/vnd.scmm-repository+json;v=2") diff --git a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/ScmManagerApi.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/ScmManagerApi.groovy index e974b3cd6..d06e832be 100644 --- a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/ScmManagerApi.groovy +++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/ScmManagerApi.groovy @@ -8,10 +8,10 @@ import retrofit2.http.PUT interface ScmManagerApi { - @GET("api/v2") + @GET("v2") Call checkScmmAvailable() - @PUT("api/v2/config") + @PUT("v2/config") @Headers("Content-Type: application/vnd.scmm-config+json;v=2") Call setConfig(@Body Map config) } \ No newline at end of file diff --git a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/ScmManagerApiClient.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/ScmManagerApiClient.groovy index 5d804e8d5..336a16ad3 100644 --- a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/ScmManagerApiClient.groovy +++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/ScmManagerApiClient.groovy @@ -3,13 +3,17 @@ package com.cloudogu.gitops.git.providers.scmmanager.api import com.cloudogu.gitops.config.Credentials import com.cloudogu.gitops.dependencyinjection.HttpClientFactory +import groovy.util.logging.Slf4j import okhttp3.OkHttpClient +import retrofit2.Call +import retrofit2.Response import retrofit2.Retrofit import retrofit2.converter.jackson.JacksonConverterFactory /** * Parent class for all SCMM Apis that lazily creates the APIs */ +@Slf4j class ScmManagerApiClient { Credentials credentials OkHttpClient okHttpClient @@ -29,6 +33,37 @@ class ScmManagerApiClient { return retrofit().create(RepositoryApi) } + ScmManagerApi generalApi() { + return retrofit().create(ScmManagerApi) + } + + PluginApi pluginApi() { + return retrofit().create(PluginApi) + } + + static handleApiResponse(Call apiCall, String additionalMessage = "") { + try { + Response response = apiCall.execute() + + if (!response.isSuccessful() && + response.code() != 409 && + response.code() != 201) { + def errorMessage = "API call failed!'. HTTP Status: ${response.code()} - ${response.message()}" + if (additionalMessage) { + errorMessage += " Additional Info: ${additionalMessage}" + } + log.error(errorMessage) + throw new RuntimeException(errorMessage) + } else { + log.info("Successfully completed ${apiCall}") + } + } catch (Exception e) { + def errorMessage = "Error executing API: ${e.message}" + log.error(errorMessage, e) + throw new RuntimeException(errorMessage, e) + } + } + protected Retrofit retrofit() { return new Retrofit.Builder() .baseUrl(this.url) diff --git a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/UsersApi.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/UsersApi.groovy index 671996d89..c59a27f44 100644 --- a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/UsersApi.groovy +++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/UsersApi.groovy @@ -6,13 +6,21 @@ import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.Headers import retrofit2.http.POST +import retrofit2.http.PUT import retrofit2.http.Path interface UsersApi { @DELETE("v2/users/{id}") - Call delete(@Path("id") String id) + Call delete(@Path("id") String id) @Headers(["Content-Type: application/vnd.scmm-user+json;v=2"]) - @POST("/api/v2/users") + @POST("v2/users") Call addUser(@Body ScmManagerUser user) + + @Headers(["Content-Type: application/vnd.scmm-permissionCollection+json;v=2"]) + @PUT("v2/users/{username}/permissions") + Call setPermissionForUser( + @Path("username") String username, + @Body Map> permissions + ) } \ No newline at end of file diff --git a/src/main/groovy/com/cloudogu/gitops/utils/TemplatingEngine.groovy b/src/main/groovy/com/cloudogu/gitops/utils/TemplatingEngine.groovy index 7290a170d..381904553 100644 --- a/src/main/groovy/com/cloudogu/gitops/utils/TemplatingEngine.groovy +++ b/src/main/groovy/com/cloudogu/gitops/utils/TemplatingEngine.groovy @@ -3,6 +3,7 @@ package com.cloudogu.gitops.utils import freemarker.template.Configuration import freemarker.template.Template import freemarker.template.Version +import groovy.yaml.YamlSlurper import java.nio.file.Files import java.nio.file.Path @@ -47,6 +48,16 @@ class TemplatingEngine { .each { Path it -> replaceTemplate(it.toFile(), parameters) } } + static Map templateToMap(String filePath, Map parameters) { + def hydratedString = new TemplatingEngine().template(new File(filePath), parameters) + + if (hydratedString.trim().isEmpty()) { + // Otherwise YamlSlurper returns an empty array, whereas we expect a Map + return [:] + } + return new YamlSlurper().parseText(hydratedString) as Map + } + /** * Executes template and writes to targetFile, keeping the template file. */ @@ -87,4 +98,4 @@ class TemplatingEngine { } -} +} \ No newline at end of file diff --git a/src/test/groovy/com/cloudogu/gitops/ApplicationTest.groovy b/src/test/groovy/com/cloudogu/gitops/ApplicationTest.groovy index 14b34afe3..70b7ffd69 100644 --- a/src/test/groovy/com/cloudogu/gitops/ApplicationTest.groovy +++ b/src/test/groovy/com/cloudogu/gitops/ApplicationTest.groovy @@ -17,7 +17,7 @@ class ApplicationTest { .getBean(Application) def features = application.features.collect { it.class.simpleName } - assertThat(features).isEqualTo(["Registry", "ScmManagerSetup", "GitHandler" ,"Jenkins", "ArgoCD", "IngressNginx", "CertManager", "Mailhog", "PrometheusStack", "ExternalSecretsOperator", "Vault", "ContentLoader"]) + assertThat(features).isEqualTo(["Registry", "GitHandler" ,"Jenkins", "ArgoCD", "IngressNginx", "CertManager", "Mailhog", "PrometheusStack", "ExternalSecretsOperator", "Vault", "ContentLoader"]) } @Test @@ -39,7 +39,6 @@ class ApplicationTest { "test1-example-apps-production", "test1-ingress-nginx", "test1-monitoring", - "test1-scm-manager", "test1-registry", "test1-jenkins" )) @@ -70,7 +69,6 @@ class ApplicationTest { "test1-example-apps-production", "test1-ingress-nginx", "test1-monitoring", - "test1-scm-manager", "test1-registry", "test1-jenkins" )) diff --git a/src/test/groovy/com/cloudogu/gitops/features/ScmManagerSetupTest.groovy b/src/test/groovy/com/cloudogu/gitops/features/ScmManagerSetupTest.groovy index 10f6bc5c8..e69de29bb 100644 --- a/src/test/groovy/com/cloudogu/gitops/features/ScmManagerSetupTest.groovy +++ b/src/test/groovy/com/cloudogu/gitops/features/ScmManagerSetupTest.groovy @@ -1,189 +0,0 @@ -package com.cloudogu.gitops.features - -import com.cloudogu.gitops.config.Config -import com.cloudogu.gitops.config.MultiTenantSchema -import com.cloudogu.gitops.features.deployment.HelmStrategy -import com.cloudogu.gitops.features.git.config.ScmCentralSchema -import com.cloudogu.gitops.features.git.config.ScmTenantSchema -import com.cloudogu.gitops.features.git.config.ScmTenantSchema.ScmManagerTenantConfig -import com.cloudogu.gitops.utils.* -import groovy.yaml.YamlSlurper -import org.junit.jupiter.api.Test - -import java.nio.file.Path - -import static org.assertj.core.api.Assertions.assertThat -import static org.mockito.ArgumentMatchers.anyString -import static org.mockito.Mockito.mock -import static org.mockito.Mockito.when - -class ScmManagerSetupTest { - - Config config = new Config( - application: new Config.ApplicationSchema( - username: 'abc', - password: '123', - namePrefix: 'foo-', - trace: true, - insecure: false, - gitName: 'Cloudogu', - gitEmail: 'hello@cloudogu.com', - runningInsideK8s: true - ), - multiTenant: new MultiTenantSchema( - scmManager: new ScmCentralSchema.ScmManagerCentralConfig( - username: 'scmm-usr' - ) - ), - scm: new ScmTenantSchema( - scmManager: new ScmManagerTenantConfig( - url: 'http://scmm', - internal: true, - ingress: 'scmm.localhost', - username: 'scmm-usr', - password: 'scmm-pw', - gitOpsUsername: 'foo-gitops', - urlForJenkins: 'http://scmm4jenkins', - helm: new Config.HelmConfigWithValues( - chart: 'scm-manager-chart', - version: '2.47.0', - repoURL: 'https://packages.scm-manager.org/repository/helm-v2-releases/', - values: [:] - ) - ) - ), - jenkins: new Config.JenkinsSchema( - internal: true, - url: 'http://jenkins', - urlForScm: 'http://jenkins4scm' - ) - ) - - CommandExecutorForTest commandExecutor = new CommandExecutorForTest() - - Path temporaryYamlFile - CommandExecutorForTest helmCommands = new CommandExecutorForTest() - HelmClient helmClient = new HelmClient(helmCommands) - NetworkingUtils networkingUtils = mock(NetworkingUtils.class) - K8sClient k8sClient = mock(K8sClient) - - @Test - void 'Installs SCMM and calls script with proper params'() { - config.multiTenant.scmManager.username = 'scmm-usr' - config.features.ingressNginx.active = true - config.features.argocd.active = true - config.scm.scmManager.skipPlugins = true - config.scm.scmManager.skipRestart = true - createScmManager().install() - - assertThat(parseActualYaml()['extraEnv'] as String).contains('SCM_WEBAPP_INITIALUSER\n value: "scmm-usr"') - assertThat(parseActualYaml()['extraEnv'] as String).contains('SCM_WEBAPP_INITIALPASSWORD\n value: "scmm-pw"') - assertThat(parseActualYaml()['service']).isEqualTo([type: 'NodePort']) - assertThat(parseActualYaml()['ingress']).isEqualTo([enabled: true, path: '/', hosts: ['scmm.localhost']]) - assertThat(helmCommands.actualCommands[0].trim()).isEqualTo( - 'helm repo add scm-manager https://packages.scm-manager.org/repository/helm-v2-releases/') - assertThat(helmCommands.actualCommands[1].trim()).startsWith( - 'helm upgrade -i scmm scm-manager/scm-manager-chart --create-namespace') - assertThat(helmCommands.actualCommands[1].trim()).contains('--version 2.47.0') - assertThat(helmCommands.actualCommands[1].trim()).contains(" --values ${temporaryYamlFile}") - assertThat(helmCommands.actualCommands[1].trim()).contains('--namespace foo-scm-manager') - - def env = getEnvAsMap() - assertThat(commandExecutor.actualCommands[0] as String).isEqualTo( - "${System.getProperty('user.dir')}/scripts/scm-manager/init-scmm.sh" as String) - - assertThat(env['GIT_COMMITTER_NAME']).isEqualTo('Cloudogu') - assertThat(env['GIT_COMMITTER_EMAIL']).isEqualTo('hello@cloudogu.com') - assertThat(env['GIT_AUTHOR_NAME']).isEqualTo('Cloudogu') - assertThat(env['GIT_AUTHOR_EMAIL']).isEqualTo('hello@cloudogu.com') - assertThat(env['GITOPS_USERNAME']).isEqualTo('foo-gitops') - assertThat(env['TRACE']).isEqualTo('true') - assertThat(env['SCMM_URL']).isEqualTo('http://scmm.foo-scm-manager.svc.cluster.local:80/scm') - assertThat(env['SCMM_USERNAME']).isEqualTo('scmm-usr') - assertThat(env['SCMM_PASSWORD']).isEqualTo('scmm-pw') - assertThat(env['JENKINS_URL']).isEqualTo('http://jenkins') - assertThat(env['JENKINS_URL_FOR_SCMM']).isEqualTo('http://jenkins4scm') - assertThat(env['SCMM_URL_FOR_JENKINS']).isEqualTo('http://scmm4jenkins') - assertThat(env['REMOTE_CLUSTER']).isEqualTo('false') - assertThat(env['INSTALL_ARGOCD']).isEqualTo('true') - assertThat(env['NAME_PREFIX']).isEqualTo('foo-') - assertThat(env['INSECURE']).isEqualTo('false') - assertThat(env['SKIP_PLUGINS']).isEqualTo('true') - assertThat(env['SKIP_RESTART']).isEqualTo('true') - } - - @Test - void 'Sets service and host only if enabled'() { - config.application.remote = true - config.scm.scmManager.ingress = '' - createScmManager().install() - - Map actualYaml = parseActualYaml() as Map - - assertThat(actualYaml).doesNotContainKey('service') - assertThat(actualYaml).doesNotContainKey('ingress') - } - - @Test - void 'Installs only if internal'() { - config.scm.scmManager.internal = false - createScmManager().install() - - assertThat(temporaryYamlFile).isNull() - } - - @Test - void 'initialDelaySeconds is set properly'() { - config.scm.scmManager.helm.values = [ - livenessProbe: [ - initialDelaySeconds: 140 - ] - ] - - createScmManager().install() - assertThat(parseActualYaml()['livenessProbe'] as String).contains('initialDelaySeconds:140') - } - - @Test - void "URL: Use k8s service name if running as k8s pod"() { - config.scm.scmManager.internal = true - config.application.runningInsideK8s = true - - createScmManager().install() - assertThat(config.scm.scmManager.url).isEqualTo("http://scmm.foo-scm-manager.svc.cluster.local:80/scm") - } - - @Test - void "URL: Use local ip and nodePort when outside of k8s"() { - config.scm.scmManager.internal = true - config.application.runningInsideK8s = false - - when(networkingUtils.findClusterBindAddress()).thenReturn('192.168.16.2') - when(k8sClient.waitForNodePort(anyString(), anyString())).thenReturn('42') - - createScmManager().install() - assertThat(config.scm.scmManager.url).endsWith('192.168.16.2:42/scm') - } - - protected Map getEnvAsMap() { - commandExecutor.environment.collectEntries { it.split('=') } - } - - private Map parseActualYaml() { - def ys = new YamlSlurper() - return ys.parse(temporaryYamlFile) as Map - } - - private ScmManagerSetup createScmManager() { - when(networkingUtils.createUrl(anyString(), anyString(), anyString())).thenCallRealMethod() - when(networkingUtils.createUrl(anyString(), anyString())).thenCallRealMethod() - new ScmManagerSetup(config, commandExecutor, new FileSystemUtils() { - @Override - Path writeTempFile(Map mapValues) { - def ret = super.writeTempFile(mapValues) - temporaryYamlFile = Path.of(ret.toString().replace(".ftl", "")) // Path after template invocation - return ret - } - }, new HelmStrategy(config, helmClient), k8sClient, networkingUtils) - } -} \ No newline at end of file diff --git a/src/test/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerSetupTest.groovy b/src/test/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerSetupTest.groovy new file mode 100644 index 000000000..a7ac05127 --- /dev/null +++ b/src/test/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerSetupTest.groovy @@ -0,0 +1,90 @@ +package com.cloudogu.gitops.git.providers.scmmanager + +import com.cloudogu.gitops.config.Config +import com.cloudogu.gitops.features.deployment.HelmStrategy +import com.cloudogu.gitops.git.providers.scmmanager.api.PluginApi +import com.cloudogu.gitops.git.providers.scmmanager.api.ScmManagerApi +import com.cloudogu.gitops.git.providers.scmmanager.api.ScmManagerApiClient +import org.junit.jupiter.api.Test +import retrofit2.Call +import retrofit2.Response + +import static org.mockito.ArgumentMatchers.any +import static org.mockito.ArgumentMatchers.eq +import static org.mockito.Mockito.* + +class ScmManagerSetupTest { + + ScmManager scmManager = mock(ScmManager.class) + + HelmStrategy helmStrategy = mock(HelmStrategy.class) + ScmManagerApiClient apiClient = mock(ScmManagerApiClient.class) + + PluginApi pluginApi = mock(PluginApi.class) + ScmManagerApi generalApi = mock(ScmManagerApi.class) + + Config config = Config.fromMap([ + application: [ + namePrefix: 'test', + ], + scm : [ + scmManager: [ + internal : true, + url : "", + namespace : "scm-manager", + username : "admin", + password : "admin", + helm : [ + chart : "scm-manager", + repoURL: "https://packages.scm-manager.org/repository/helm-v2-releases/", + version: "3.11.0", + values : [:] + ], + rootPath : "repo", + urlForJenkins : "http://scmm.scm-manager.svc.cluster.local/scm", + ingress : "scmm.master.localhost", + skipRestart : false, + skipPlugins : false, + gitOpsUsername: "" + ] + ] + ]) + + @Test + void 'Helm chart is installed correctly'() { + when(scmManager.getConfig()).thenReturn(config) + when(scmManager.getHelmStrategy()).thenReturn(helmStrategy) + when(scmManager.getScmmConfig()).thenReturn(config.scm.scmManager) + ScmManagerSetup scmManagerSetup = new ScmManagerSetup(scmManager) + scmManagerSetup.setupHelm() + verify(helmStrategy).deployFeature( + eq( "https://packages.scm-manager.org/repository/helm-v2-releases/"), + eq("scm-manager"), + any(), + eq("3.11.0"), + eq("scm-manager"), + eq("scmm"), + any() + ) + } + + @Test + void 'ScmManager Plugins are installed correctly'() { + when(scmManager.getConfig()).thenReturn(config) + when(scmManager.getHelmStrategy()).thenReturn(helmStrategy) + when(scmManager.getScmmConfig()).thenReturn(config.scm.scmManager) + when(scmManager.getApiClient()).thenReturn(apiClient) + + Call apiCall = mock(Call.class) + + when(pluginApi.install(any(),any())).thenReturn(apiCall) + when(generalApi.checkScmmAvailable()).thenReturn(apiCall) + when(apiClient.pluginApi()).thenReturn(pluginApi) + when(apiClient.generalApi()).thenReturn(generalApi) + when(apiCall.execute()).thenReturn(Response.success(null)) + ScmManagerSetup scmManagerSetup = new ScmManagerSetup(scmManager) + scmManagerSetup.installScmmPlugins() + verify(pluginApi,atLeast(10)).install(any(),any()) + } + +} \ No newline at end of file diff --git a/src/test/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerTest.groovy b/src/test/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerTest.groovy index c501f5a42..09ed83778 100644 --- a/src/test/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerTest.groovy +++ b/src/test/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerTest.groovy @@ -49,7 +49,7 @@ class ScmManagerTest { ) lenient().when(scmmCfg.getCredentials()).thenReturn(new Credentials("user","password")) - lenient(). when(scmmCfg.getGitOpsUsername()).thenReturn("gitops-bot") + lenient().when(scmmCfg.getGitOpsUsername()).thenReturn("gitops-bot") lenient().when(urls.inClusterBase()).thenReturn(new URI("http://scmm.ns.svc.cluster.local/scm")) lenient().when(urls.inClusterRepoPrefix()).thenReturn("http://scmm.ns.svc.cluster.local/scm/repo/fv40-") @@ -163,4 +163,4 @@ class ScmManagerTest { assertEquals("gitops-bot", scmManager.gitOpsUsername) } -} +} \ No newline at end of file