diff --git a/README.md b/README.md
index 3e1f585d0..16bc99eaa 100644
--- a/README.md
+++ b/README.md
@@ -84,7 +84,9 @@ We recommend running this command as an unprivileged user, that is inside the [d
- [Why not use argocd-autopilot?](#why-not-use-argocd-autopilot)
- [cluster-resources](#cluster-resources)
- [Jenkins](#jenkins)
- - [SCM-Manager](#scm-manager)
+ - [SCMs](#scms)
+ - [SCM-Manager](#scm-manager)
+ - [Gitlab](#gitlab)
- [Monitoring tools](#monitoring-tools)
- [Secrets Management Tools](#secrets-management-tools)
- [dev mode](#dev-mode)
@@ -266,21 +268,54 @@ You can also find a list of all CLI/config options [here](#overview-of-all-cli-a
That is, if you pass a param via CLI, for example, it will overwrite the corresponding value in the configuration.
##### Overview of all CLI and config options
-
+- [Application](#application)
- [Registry](#registry)
- [Jenkins](#jenkins)
-- [Multitenant](#multitenant)
-- [SCMM](#scmm)
-- [Application](#application)
+- [SCM](#scmtenant)
+ - [SCMM](#scmmtenant)
+ - [GITLAB](#gitlabtenant)
- [Images](#images)
-- [Features](#features)
- - [ArgoCD](#argocd)
- - [Mail](#mail)
- - [Monitoring](#monitoring)
- - [Secrets](#secrets)
- - [Ingress Nginx](#ingress-nginx)
- - [Cert Manager](#cert-manager)
+- [Features](#argocd)
+ - [ArgoCD](#argocd)
+ - [Mail](#mail)
+ - [Monitoring](#monitoring)
+ - [Secrets](#secrets)
+ - [Ingress Nginx](#ingress-nginx)
+ - [Cert Manager](#cert-manager)
- [Content](#content)
+- [Multitenant](#multitenant)
+ - [SCMM](#scm-managercentral)
+ - [GITLAB](#gitlabcentral)
+
+###### Application
+
+| CLI | Config | Default | Type | Description |
+|-----|--------|---------|------|-------------|
+| `--config-file` | - | `''` | String | Config file path |
+| `--config-map` | - | `''` | String | Config map name |
+| `-d, --debug` | `application.debug` | - | Boolean | Enable debug mode |
+| `-x, --trace` | `application.trace` | - | Boolean | Enable trace mode |
+| `--output-config-file` | `application.outputConfigFile` | `false` | Boolean | Output configuration file |
+| `-v, --version` | `application.versionInfoRequested` | `false` | Boolean | Display version and license info |
+| `-h, --help` | `application.usageHelpRequested` | `false` | Boolean | Display help message |
+| `--remote` | `application.remote` | `false` | Boolean | Expose services as LoadBalancers |
+| `--insecure` | `application.insecure` | `false` | Boolean | Sets insecure-mode in cURL which skips cert validation |
+| `--openshift` | `application.openshift` | `false` | Boolean | When set, openshift specific resources and configurations are applied |
+| `--username` | `application.username` | `'admin'` | String | Set initial admin username |
+| `--password` | `application.password` | `'admin'` | String | Set initial admin passwords |
+| `-y, --yes` | `application.yes` | `false` | Boolean | Skip confirmation |
+| `--name-prefix` | `application.namePrefix` | `''` | String | Set name-prefix for repos, jobs, namespaces |
+| `--destroy` | `application.destroy` | `false` | Boolean | Unroll playground |
+| `--pod-resources` | `application.podResources` | `false` | Boolean | Write kubernetes resource requests and limits on each pod |
+| `--git-name` | `application.gitName` | `'Cloudogu'` | String | Sets git author and committer name used for initial commits |
+| `--git-email` | `application.gitEmail` | `'hello@cloudogu.com'` | String | Sets git author and committer email used for initial commits |
+| `--base-url` | `application.baseUrl` | `''` | String | The external base url (TLD) for all tools |
+| `--url-separator-hyphen` | `application.urlSeparatorHyphen` | `false` | Boolean | Use hyphens instead of dots to separate application name from base-url |
+| `--mirror-repos` | `application.mirrorRepos` | `false` | Boolean | Changes the sources of deployed tools so they work in air-gapped environments |
+| `--skip-crds` | `application.skipCrds` | `false` | Boolean | Skip installation of CRDs |
+| `--namespace-isolation` | `application.namespaceIsolation` | `false` | Boolean | Configure tools to work with given namespaces only |
+| `--netpols` | `application.netpols` | `false` | Boolean | Sets Network Policies |
+
###### Registry
@@ -322,19 +357,16 @@ That is, if you pass a param via CLI, for example, it will overwrite the corresp
| - | `jenkins.helm.version` | `'5.8.43'` | String | The version of the Helm chart to be installed |
| - | `jenkins.helm.values` | `[:]` | Map | Helm values of the chart |
-###### Multitenant
+###### Scm(Tenant)
-| CLI | Config | Default | Type | Description |
-|-----|--------|---------|------|-------------|
-| `--dedicated-internal` | `multiTenant.internal` | `false` | Boolean | SCM for Central Management is running on the same cluster |
-| `--dedicated-instance` | `multiTenant.useDedicatedInstance` | `false` | Boolean | Toggles the Dedicated Instances Mode |
-| `--central-scm-url` | `multiTenant.centralScmUrl` | `''` | String | URL for the centralized Management Repo |
-| `--central-scm-username` | `multiTenant.username` | `''` | String | CENTRAL SCMM USERNAME |
-| `--central-scm-password` | `multiTenant.password` | `''` | String | CENTRAL SCMM Password |
-| `--central-argocd-namespace` | `multiTenant.centralArgocdNamespace` | `'argocd'` | String | CENTRAL Argocd Repo Namespace |
-| `--central-scm-namespace` | `multiTenant.centralSCMamespace` | `'scm-manager'` | String | Central SCM namespace |
+| CLI | Config | Default | Type | Description |
+|------------------|---------------------------------|--------------|-------------------------|-----------------------------------------------------------------------|
+| `--scm-provider` | `scmTenant.scmProviderType` | `SCM_MANAGER` | ScmProviderType | Specifies the SCM provider type. Possible values: `SCM_MANAGER`, `GITLAB`. |
+| | `scmTenant.gitOpsUsername` | `''` | String | The username for the GitOps user. |
+| | `scmTenant.gitlab` | `''` | GitlabTenantConfig | Configuration for GitLab, including URL, username, token, and parent group ID. |
+| | `scmTenant.scmManager` | `''` | ScmManagerTenantConfig | Configuration for SCM Manager, such as internal setup or plugin handling. |
-###### SCMM
+###### SCMM(Tenant)
| CLI | Config | Default | Type | Description |
|-----|--------|---------|------|-------------|
@@ -344,40 +376,22 @@ That is, if you pass a param via CLI, for example, it will overwrite the corresp
| `--scmm-username` | `scmm.username` | `'admin'` | String | Mandatory when scmm-url is set |
| `--scmm-password` | `scmm.password` | `'admin'` | String | Mandatory when scmm-url is set |
| `--scm-root-path` | `scmm.rootPath` | `'repo'` | String | Sets the root path for the Git Repositories |
-| `--scm-provider` | `scmm.provider` | `'scm-manager'` | String | Sets the scm Provider. Possible Options are "scm-manager" and "gitlab" |
| - | `scmm.helm.chart` | `'scm-manager'` | String | Name of the Helm chart |
| - | `scmm.helm.repoURL` | `'https://packages.scm-manager.org/repository/helm-v2-releases/'` | String | Repository url from which the Helm chart should be obtained |
| - | `scmm.helm.version` | `'3.10.2'` | String | The version of the Helm chart to be installed |
| - | `scmm.helm.values` | `[:]` | Map | Helm values of the chart |
-###### Application
-| CLI | Config | Default | Type | Description |
-|-----|--------|---------|------|-------------|
-| `--config-file` | - | `''` | String | Config file path |
-| `--config-map` | - | `''` | String | Config map name |
-| `-d, --debug` | `application.debug` | - | Boolean | Enable debug mode |
-| `-x, --trace` | `application.trace` | - | Boolean | Enable trace mode |
-| `--output-config-file` | `application.outputConfigFile` | `false` | Boolean | Output configuration file |
-| `-v, --version` | `application.versionInfoRequested` | `false` | Boolean | Display version and license info |
-| `-h, --help` | `application.usageHelpRequested` | `false` | Boolean | Display help message |
-| `--remote` | `application.remote` | `false` | Boolean | Expose services as LoadBalancers |
-| `--insecure` | `application.insecure` | `false` | Boolean | Sets insecure-mode in cURL which skips cert validation |
-| `--openshift` | `application.openshift` | `false` | Boolean | When set, openshift specific resources and configurations are applied |
-| `--username` | `application.username` | `'admin'` | String | Set initial admin username |
-| `--password` | `application.password` | `'admin'` | String | Set initial admin passwords |
-| `-y, --yes` | `application.yes` | `false` | Boolean | Skip confirmation |
-| `--name-prefix` | `application.namePrefix` | `''` | String | Set name-prefix for repos, jobs, namespaces |
-| `--destroy` | `application.destroy` | `false` | Boolean | Unroll playground |
-| `--pod-resources` | `application.podResources` | `false` | Boolean | Write kubernetes resource requests and limits on each pod |
-| `--git-name` | `application.gitName` | `'Cloudogu'` | String | Sets git author and committer name used for initial commits |
-| `--git-email` | `application.gitEmail` | `'hello@cloudogu.com'` | String | Sets git author and committer email used for initial commits |
-| `--base-url` | `application.baseUrl` | `''` | String | The external base url (TLD) for all tools |
-| `--url-separator-hyphen` | `application.urlSeparatorHyphen` | `false` | Boolean | Use hyphens instead of dots to separate application name from base-url |
-| `--mirror-repos` | `application.mirrorRepos` | `false` | Boolean | Changes the sources of deployed tools so they work in air-gapped environments |
-| `--skip-crds` | `application.skipCrds` | `false` | Boolean | Skip installation of CRDs |
-| `--namespace-isolation` | `application.namespaceIsolation` | `false` | Boolean | Configure tools to work with given namespaces only |
-| `--netpols` | `application.netpols` | `false` | Boolean | Sets Network Policies |
+###### Gitlab(Tenant)
+
+| CLI | Config | Default | Type | Description |
+|-------------------|--------------------|-----------|--------|------------------------------------------------------------------------------------------------------------|
+| `--gitlab-url` | `gitlabTenant.url` | `''` | String | Base URL for the GitLab instance. |
+| `--gitlab-username` | `gitlabTenant.username` | `'oauth2.0'` | String | Defaults to: `oauth2.0` when a PAT token is provided. |
+| `--gitlab-token` | `gitlabTenant.password` | `''` | String | PAT token for the account. |
+| `--gitlab-parent-id` | `gitlabTenant.parentGroupId` | `''` | String | The numeric ID for the GitLab Group where repositories and subgroups should be created. |
+| | `gitlabTenant.internal` | `false` | Boolean | Indicates if GitLab is running in the same Kubernetes cluster. Currently only external URLs are supported. |
+
###### Images
@@ -503,6 +517,37 @@ That is, if you pass a param via CLI, for example, it will overwrite the corresp
| - | `content.repos[].overwriteMode` | `INIT` | OverwriteMode | How customer repos will be updated (INIT, RESET, UPGRADE) |
| - | `content.repos[].createJenkinsJob` | `false` | Boolean | If true, creates a Jenkins job |
+###### MultiTenant
+
+| CLI | Config | Default | Type | Description |
+|------------------------------|-------------------------------------|---------------|--------------------------|----------------------------------------------------------------|
+| `--dedicated-instance` | `multiTenant.useDedicatedInstance` | `false` | Boolean | Toggles the Dedicated Instances Mode. See docs for more info |
+| `--central-argocd-namespace` | `multiTenant.centralArgocdNamespace`| `'argocd'` | String | Namespace for the centralized Argocd |
+| `--central-scm-provider` | `multiTenant.scmProviderType` | `SCM_MANAGER` | ScmProviderType | The SCM provider type. Possible values: `SCM_MANAGER`, `GITLAB`|
+| | `multiTenant.gitlab` | `` | GitlabCentralConfig | Config for GITLAB |
+| | `multiTenant.scmManager` | `` | ScmManagerCentralConfig | Config for SCM Manager |
+
+###### Gitlab(Central)
+
+| CLI | Config | Default | Type | Description |
+|------------------------------|--------------------------------|-------------|---------|------------------------------------------------------------------|
+| `--central-gitlab-url` | `multiTenant.gitlab.url` | `''` | String | URL for external Gitlab |
+| `--central-gitlab-username` | `multiTenant.gitlab.username` | `'oauth2.0'`| String | Username for GitLab authentication |
+| `--central-gitlab-token` | `multiTenant.gitlab.password` | `''` | String | Password for SCM Manager authentication |
+| `--central-gitlab-group-id` | `multiTenant.gitlab.parentGroupId` | `''` | String | Main Group for Gitlab where the GOP creates it's groups/repos |
+| | `multiTenant.gitlab.internal` | `false` | Boolean | SCM is running on the same cluster (only external supported now) |
+
+###### Scm-Manager(Central)
+
+| CLI | Config | Default | Type | Description |
+|------------------------------|-------------------------------------|-----------------|---------|--------------------------------------------------------------------------------------|
+| `--central-scmm-internal` | `multiTenant.scmManager.internal` | `false` | Boolean | SCM for Central Management is running on the same cluster, so k8s internal URLs can be used for access |
+| `--central-scmm-url` | `multiTenant.scmManager.url` | `''` | String | URL for the centralized Management Repo |
+| `--central-scmm-username` | `multiTenant.scmManager.username` | `''` | String | CENTRAL SCMM USERNAME |
+| `--central-scmm-password` | `multiTenant.scmManager.password` | `''` | String | CENTRAL SCMM Password |
+| `--central-scmm-root-path` | `multiTenant.scmManager.rootPath` | `'repo'` | String | Root path for SCM Manager |
+| `--central-scmm-namespace` | `multiTenant.scmManager.namespace` | `'scm-manager'` | String | Namespace where to find the Central SCMM |
+
##### Configuration file
You can also use a configuration file to specify the parameters (`--config-file` or `--config-map`).
@@ -669,8 +714,8 @@ To use them locally,
* `--argocd` - deploy Argo CD GitOps operator
> ⚠️ **Note** that switching between operators is not supported.
-That is, expect errors (for example with cluster-resources) if you apply the playground once with Argo CD and the next
-time without it. We recommend resetting the cluster with `init-cluster.sh` beforehand.
+> That is, expect errors (for example with cluster-resources) if you apply the playground once with Argo CD and the next
+> time without it. We recommend resetting the cluster with `init-cluster.sh` beforehand.
##### Deploy with local Cloudogu Ecosystem
@@ -1104,6 +1149,24 @@ To apply additional global environments for jenkins you can use `--jenkins-addit
Note that the [example applications](#example-applications) pipelines will only run on a Jenkins that uses agents that provide
a docker host. That is, Jenkins must be able to run e.g. `docker ps` successfully on the agent.
+## SCMs
+
+You can choose between the following Git providers:
+
+- SCM-Manager
+- GitLab
+
+For configuration details, see the CLI or configuration parameters above ([SCM](#scmtenant)).
+
+### GitLab
+
+When using GitLab, you must provide a valid **parent group ID**.
+This group will serve as the main group for the GOP to create and manage all required repositories.
+
+[](https://docs.gitlab.com/user/group/#find-the-group-id)
+
+To authenticate with Gitlab provide a token token as password. More information can be found [here](https://docs.gitlab.com/api/rest/authentication/) or [here](https://docs.gitlab.com/user/profile/personal_access_tokens/)
+The username should remain 'oauth2.0' to access the API, unless stated otherwise by GitLab documentation.
### SCM-Manager
You can set an external SCM-Manager via the following parameters when applying the playground.
@@ -1333,4 +1396,4 @@ Garküche 1
or you may email hello@cloudogu.com.
Your request must be sent within three years from the date you received the software from Cloudogu that is the subject of your request or, in the case of source code licensed under the AGPL/GPL/LGPL v3, for as long as Cloudogu offers spare parts or customer support
-for the product, including the components or binaries that are the subject of your request.
+for the product, including the components or binaries that are the subject of your request.
\ No newline at end of file
diff --git a/applications/cluster-resources/monitoring/prometheus-stack-helm-values.ftl.yaml b/applications/cluster-resources/monitoring/prometheus-stack-helm-values.ftl.yaml
index 5181c59f3..6dc2fdb67 100644
--- a/applications/cluster-resources/monitoring/prometheus-stack-helm-values.ftl.yaml
+++ b/applications/cluster-resources/monitoring/prometheus-stack-helm-values.ftl.yaml
@@ -341,14 +341,16 @@ prometheus:
- prometheus-metrics-creds-scmm
- prometheus-metrics-creds-jenkins
additionalScrapeConfigs:
+<#if config.scm.scmProviderType?lower_case == "scm_manager">
- job_name: 'scm-manager'
static_configs:
- - targets: [ '${scmm.host}' ]
- scheme: ${scmm.protocol}
- metrics_path: '${scmm.path}'
+ - targets: [ '${scm.host}' ]
+ scheme: ${scm.protocol}
+ metrics_path: '${scm.path}'
basic_auth:
username: '${config.application.namePrefix}metrics'
password_file: '/etc/prometheus/secrets/prometheus-metrics-creds-scmm/password'
+#if>
- job_name: 'jenkins'
static_configs:
- targets: [ '${jenkins.host}' ]
diff --git a/argocd/argocd/applications/argocd.ftl.yaml b/argocd/argocd/applications/argocd.ftl.yaml
index 80e85e1e1..99a0f6a4b 100644
--- a/argocd/argocd/applications/argocd.ftl.yaml
+++ b/argocd/argocd/applications/argocd.ftl.yaml
@@ -19,7 +19,7 @@ spec:
project: argocd
source:
path: ${config.features.argocd.operator?string("operator/", "argocd/")}
- repoURL: ${scmm.repoUrl}argocd/argocd<#if config.scmm.provider == "gitlab">.git#if>
+ repoURL: ${scm.repoUrl}argocd/argocd.git
targetRevision: main
# needed to sync the operator/rbac folder
<#if config.features.argocd.operator>
diff --git a/argocd/argocd/applications/bootstrap.ftl.yaml b/argocd/argocd/applications/bootstrap.ftl.yaml
index 0785dddb6..6454ab3ad 100644
--- a/argocd/argocd/applications/bootstrap.ftl.yaml
+++ b/argocd/argocd/applications/bootstrap.ftl.yaml
@@ -14,7 +14,7 @@ spec:
project: argocd
source:
path: applications/
- repoURL: ${scmm.repoUrl}argocd/argocd<#if config.scmm.provider == "gitlab">.git#if>
+ repoURL: ${scm.repoUrl}argocd/argocd.git
targetRevision: main
directory:
recurse: true
diff --git a/argocd/argocd/applications/cluster-resources.ftl.yaml b/argocd/argocd/applications/cluster-resources.ftl.yaml
index 200edbc37..9b4b11302 100644
--- a/argocd/argocd/applications/cluster-resources.ftl.yaml
+++ b/argocd/argocd/applications/cluster-resources.ftl.yaml
@@ -13,7 +13,7 @@ spec:
project: argocd
source:
path: argocd/
- repoURL: ${scmm.repoUrl}argocd/cluster-resources<#if config.scmm.provider == "gitlab">.git#if>
+ repoURL: ${scm.repoUrl}argocd/cluster-resources.git
targetRevision: main
directory:
recurse: true
diff --git a/argocd/argocd/applications/example-apps.ftl.yaml b/argocd/argocd/applications/example-apps.ftl.yaml
deleted file mode 100644
index 02f9402d3..000000000
--- a/argocd/argocd/applications/example-apps.ftl.yaml
+++ /dev/null
@@ -1,23 +0,0 @@
-apiVersion: argoproj.io/v1alpha1
-kind: Application
-metadata:
- name: example-apps
- namespace: ${config.application.namePrefix}argocd
-# finalizer disabled, because otherwise everything under this Application would be deleted as well, if this Application is deleted by accident
-# finalizers:
-# - resources-finalizer.argocd.argoproj.io
-spec:
- destination:
- namespace: ${config.application.namePrefix}argocd
- server: https://kubernetes.default.svc
- project: argocd
- source:
- path: argocd/
- repoURL: ${scmm.repoUrl}argocd/example-apps<#if config.scmm.provider == "gitlab">.git#if>
- targetRevision: main
- directory:
- recurse: true
- syncPolicy:
- automated:
- prune: false # is set to false to prevent projects to be deleted by accident
- selfHeal: true
diff --git a/argocd/argocd/applications/projects.ftl.yaml b/argocd/argocd/applications/projects.ftl.yaml
index acdfa3b14..9c66cd88e 100644
--- a/argocd/argocd/applications/projects.ftl.yaml
+++ b/argocd/argocd/applications/projects.ftl.yaml
@@ -13,7 +13,7 @@ spec:
project: argocd
source:
path: projects/
- repoURL: ${scmm.repoUrl}argocd/argocd<#if config.scmm.provider == "gitlab">.git#if>
+ repoURL: ${scm.repoUrl}argocd/argocd.git
targetRevision: main
directory:
recurse: true
diff --git a/argocd/argocd/multiTenant/central/applications/argocd.ftl.yaml b/argocd/argocd/multiTenant/central/applications/argocd.ftl.yaml
index 09ffecae6..994685220 100644
--- a/argocd/argocd/multiTenant/central/applications/argocd.ftl.yaml
+++ b/argocd/argocd/multiTenant/central/applications/argocd.ftl.yaml
@@ -19,7 +19,7 @@ spec:
project: ${tenantName}
source:
path: ${config.features.argocd.operator?string("operator/", "argocd/")}
- repoURL: ${scmm.centralScmmUrl}/repo/${config.application.namePrefix}argocd/argocd
+ repoURL: ${scm.centralScmUrl}argocd/argocd.git
targetRevision: main
# needed to sync the operator/rbac folder
<#if config.features.argocd.operator??>
diff --git a/argocd/argocd/multiTenant/central/applications/bootstrap.ftl.yaml b/argocd/argocd/multiTenant/central/applications/bootstrap.ftl.yaml
index ad529c726..71a56c1af 100644
--- a/argocd/argocd/multiTenant/central/applications/bootstrap.ftl.yaml
+++ b/argocd/argocd/multiTenant/central/applications/bootstrap.ftl.yaml
@@ -14,7 +14,7 @@ spec:
project: ${tenantName}
source:
path: multiTenant/central/applications/
- repoURL: ${scmm.centralScmmUrl}/repo/${config.application.namePrefix}argocd/argocd
+ repoURL: ${scm.centralScmUrl}argocd/argocd.git
targetRevision: main
directory:
recurse: true
diff --git a/argocd/argocd/multiTenant/central/applications/cluster-resources.ftl.yaml b/argocd/argocd/multiTenant/central/applications/cluster-resources.ftl.yaml
index 57a671035..b27f1a8c0 100644
--- a/argocd/argocd/multiTenant/central/applications/cluster-resources.ftl.yaml
+++ b/argocd/argocd/multiTenant/central/applications/cluster-resources.ftl.yaml
@@ -13,7 +13,7 @@ spec:
project: ${tenantName}
source:
path: argocd/
- repoURL: ${scmm.centralScmmUrl}/repo/${config.application.namePrefix}argocd/cluster-resources
+ repoURL: ${scm.centralScmUrl}argocd/cluster-resources.git
targetRevision: main
directory:
recurse: true
diff --git a/argocd/argocd/multiTenant/central/applications/projects.ftl.yaml b/argocd/argocd/multiTenant/central/applications/projects.ftl.yaml
index b9eb2259e..22daaf97b 100644
--- a/argocd/argocd/multiTenant/central/applications/projects.ftl.yaml
+++ b/argocd/argocd/multiTenant/central/applications/projects.ftl.yaml
@@ -13,7 +13,7 @@ spec:
project: ${tenantName}
source:
path: multiTenant/central/projects/
- repoURL: ${scmm.centralScmmUrl}/repo/${config.application.namePrefix}argocd/argocd
+ repoURL: ${scm.centralScmUrl}argocd/argocd.git
targetRevision: main
directory:
recurse: true
diff --git a/argocd/argocd/multiTenant/central/projects/tenant.ftl.yaml b/argocd/argocd/multiTenant/central/projects/tenant.ftl.yaml
index 74d479293..12f54540f 100644
--- a/argocd/argocd/multiTenant/central/projects/tenant.ftl.yaml
+++ b/argocd/argocd/multiTenant/central/projects/tenant.ftl.yaml
@@ -9,15 +9,15 @@ spec:
- namespace: '*'
server: https://kubernetes.default.svc
sourceRepos:
- - ${scmm.centralScmmUrl}/repo/${config.application.namePrefix}argocd/argocd
- - ${scmm.centralScmmUrl}/repo/${config.application.namePrefix}argocd/cluster-resources
+ - ${scm.centralScmUrl}argocd/argocd.git
+ - ${scm.centralScmUrl}argocd/cluster-resources.git
<#if config.application.mirrorRepos>
- - ${scmm.repoUrl}3rd-party-dependencies/kube-prometheus-stack<#if config.scmm.provider == "gitlab">.git#if>
- - ${scmm.repoUrl}3rd-party-dependencies/mailhog<#if config.scmm.provider == "gitlab">.git#if>
- - ${scmm.repoUrl}3rd-party-dependencies/ingress-nginx<#if config.scmm.provider == "gitlab">.git#if>
- - ${scmm.repoUrl}3rd-party-dependencies/external-secrets<#if config.scmm.provider == "gitlab">.git#if>
- - ${scmm.repoUrl}3rd-party-dependencies/vault<#if config.scmm.provider == "gitlab">.git#if>
- - ${scmm.repoUrl}3rd-party-dependencies/cert-manager<#if config.scmm.provider == "gitlab">.git#if>
+ - ${scm.repoUrl}3rd-party-dependencies/kube-prometheus-stack.git
+ - ${scm.repoUrl}3rd-party-dependencies/mailhog.git
+ - ${scm.repoUrl}3rd-party-dependencies/ingress-nginx.git
+ - ${scm.repoUrl}3rd-party-dependencies/external-secrets.git
+ - ${scm.repoUrl}3rd-party-dependencies/vault.git
+ - ${scm.repoUrl}3rd-party-dependencies/cert-manager.git
<#else>
- https://prometheus-community.github.io/helm-charts
- https://codecentric.github.io/helm-charts
diff --git a/argocd/argocd/multiTenant/tenant/applications/bootstrap.ftl.yaml b/argocd/argocd/multiTenant/tenant/applications/bootstrap.ftl.yaml
index 129b6be32..993671016 100644
--- a/argocd/argocd/multiTenant/tenant/applications/bootstrap.ftl.yaml
+++ b/argocd/argocd/multiTenant/tenant/applications/bootstrap.ftl.yaml
@@ -14,7 +14,7 @@ spec:
project: argocd
source:
path: applications/
- repoURL: ${scmm.repoUrl}argocd/argocd
+ repoURL: ${scm.repoUrl}argocd/argocd.git
targetRevision: main
directory:
recurse: true
@@ -39,7 +39,7 @@ spec:
project: argocd
source:
path: projects/
- repoURL: ${scmm.repoUrl}argocd/argocd
+ repoURL: ${scm.repoUrl}argocd/argocd.git
targetRevision: main
directory:
recurse: true
@@ -67,8 +67,8 @@ spec:
- namespace: ${config.application.namePrefix}example-apps-staging
server: https://kubernetes.default.svc
sourceRepos:
- - ${scmm.repoUrl}argocd/example-apps<#if config.scmm.provider == "gitlab">.git#if>
- - ${scmm.repoUrl}argocd/nginx-helm-umbrella<#if config.scmm.provider == "gitlab">.git#if>
+ - ${scm.repoUrl}argocd/example-apps.git
+ - ${scm.repoUrl}argocd/nginx-helm-umbrella.git
# allow to only see application resources from the specified namespace
sourceNamespaces:
diff --git a/argocd/argocd/operator/argocd.ftl.yaml b/argocd/argocd/operator/argocd.ftl.yaml
index 5e280eef6..6506db8dd 100644
--- a/argocd/argocd/operator/argocd.ftl.yaml
+++ b/argocd/argocd/operator/argocd.ftl.yaml
@@ -127,11 +127,11 @@ spec:
ingress:
enabled: ${((!config.application.openshift) && (!config.application.insecure))?c}
initialRepositories: |
-<#if !(scmm.centralScmmUrl?has_content)>
+<#if !(scm.centralScmUrl?has_content)>
- name: argocd
- url: ${scmm.repoUrl}argocd/argocd<#if config.scmm.provider == "gitlab">.git#if>
+ url: ${scm.repoUrl}argocd/argocd.git
- name: cluster-resources
- url: ${scmm.repoUrl}argocd/cluster-resources<#if config.scmm.provider == "gitlab">.git#if>
+ url: ${scm.repoUrl}argocd/cluster-resources.git
- name: prometheus-community
type: helm
url: https://prometheus-community.github.io/helm-charts
@@ -143,11 +143,11 @@ spec:
url: https://kubernetes.github.io/ingress-nginx
#if>
- name: example-apps
- url: ${scmm.repoUrl}argocd/example-apps<#if config.scmm.provider == "gitlab">.git#if>
+ url: ${scm.repoUrl}argocd/example-apps.git
- name: nginx-helm-jenkins
- url: ${scmm.repoUrl}argocd/nginx-helm-jenkins<#if config.scmm.provider == "gitlab">.git#if>
+ url: ${scm.repoUrl}argocd/nginx-helm-jenkins.git
- name: nginx-helm-umbrella
- url: ${scmm.repoUrl}argocd/nginx-helm-umbrella<#if config.scmm.provider == "gitlab">.git#if>
+ url: ${scm.repoUrl}argocd/nginx-helm-umbrella.git
- name: bitnami
type: helm
url: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
diff --git a/argocd/argocd/projects/cluster-resources.ftl.yaml b/argocd/argocd/projects/cluster-resources.ftl.yaml
index e237b12ab..de6579fd4 100644
--- a/argocd/argocd/projects/cluster-resources.ftl.yaml
+++ b/argocd/argocd/projects/cluster-resources.ftl.yaml
@@ -14,14 +14,14 @@ spec:
- namespace: '*'
server: https://kubernetes.default.svc
sourceRepos:
- - ${scmm.repoUrl}argocd/cluster-resources<#if config.scmm.provider == "gitlab">.git#if>
+ - ${scm.repoUrl}argocd/cluster-resources.git
<#if config.application.mirrorRepos>
- - ${scmm.baseUrl}<#if config.scmm.provider == "gitlab">/3rd-party-dependencies/kube-prometheus-stack.git<#else>/repo/3rd-party-dependencies/kube-prometheus-stack#if>
- - ${scmm.baseUrl}<#if config.scmm.provider == "gitlab">/3rd-party-dependencies/mailhog.git<#else>/repo/3rd-party-dependencies/mailhog#if>
- - ${scmm.baseUrl}<#if config.scmm.provider == "gitlab">/3rd-party-dependencies/ingress-nginx.git<#else>/repo/3rd-party-dependencies/ingress-nginx#if>
- - ${scmm.baseUrl}<#if config.scmm.provider == "gitlab">/3rd-party-dependencies/external-secrets.git<#else>/repo/3rd-party-dependencies/external-secrets#if>
- - ${scmm.baseUrl}<#if config.scmm.provider == "gitlab">/3rd-party-dependencies/vault.git<#else>/repo/3rd-party-dependencies/vault#if>
- - ${scmm.baseUrl}<#if config.scmm.provider == "gitlab">/3rd-party-dependencies/cert-manager.git<#else>/repo/3rd-party-dependencies/cert-manager#if>
+ - ${scm.baseUrl}<#if config.scm.scmProviderType == "GITLAB">/3rd-party-dependencies/kube-prometheus-stack.git<#else>/repo/3rd-party-dependencies/kube-prometheus-stack#if>
+ - ${scm.baseUrl}<#if config.scm.scmProviderType == "GITLAB">/3rd-party-dependencies/mailhog.git<#else>/repo/3rd-party-dependencies/mailhog#if>
+ - ${scm.baseUrl}<#if config.scm.scmProviderType == "GITLAB">/3rd-party-dependencies/ingress-nginx.git<#else>/repo/3rd-party-dependencies/ingress-nginx#if>
+ - ${scm.baseUrl}<#if config.scm.scmProviderType == "GITLAB">/3rd-party-dependencies/external-secrets.git<#else>/repo/3rd-party-dependencies/external-secrets#if>
+ - ${scm.baseUrl}<#if config.scm.scmProviderType == "GITLAB">/3rd-party-dependencies/vault.git<#else>/repo/3rd-party-dependencies/vault#if>
+ - ${scm.baseUrl}<#if config.scm.scmProviderType == "GITLAB">/3rd-party-dependencies/cert-manager.git<#else>/repo/3rd-party-dependencies/cert-manager#if>
<#else>
- https://prometheus-community.github.io/helm-charts
- https://codecentric.github.io/helm-charts
diff --git a/argocd/argocd/projects/example-apps.ftl.yaml b/argocd/argocd/projects/example-apps.ftl.yaml
index e10024b03..23901dd58 100644
--- a/argocd/argocd/projects/example-apps.ftl.yaml
+++ b/argocd/argocd/projects/example-apps.ftl.yaml
@@ -15,8 +15,8 @@ spec:
- namespace: ${config.application.namePrefix}example-apps-staging
server: https://kubernetes.default.svc
sourceRepos:
- - ${scmm.repoUrl}argocd/example-apps<#if config.scmm.provider == "gitlab">.git#if>
- - ${scmm.repoUrl}argocd/nginx-helm-umbrella<#if config.scmm.provider == "gitlab">.git#if>
+ - ${scm.repoUrl}argocd/example-apps.git
+ - ${scm.repoUrl}argocd/nginx-helm-umbrella.git
# allow to only see application resources from the specified namespace
diff --git a/argocd/cluster-resources/argocd/misc.ftl.yaml b/argocd/cluster-resources/argocd/misc.ftl.yaml
index e46133870..4bba8b06a 100644
--- a/argocd/cluster-resources/argocd/misc.ftl.yaml
+++ b/argocd/cluster-resources/argocd/misc.ftl.yaml
@@ -13,9 +13,9 @@ spec:
source:
path: misc/
<#if config.multiTenant.useDedicatedInstance>
- repoURL: ${scmm.centralScmmUrl}/repo/${config.application.namePrefix}argocd/cluster-resources
+ repoURL: ${scm.centralScmUrl}argocd/cluster-resources.git
<#else>
- repoURL: ${scmm.repoUrl}argocd/cluster-resources<#if config.scmm.provider == "gitlab">.git#if>
+ repoURL: ${scm.repoUrl}argocd/cluster-resources.git
#if>
targetRevision: main
directory:
diff --git a/argocd/example-apps/README.md b/argocd/example-apps/README.md
deleted file mode 100644
index 143c98de3..000000000
--- a/argocd/example-apps/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Contains examples of end-user applications
\ No newline at end of file
diff --git a/argocd/example-apps/apps/.gitkeep b/argocd/example-apps/apps/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/argocd/example-apps/argocd/exercise-nginx-helm.ftl.yaml b/argocd/example-apps/argocd/exercise-nginx-helm.ftl.yaml
deleted file mode 100644
index 0e665aff3..000000000
--- a/argocd/example-apps/argocd/exercise-nginx-helm.ftl.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
-apiVersion: argoproj.io/v1alpha1
-kind: Application
-metadata:
-<#if config.features.argocd.operator>
- name: exercise-nginx-helm-staging
- namespace: ${config.application.namePrefix}argocd
-<#else>
- name: exercise-nginx-helm
- namespace: ${config.application.namePrefix}example-apps-staging
-#if>
-spec:
- destination:
- namespace: ${config.application.namePrefix}example-apps-staging
- server: https://kubernetes.default.svc
- project: example-apps
- source:
- path: apps/exercise-nginx-helm/staging
- repoURL: ${scmm.repoUrl}argocd/example-apps
- targetRevision: main
- directory:
- recurse: true
- syncPolicy:
- automated:
- prune: true
- selfHeal: true
-
----
-
-apiVersion: argoproj.io/v1alpha1
-kind: Application
-metadata:
-<#if config.features.argocd.operator>
- name: exercise-nginx-helm-production
- namespace: ${config.application.namePrefix}argocd
-<#else>
- name: exercise-nginx-helm
- namespace: ${config.application.namePrefix}example-apps-production
-#if>
-spec:
- destination:
- namespace: ${config.application.namePrefix}example-apps-production
- server: https://kubernetes.default.svc
- project: example-apps
- source:
- path: apps/exercise-nginx-helm/production
- repoURL: ${scmm.repoUrl}argocd/example-apps
- targetRevision: main
- directory:
- recurse: true
- syncPolicy:
- automated:
- prune: true
- selfHeal: true
\ No newline at end of file
diff --git a/argocd/example-apps/argocd/exercise-petclinic-helm.ftl.yaml b/argocd/example-apps/argocd/exercise-petclinic-helm.ftl.yaml
deleted file mode 100644
index 12c5985c9..000000000
--- a/argocd/example-apps/argocd/exercise-petclinic-helm.ftl.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
-apiVersion: argoproj.io/v1alpha1
-kind: Application
-metadata:
-<#if config.features.argocd.operator>
- name: exercise-petclinic-helm-staging
- namespace: ${config.application.namePrefix}argocd
-<#else>
- name: exercise-petclinic-helm
- namespace: ${config.application.namePrefix}example-apps-staging
-#if>
-spec:
- destination:
- namespace: ${config.application.namePrefix}example-apps-staging
- server: https://kubernetes.default.svc
- project: example-apps
- source:
- path: apps/exercise-spring-petclinic-helm/staging
- repoURL: ${scmm.repoUrl}argocd/example-apps
- targetRevision: main
- directory:
- recurse: true
- syncPolicy:
- automated:
- prune: true
- selfHeal: true
-
----
-
-apiVersion: argoproj.io/v1alpha1
-kind: Application
-metadata:
-<#if config.features.argocd.operator>
- name: exercise-petclinic-helm-production
- namespace: ${config.application.namePrefix}argocd
-<#else>
- name: exercise-petclinic-helm
- namespace: ${config.application.namePrefix}example-apps-production
-#if>
-spec:
- destination:
- namespace: ${config.application.namePrefix}example-apps-production
- server: https://kubernetes.default.svc
- project: example-apps
- source:
- path: apps/exercise-spring-petclinic-helm/production
- repoURL: ${scmm.repoUrl}argocd/example-apps
- targetRevision: main
- directory:
- recurse: true
- syncPolicy:
- automated:
- prune: true
- selfHeal: true
\ No newline at end of file
diff --git a/argocd/example-apps/argocd/misc.ftl.yaml b/argocd/example-apps/argocd/misc.ftl.yaml
deleted file mode 100644
index e7c99fb77..000000000
--- a/argocd/example-apps/argocd/misc.ftl.yaml
+++ /dev/null
@@ -1,60 +0,0 @@
-# this misc-Application manages all resources in the misc folder in the gitops repository
-# use the misc folder to deploy resources, which are needed for multiple other apps such as ServiceAccounts or shared ConfigMaps
-apiVersion: argoproj.io/v1alpha1
-kind: Application
-metadata:
-<#if config.features.argocd.operator>
- name: misc-example-apps-production
- namespace: ${config.application.namePrefix}argocd
-<#else>
- name: misc
- namespace: ${config.application.namePrefix}example-apps-production
-#if>
-spec:
- project: example-apps
- destination:
- server: https://kubernetes.default.svc
- # a namespace must be specified here, because teams are not allowed to deploy cluster-wide.
- # You can specify a different namespace in the ressource YAMLs, if you are permitted to deploy in them
- namespace: ${config.application.namePrefix}example-apps-production
- source:
- path: misc/
- repoURL: ${scmm.repoUrl}argocd/example-apps
- targetRevision: main
- directory:
- recurse: true
- syncPolicy:
- automated:
- prune: true
- selfHeal: true
-<#if config.multiTenant.centralSCMUrl?has_content>
----
-apiVersion: argoproj.io/v1alpha1
-kind: AppProject
-metadata:
- name: example-apps
- namespace: ${config.application.namePrefix}argocd
-spec:
- description: Contains examples of end-user application
- destinations:
- - namespace: ${config.application.namePrefix}example-apps-production
- server: https://kubernetes.default.svc
- - namespace: ${config.application.namePrefix}example-apps-staging
- server: https://kubernetes.default.svc
- - namespace: ${config.application.namePrefix}argocd
- server: https://kubernetes.default.svc
- sourceRepos:
- - http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local/scm/repo/${config.application.namePrefix}argocd/example-apps
- - http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local/scm/repo/${config.application.namePrefix}argocd/nginx-helm-umbrella
-
- sourceNamespaces:
- - ${config.application.namePrefix}example-apps-staging
- - ${config.application.namePrefix}example-apps-production
- - ${config.application.namePrefix}argocd
-
- namespaceResourceWhitelist:
- - group: '*'
- kind: '*'
-
- clusterResourceWhitelist: []
-#if>
\ No newline at end of file
diff --git a/argocd/example-apps/argocd/nginx-helm-jenkins.ftl.yaml b/argocd/example-apps/argocd/nginx-helm-jenkins.ftl.yaml
deleted file mode 100644
index 782304b9f..000000000
--- a/argocd/example-apps/argocd/nginx-helm-jenkins.ftl.yaml
+++ /dev/null
@@ -1,52 +0,0 @@
-apiVersion: argoproj.io/v1alpha1
-kind: Application
-metadata:
-<#if config.features.argocd.operator>
- name: nginx-helm-jenkins-staging
- namespace: ${config.application.namePrefix}argocd
-<#else>
- name: nginx-helm-jenkins
- namespace: ${config.application.namePrefix}example-apps-staging
-#if>
-spec:
- destination:
- namespace: ${config.application.namePrefix}example-apps-staging
- server: https://kubernetes.default.svc
- project: example-apps
- source:
- path: apps/nginx-helm-jenkins/staging
- repoURL: ${scmm.repoUrl}argocd/example-apps
- targetRevision: main
- directory:
- recurse: true
- syncPolicy:
- automated:
- prune: true
- selfHeal: true
-
----
-apiVersion: argoproj.io/v1alpha1
-kind: Application
-metadata:
-<#if config.features.argocd.operator>
- name: nginx-helm-jenkins-production
- namespace: ${config.application.namePrefix}argocd
-<#else>
- name: nginx-helm-jenkins
- namespace: ${config.application.namePrefix}example-apps-production
-#if>
-spec:
- destination:
- namespace: ${config.application.namePrefix}example-apps-production
- server: https://kubernetes.default.svc
- project: example-apps
- source:
- path: apps/nginx-helm-jenkins/production
- repoURL: ${scmm.repoUrl}argocd/example-apps
- targetRevision: main
- directory:
- recurse: true
- syncPolicy:
- automated:
- prune: true
- selfHeal: true
\ No newline at end of file
diff --git a/argocd/example-apps/argocd/nginx-helm-umbrella.ftl.yaml b/argocd/example-apps/argocd/nginx-helm-umbrella.ftl.yaml
deleted file mode 100644
index 419f8817e..000000000
--- a/argocd/example-apps/argocd/nginx-helm-umbrella.ftl.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-apiVersion: argoproj.io/v1alpha1
-kind: Application
-metadata:
- name: nginx-helm-umbrella
-<#if config.features.argocd.operator>
- namespace: ${config.application.namePrefix}argocd
-<#else>
- namespace: ${config.application.namePrefix}example-apps-production
-#if>
-spec:
- destination:
- namespace: ${config.application.namePrefix}example-apps-production
- server: https://kubernetes.default.svc
- project: example-apps
- source:
- path: apps/nginx-helm-umbrella
- repoURL: ${scmm.repoUrl}argocd/example-apps
- targetRevision: main
- syncPolicy:
- automated:
- prune: true
- selfHeal: true
\ No newline at end of file
diff --git a/argocd/example-apps/argocd/petclinic-helm.ftl.yaml b/argocd/example-apps/argocd/petclinic-helm.ftl.yaml
deleted file mode 100644
index 37178cae0..000000000
--- a/argocd/example-apps/argocd/petclinic-helm.ftl.yaml
+++ /dev/null
@@ -1,52 +0,0 @@
-apiVersion: argoproj.io/v1alpha1
-kind: Application
-metadata:
-<#if config.features.argocd.operator>
- name: petclinic-helm-staging
- namespace: ${config.application.namePrefix}argocd
-<#else>
- name: petclinic-helm
- namespace: ${config.application.namePrefix}example-apps-staging
-#if>
-spec:
- destination:
- namespace: ${config.application.namePrefix}example-apps-staging
- server: https://kubernetes.default.svc
- project: example-apps
- source:
- path: apps/spring-petclinic-helm/staging
- repoURL: ${scmm.repoUrl}argocd/example-apps
- targetRevision: main
- directory:
- recurse: true
- syncPolicy:
- automated:
- prune: true
- selfHeal: true
-
----
-apiVersion: argoproj.io/v1alpha1
-kind: Application
-metadata:
-<#if config.features.argocd.operator>
- name: petclinic-helm-production
- namespace: ${config.application.namePrefix}argocd
-<#else>
- name: petclinic-helm
- namespace: ${config.application.namePrefix}example-apps-production
-#if>
-spec:
- destination:
- namespace: ${config.application.namePrefix}example-apps-production
- server: https://kubernetes.default.svc
- project: example-apps
- source:
- path: apps/spring-petclinic-helm/production
- repoURL: ${scmm.repoUrl}argocd/example-apps
- targetRevision: main
- directory:
- recurse: true
- syncPolicy:
- automated:
- prune: true
- selfHeal: true
\ No newline at end of file
diff --git a/argocd/example-apps/argocd/petclinic-plain.ftl.yaml b/argocd/example-apps/argocd/petclinic-plain.ftl.yaml
deleted file mode 100644
index 89afcf2b8..000000000
--- a/argocd/example-apps/argocd/petclinic-plain.ftl.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
-apiVersion: argoproj.io/v1alpha1
-kind: Application
-metadata:
-<#if config.features.argocd.operator>
- name: petclinic-plain-staging
- namespace: ${config.application.namePrefix}argocd
-<#else>
- name: petclinic-plain
- namespace: ${config.application.namePrefix}example-apps-staging
-#if>
-spec:
- destination:
- namespace: ${config.application.namePrefix}example-apps-staging
- server: https://kubernetes.default.svc
- project: example-apps
- source:
- path: apps/spring-petclinic-plain/staging
- repoURL: ${scmm.repoUrl}argocd/example-apps
- targetRevision: main
- directory:
- recurse: true
- syncPolicy:
- automated:
- prune: true
- selfHeal: true
-
----
-
-apiVersion: argoproj.io/v1alpha1
-kind: Application
-metadata:
-<#if config.features.argocd.operator>
- name: petclinic-plain-production
- namespace: ${config.application.namePrefix}argocd
-<#else>
- name: petclinic-plain
- namespace: ${config.application.namePrefix}example-apps-production
-#if>
-spec:
- destination:
- namespace: ${config.application.namePrefix}example-apps-production
- server: https://kubernetes.default.svc
- project: example-apps
- source:
- path: apps/spring-petclinic-plain/production
- repoURL: ${scmm.repoUrl}argocd/example-apps
- targetRevision: main
- directory:
- recurse: true
- syncPolicy:
- automated:
- prune: true
- selfHeal: true
\ No newline at end of file
diff --git a/argocd/example-apps/misc/.gitkeep b/argocd/example-apps/misc/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/docs/configuration.schema.json b/docs/configuration.schema.json
index 53543e25a..27980ed67 100644
--- a/docs/configuration.schema.json
+++ b/docs/configuration.schema.json
@@ -65,6 +65,14 @@
}
},
"additionalProperties" : false
+ },
+ "ScmProviderType-nullable" : {
+ "anyOf" : [ {
+ "type" : "null"
+ }, {
+ "type" : "string",
+ "enum" : [ "GITLAB", "SCM_MANAGER" ]
+ } ]
}
},
"type" : "object",
@@ -159,7 +167,7 @@
}
},
"repos" : {
- "description" : "Content repos to push into target environment",
+ "description" : "ContentLoader repos to push into target environment",
"type" : [ "array", "null" ],
"items" : {
"type" : "object",
@@ -208,7 +216,7 @@
"type" : "string",
"enum" : [ "FOLDER_BASED", "COPY", "MIRROR" ]
} ],
- "description" : "Content Repos can either be:\ncopied (only the files, starting on ref, starting at path within the repo. Requires target)\n, mirrored (FORCE pushes ref or the whole git repo if no ref set). Requires target, does not allow path and template.)\nfolderBased (folder structure is interpreted as repos. That is, root folder becomes namespace in SCM, sub folders become repository names in SCM, files are copied. Requires target.)"
+ "description" : "ContentLoader Repos can either be:\ncopied (only the files, starting on ref, starting at path within the repo. Requires target)\n, mirrored (FORCE pushes ref or the whole git repo if no ref set). Requires target, does not allow path and template.)\nfolderBased (folder structure is interpreted as repos. That is, root folder becomes namespace in SCM, sub folders become repository names in SCM, files are copied. Requires target.)"
},
"url" : {
"type" : [ "string", "null" ],
@@ -715,31 +723,69 @@
"properties" : {
"centralArgocdNamespace" : {
"type" : [ "string", "null" ],
- "description" : "CENTRAL Argocd Repo Namespace"
- },
- "centralSCMamespace" : {
- "type" : [ "string", "null" ],
- "description" : "CENTRAL Argocd Repo Namespace"
+ "description" : "Namespace for the centralized Argocd"
},
- "centralScmUrl" : {
- "type" : [ "string", "null" ],
- "description" : "URL for the centralized Management Repo"
+ "gitlab" : {
+ "type" : [ "object", "null" ],
+ "properties" : {
+ "parentGroupId" : {
+ "type" : [ "string", "null" ],
+ "description" : "Main Group for Gitlab where the GOP creates it's groups/repos"
+ },
+ "password" : {
+ "type" : [ "string", "null" ],
+ "description" : "Password for SCM Manager authentication"
+ },
+ "url" : {
+ "type" : [ "string", "null" ],
+ "description" : "URL for external Gitlab"
+ },
+ "username" : {
+ "type" : [ "string", "null" ],
+ "description" : "GitLab username for API access. Must be 'oauth2' when using Personal Access Token (PAT) authentication"
+ }
+ },
+ "additionalProperties" : false,
+ "description" : "Config for GITLAB"
},
- "internal" : {
- "type" : [ "boolean", "null" ],
- "description" : "SCM for Central Management is running on the same cluster, so k8s internal URLs can be used for access"
+ "scmManager" : {
+ "type" : [ "object", "null" ],
+ "properties" : {
+ "internal" : {
+ "type" : [ "boolean", "null" ],
+ "description" : "SCM for Central Management is running on the same cluster, so k8s internal URLs can be used for access"
+ },
+ "namespace" : {
+ "type" : [ "string", "null" ],
+ "description" : "Namespace where to find the Central SCMM"
+ },
+ "password" : {
+ "type" : [ "string", "null" ],
+ "description" : "CENTRAL SCMM password"
+ },
+ "rootPath" : {
+ "type" : [ "string", "null" ],
+ "description" : "Root path for SCM Manager"
+ },
+ "url" : {
+ "type" : [ "string", "null" ],
+ "description" : "URL for the centralized Management Repo"
+ },
+ "username" : {
+ "type" : [ "string", "null" ],
+ "description" : "CENTRAL SCMM username"
+ }
+ },
+ "additionalProperties" : false,
+ "description" : "Config for GITLAB"
},
- "password" : {
- "type" : [ "string", "null" ],
- "description" : "CENTRAL SCMM Password"
+ "scmProviderType" : {
+ "$ref" : "#/$defs/ScmProviderType-nullable",
+ "description" : "The SCM provider type. Possible values: SCM_MANAGER, GITLAB"
},
"useDedicatedInstance" : {
"type" : [ "boolean", "null" ],
- "description" : "Toggles the Dedicated Instances Mode"
- },
- "username" : {
- "type" : [ "string", "null" ],
- "description" : "CENTRAL SCMM USERNAME"
+ "description" : "Toggles the Dedicated Instances Mode. See docs for more info"
}
},
"additionalProperties" : false,
@@ -827,44 +873,82 @@
"additionalProperties" : false,
"description" : "Config params for repositories used within GOP"
},
- "scmm" : {
+ "scm" : {
"type" : [ "object", "null" ],
"properties" : {
- "helm" : {
- "$ref" : "#/$defs/HelmConfigWithValues-nullable",
- "description" : "Common Config parameters for the Helm package manager: Name of Chart (chart), URl of Helm-Repository (repoURL) and Chart Version (version). Note: These config is intended to obtain the chart from a different source (e.g. in air-gapped envs), not to use a different version of a helm chart. Using a different helm chart or version to the one used in the GOP version will likely cause errors."
- },
- "password" : {
- "type" : [ "string", "null" ],
- "description" : "Mandatory when scmm-url is set"
- },
- "provider" : {
- "type" : [ "string", "null" ],
- "description" : "Sets the scm Provider. Possible Options are \"scm-manager\" and \"gitlab\""
- },
- "rootPath" : {
- "type" : [ "string", "null" ],
- "description" : "Sets the root path for the Git Repositories. In SCM-Manager it is always \"repo\""
- },
- "skipPlugins" : {
- "type" : [ "boolean", "null" ],
- "description" : "Skips plugin installation. Use with caution! If the plugins are not installed up front, the installation will likely fail. The intended use case for this is after the first installation, for config changes only. Do not use on first installation or upgrades."
- },
- "skipRestart" : {
- "type" : [ "boolean", "null" ],
- "description" : "Skips restarting SCM-Manager after plugin installation. Use with caution! If the plugins are not installed up front, the installation will likely fail. The intended use case for this is after the first installation, for config changes only. Do not use on first installation or upgrades.'"
+ "gitlab" : {
+ "type" : [ "object", "null" ],
+ "properties" : {
+ "internal" : {
+ "type" : [ "boolean", "null" ],
+ "description" : "True if Gitlab is running in the same K8s cluster. For now we only support access by external URL"
+ },
+ "parentGroupId" : {
+ "type" : [ "string", "null" ],
+ "description" : "Number for the Gitlab Group where the repos and subgroups should be created"
+ },
+ "password" : {
+ "type" : [ "string", "null" ],
+ "description" : "PAT Token for the account. Needs read/write repo permissions. See docs for mor information"
+ },
+ "url" : {
+ "type" : [ "string", "null" ],
+ "description" : "Base URL for the Gitlab instance"
+ },
+ "username" : {
+ "type" : [ "string", "null" ],
+ "description" : "Defaults to: oauth2.0 when PAT token is given."
+ }
+ },
+ "additionalProperties" : false,
+ "description" : "Config for GITLAB"
},
- "url" : {
- "type" : [ "string", "null" ],
- "description" : "The host of your external scm-manager"
+ "scmManager" : {
+ "type" : [ "object", "null" ],
+ "properties" : {
+ "helm" : {
+ "$ref" : "#/$defs/HelmConfigWithValues-nullable",
+ "description" : "Common Config parameters for the Helm package manager: Name of Chart (chart), URl of Helm-Repository (repoURL) and Chart Version (version). Note: These config is intended to obtain the chart from a different source (e.g. in air-gapped envs), not to use a different version of a helm chart. Using a different helm chart or version to the one used in the GOP version will likely cause errors."
+ },
+ "namespace" : {
+ "type" : [ "string", "null" ],
+ "description" : "Namespace where SCM-Manager should run"
+ },
+ "password" : {
+ "type" : [ "string", "null" ],
+ "description" : "Mandatory when scmm-url is set"
+ },
+ "rootPath" : {
+ "type" : [ "string", "null" ],
+ "description" : "Sets the root path for the Git Repositories. In SCM-Manager it is always \"repo\""
+ },
+ "skipPlugins" : {
+ "type" : [ "boolean", "null" ],
+ "description" : "Skips plugin installation. Use with caution! If the plugins are not installed up front, the installation will likely fail. The intended use case for this is after the first installation, for config changes only. Do not use on first installation or upgrades."
+ },
+ "skipRestart" : {
+ "type" : [ "boolean", "null" ],
+ "description" : "Skips restarting SCM-Manager after plugin installation. Use with caution! If the plugins are not installed up front, the installation will likely fail. The intended use case for this is after the first installation, for config changes only. Do not use on first installation or upgrades.'"
+ },
+ "url" : {
+ "type" : [ "string", "null" ],
+ "description" : "The host of your external scm-manager"
+ },
+ "username" : {
+ "type" : [ "string", "null" ],
+ "description" : "Mandatory when scmm-url is set"
+ }
+ },
+ "additionalProperties" : false,
+ "description" : "Config for GITLAB"
},
- "username" : {
- "type" : [ "string", "null" ],
- "description" : "Mandatory when scmm-url is set"
+ "scmProviderType" : {
+ "$ref" : "#/$defs/ScmProviderType-nullable",
+ "description" : "The SCM provider type. Possible values: SCM_MANAGER, GITLAB"
}
},
"additionalProperties" : false,
- "description" : "Config parameters for SCMManager (Git repository Server, https://scm-manager.org/)"
+ "description" : "Config parameters for Scm"
}
},
"additionalProperties" : false
diff --git a/docs/developers.md b/docs/developers.md
index 7264f80c7..29cda7fa3 100644
--- a/docs/developers.md
+++ b/docs/developers.md
@@ -1026,5 +1026,4 @@ helm repo update
## Gitlab (Experimental)
### Disclaimer
-The GitLab integration is for **experimental use only** and is **not fully implemented**. Features may be incomplete, unstable, or subject to change. Use at your own discretion.
-
+The GitLab integration is for **experimental use only** and is **not fully implemented**. Features may be incomplete, unstable, or subject to change. Use at your own discretion.
\ No newline at end of file
diff --git a/docs/gitlab-parentid.png b/docs/gitlab-parentid.png
new file mode 100644
index 000000000..95f80514e
Binary files /dev/null and b/docs/gitlab-parentid.png differ
diff --git a/exercises/broken-application/README.md b/exercises/broken-application/README.md
deleted file mode 100644
index a840ed786..000000000
--- a/exercises/broken-application/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# Exercise: Broken Application
-
-This application does not run. Can you figure out how to resolve these errors?
-Try applying `broken-application.yaml` with `kubectl`:
-
-```bash
-kubectl apply -f broken-application.yaml
-```
-
-When you resolved all errors, you should be able to access the application.
-Look into the service definition on how to access it.
diff --git a/exercises/broken-application/broken-application.ftl.yaml b/exercises/broken-application/broken-application.ftl.yaml
deleted file mode 100644
index e475c4f9b..000000000
--- a/exercises/broken-application/broken-application.ftl.yaml
+++ /dev/null
@@ -1,82 +0,0 @@
-<#assign DockerImageParser=statics['com.cloudogu.gitops.utils.DockerImageParser']>
-apiVersion: apps/v1
-kind: Deploymentz
-metadata:
- name: broken-application
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: broken-application
-<#if config.registry.createImagePullSecrets == true>
- imagePullSecrets:
- - name: proxy-registry
-#if>
- template:
- metadata:
- labels:
- app: broken-application
- spec:
- containers:
- - name: broken-application
- image: <#if config.images.nginx?has_content>
- ${DockerImageParser.parse(config.images.nginx)}
- <#else>
- bitnamilegacy/nginx:1.25.1
- #if>
- ports:
- - containerPort: 8080
-<#if config.application.podResources == true>
- resources:
- limits:
- cpu: 100m
- memory: 30Mi
- requests:
- cpu: 30m
- memory: 15Mi
-#if>
-
----
-
-apiVersion: v1
-kind: Service
-metadata:
- name: broken-application
- labels:
- app: broken-application
-spec:
- type: <#if config.application.remote>LoadBalancer<#else>ClusterIP#if>
- ports:
- - name: http
- port: 80
- targetPort: 8080
- selector:
- app: broken-application
-
-<#if config.features.exampleApps.nginx.baseDomain?has_content>
----
-
-apiVersion: networking.k8s.io/v1
-kind: Ingress
-metadata:
- name: broken-application
- labels:
- app: broken-application
-spec:
- rules:
- <#if config.application.urlSeparatorHyphen>
- - host: broken-application-${config.features.exampleApps.nginx.baseDomain}
- <#else>
- - host: broken-application.${config.features.exampleApps.nginx.baseDomain}
- #if>
- http:
- paths:
- - backend:
- service:
- name: broken-application
- port:
- name: http
- path: /
- pathType: Prefix
-
-#if>
diff --git a/exercises/nginx-validation/Jenkinsfile.ftl b/exercises/nginx-validation/Jenkinsfile.ftl
index dcebcf1b5..f97db6dfa 100644
--- a/exercises/nginx-validation/Jenkinsfile.ftl
+++ b/exercises/nginx-validation/Jenkinsfile.ftl
@@ -104,4 +104,4 @@ node('docker') {
def cesBuildLib
def gitOpsBuildLib
-#noparse>
+#noparse>
\ No newline at end of file
diff --git a/exercises/nginx-validation/README.md b/exercises/nginx-validation/README.md
deleted file mode 100644
index 3b8f5252c..000000000
--- a/exercises/nginx-validation/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Exercise: Resource validation using `gitops-build-lib`
-
-This repository contains an exercise on the validation utils provided by our `gitops-build-lib`. We've prepared some
-broken yaml-resources for this nginx-pipeline. Your task is to eliminate all the bugs and let the `jenkins` deploy it.
-Jenkins will provide you guidance using its logs. The validators will spot all the bugs for you, all you have to do is check the
-logs and fix the bugs.
-
-The first step you have to take is to copy this repository under the namespace `argocd` in order for `jenkins` to pick it up.
-You can use the export and import functions in SCM-Manager. You can export in "Settings" and import by clicking "Add Repository"
diff --git a/exercises/nginx-validation/config.yamllint.yaml b/exercises/nginx-validation/config.yamllint.yaml
deleted file mode 100644
index 182f60118..000000000
--- a/exercises/nginx-validation/config.yamllint.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
----
-extends: default
-
-rules:
- document-start:
- present: false
- indentation:
- spaces: consistent
- indent-sequences: consistent
- check-multi-line-strings: false
\ No newline at end of file
diff --git a/exercises/nginx-validation/index.html b/exercises/nginx-validation/index.html
deleted file mode 100644
index 16a5df40d..000000000
--- a/exercises/nginx-validation/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
- nginx - validation
-
-
-yEAH - you fixed me and successfully overcame the validation errors!
-
-
\ No newline at end of file
diff --git a/exercises/nginx-validation/k8s/values-production.ftl.yaml b/exercises/nginx-validation/k8s/values-production.ftl.yaml
deleted file mode 100644
index 1970de912..000000000
--- a/exercises/nginx-validation/k8s/values-production.ftl.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-namespaceOverride: ${config.application.namePrefix}example-apps-production
-
-<#if config.features.exampleApps.nginx.baseDomain?has_content>
-ingress:
- enabled: true
- pathType: Prefix
- <#if config.application.urlSeparatorHyphen>
- hostname: production-exercise-nginx-helm-${config.features.exampleApps.nginx.baseDomain}
- <#else>
- hostname: production.exercise-nginx-helm.${config.features.exampleApps.nginx.baseDomain}
- #if>
-#if>
diff --git a/exercises/nginx-validation/k8s/values-shared.ftl.yaml b/exercises/nginx-validation/k8s/values-shared.ftl.yaml
deleted file mode 100644
index 8888c5658..000000000
--- a/exercises/nginx-validation/k8s/values-shared.ftl.yaml
+++ /dev/null
@@ -1,44 +0,0 @@
-<#assign DockerImageParser=statics['com.cloudogu.gitops.utils.DockerImageParser']>
-<#if config.images.nginx?has_content>
-<#assign nginxImageObject = DockerImageParser.parse(config.images.nginx)>
-image:
- registry: ${nginxImageObject.registry}
- repository: ${nginxImageObject.repository}
- tag: ${nginxImageObject.tag}
-<#else>
-image:
- repository: bitnamilegacy/nginx
-#if>
-
-<#if config.registry.createImagePullSecrets == true>
-global:
- imagePullSecrets:
- - proxy-registry
-#if>
-service:
- ports:
- http: 80
-
-staticSiteConfigmap: exercise-index-nginx
-
-extraEnvVars:
- - name: LOG_LEVEL
- value: debug
-serverBlock: |-
- server {
- listen 0.0.0.0:8080;
- location /hi {
- return 200 "hello!";
- }
- }
-
-<#if config.application.podResources == true>
-resources:
- limits:
- cpu: 100m
- memory: 30Mi
- requests:
- cpu: 30m
- memory: 15Mi
-
-#if>
\ No newline at end of file
diff --git a/exercises/nginx-validation/k8s/values-staging.ftl.yaml b/exercises/nginx-validation/k8s/values-staging.ftl.yaml
deleted file mode 100644
index ffc331d6a..000000000
--- a/exercises/nginx-validation/k8s/values-staging.ftl.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-namespaceOverride: ${config.application.namePrefix}example-apps-staging
-
-<#if config.features.exampleApps.nginx.baseDomain?has_content>
-ingress:
- enabled: true
- pathType: Prefix
- <#if config.application.urlSeparatorHyphen>
- hostname: staging-exercise-nginx-helm-${config.features.exampleApps.nginx.baseDomain}
- <#else>
- hostname: staging.exercise-nginx-helm.${config.features.exampleApps.nginx.baseDomain}
- #if>
-#if>
diff --git a/exercises/petclinic-helm/Jenkinsfile.ftl b/exercises/petclinic-helm/Jenkinsfile.ftl
deleted file mode 100644
index ea1102474..000000000
--- a/exercises/petclinic-helm/Jenkinsfile.ftl
+++ /dev/null
@@ -1,109 +0,0 @@
-#!groovy
-
-String getApplication() { "exercise-spring-petclinic-helm" }
-String getScmManagerCredentials() { 'scmm-user' }
-String getConfigRepositoryPRBaseUrl() { env.SCMM_URL }
-String getConfigRepositoryPRRepo() { '${config.application.namePrefix}argocd/example-apps' }
-
-String getDockerRegistryBaseUrl() { env.${config.application.namePrefixForEnvVars}REGISTRY_URL }
-String getDockerRegistryPath() { env.${config.application.namePrefixForEnvVars}REGISTRY_PATH }
-String getDockerRegistryCredentials() { 'registry-user' }
-
-<#if config.registry.twoRegistries>
-String getDockerRegistryProxyCredentials() { 'registry-proxy-user' }
-String getDockerRegistryProxyBaseUrl() { env.${config.application.namePrefixForEnvVars}REGISTRY_PROXY_URL }
-#if>
-<#noparse>
-String getCesBuildLibRepo() { "${env.SCMM_URL}/repo/3rd-party-dependencies/ces-build-lib/" }
-String getCesBuildLibVersion() { '2.5.0' }
-String getHelmChartRepository() { "${env.SCMM_URL}/repo/3rd-party-dependencies/spring-boot-helm-chart-with-dependency" }
-String getHelmChartVersion() { "1.0.0" }
-String getMainBranch() { 'main' }
-
-cesBuildLib = library(identifier: "ces-build-lib@${cesBuildLibVersion}",
- retriever: modernSCM([$class: 'GitSCMSource', remote: cesBuildLibRepo, credentialsId: scmManagerCredentials])
-).com.cloudogu.ces.cesbuildlib
-
-properties([
- // Don't run concurrent builds, because the ITs use the same port causing random failures on concurrent builds.
- disableConcurrentBuilds()
-])
-
-node {
-
- mvn = cesBuildLib.MavenWrapper.new(this)
-#noparse>
-<#if config.jenkins.mavenCentralMirror?has_content>
- mvn.useMirrors([name: 'maven-central-mirror', mirrorOf: 'central', url: env.${config.application.namePrefixForEnvVars}MAVEN_CENTRAL_MIRROR])
-#if>
-<#noparse>
-
-
- catchError {
-
- stage('Checkout') {
- checkout scm
- }
-
- stage('Build') {
- mvn 'clean package -DskipTests -Dcheckstyle.skip'
- archiveArtifacts artifacts: '**/target/*.jar'
- }
-
- stage('Test') {
- mvn "test -Dmaven.test.failure.ignore=true -Dcheckstyle.skip"
- }
-
- String imageName = ""
- stage('Docker') {
- String imageTag = createImageTag()
-#noparse>
-<#noparse>
- String pathPrefix = !dockerRegistryPath?.trim() ? "" : "${dockerRegistryPath}/"
- imageName = "${dockerRegistryBaseUrl}/${pathPrefix}${application}:${imageTag}"
-#noparse>
-<#if config.registry.twoRegistries>
-<#noparse>
- docker.withRegistry("https://${dockerRegistryProxyBaseUrl}", dockerRegistryProxyCredentials) {
- image = docker.build(imageName, '.')
- }
-#noparse>
-<#else>
-<#noparse>
- image = docker.build(imageName, '.')
-#noparse>
-#if>
-<#noparse>
- if (isBuildSuccessful()) {
- docker.withRegistry("https://${dockerRegistryBaseUrl}", dockerRegistryCredentials) {
- image.push()
- }
- } else {
- echo 'Skipping docker push, because build not successful'
- }
- }
-
- stage('Deploy') {
- echo 'Use our gitops-build-lib to deploy this application via helm!'
- }
- }
-
- // Archive Unit and integration test results, if any
- junit allowEmptyResults: true, testResults: '**/target/failsafe-reports/TEST-*.xml,**/target/surefire-reports/TEST-*.xml'
-}
-
-
-String createImageTag() {
- def git = cesBuildLib.Git.new(this)
- String branch = git.simpleBranchName
- String branchSuffix = ""
-
- if (!"develop".equals(branch)) {
- branchSuffix = "-${branch}"
- }
-
- return "${new Date().format('yyyyMMddHHmm')}-${git.commitHashShort}${branchSuffix}"
-}
-
-def cesBuildLib
-#noparse>
diff --git a/exercises/petclinic-helm/README.md b/exercises/petclinic-helm/README.md
deleted file mode 100644
index 8d0b6388d..000000000
--- a/exercises/petclinic-helm/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Exercise: Deployment from helm application
-
-This repository contains an exercise on how to use our `gitops-build-lib` to create k8s resources and deploy an application.
-The `Jenkinsfile` contains stages on build, tests and building an image but is missing the deploy stage.
-Your task is to add the deploy-stage creating, verifying and deploying resources to the cluster using our `gitops-build-lib`.
-You can use our documentation on the `gitops-build-lib` to solve it - you can find hints (or the whole solution) in the `argocd/petclinic-helm` repository.
-
-The first step you have to take is to copy this repository under the namespace `argocd` in order for `jenkins` to pick it up.
-You can use the export and import functions in SCM-Manager. You can export in "Settings" and import by clicking "Add Repository"
diff --git a/exercises/petclinic-helm/k8s/values-production.ftl.yaml b/exercises/petclinic-helm/k8s/values-production.ftl.yaml
deleted file mode 100644
index 5d007d2a8..000000000
--- a/exercises/petclinic-helm/k8s/values-production.ftl.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-service:
- port: 80
-
-<#if config.features.exampleApps.petclinic.baseDomain?has_content>
-ingress:
- hosts:
- <#if config.application.urlSeparatorHyphen>
- - host: production-exercise-petclinic-helm-${config.features.exampleApps.petclinic.baseDomain}
- <#else>
- - host: production.exercise-petclinic-helm.${config.features.exampleApps.petclinic.baseDomain}
- #if>
- paths: ['/']
-#if>
diff --git a/exercises/petclinic-helm/k8s/values-shared.ftl.yaml b/exercises/petclinic-helm/k8s/values-shared.ftl.yaml
deleted file mode 100644
index 3514effac..000000000
--- a/exercises/petclinic-helm/k8s/values-shared.ftl.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
-extraEnv: |
- - name: TZ
- value: Europe/Berlin
-service:
- type: <#if config.application.remote>LoadBalancer<#else>ClusterIP#if>
-
-ingress:
- enabled: <#if config.features.exampleApps.petclinic.baseDomain?has_content>true<#else>false#if>
-
-# this is a helm chart dependency
-podinfo:
- ui:
- color: '#456456'
-
-<#if config.application.podResources == true>
-resources:
- limits:
- cpu: '1'
- memory: 1Gi
- requests:
- cpu: 300m
- memory: 650Mi
-<#else>
-<#-- Explicitly set to null, because the chart sets memory by default
- https://github.com/cloudogu/spring-boot-helm-chart/blob/0.3.2/values.yaml#L40 -->
-resources: null
-#if>
\ No newline at end of file
diff --git a/exercises/petclinic-helm/k8s/values-staging.ftl.yaml b/exercises/petclinic-helm/k8s/values-staging.ftl.yaml
deleted file mode 100644
index 0f4ae6d67..000000000
--- a/exercises/petclinic-helm/k8s/values-staging.ftl.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-service:
- port: 80
-
-<#if config.features.exampleApps.petclinic.baseDomain?has_content>
-ingress:
- hosts:
- <#if config.application.urlSeparatorHyphen>
- - host: staging-exercise-petclinic-helm-${config.features.exampleApps.petclinic.baseDomain}
- <#else>
- - host: staging.exercise-petclinic-helm.${config.features.exampleApps.petclinic.baseDomain}
- #if>
- paths: ['/']
-#if>
diff --git a/pom.xml b/pom.xml
index 109282fd4..9856fc124 100644
--- a/pom.xml
+++ b/pom.xml
@@ -232,6 +232,12 @@
2.3.32
+
+ io.kubernetes
+ client-java
+ 24.0.0
+
+
io.micronaut
micronaut-http-client
@@ -264,13 +270,6 @@
test
-
- io.kubernetes
- client-java
- 22.0.0
- test
-
-
org.assertj
assertj-core
@@ -360,7 +359,6 @@
-
maven-surefire-plugin
2.22.2
@@ -551,4 +549,4 @@
-
+
\ No newline at end of file
diff --git a/scripts/local/install-argocd-operator.sh b/scripts/local/install-argocd-operator.sh
index 4dc0a0ee4..b7f2bf106 100644
--- a/scripts/local/install-argocd-operator.sh
+++ b/scripts/local/install-argocd-operator.sh
@@ -1,24 +1,6 @@
git clone https://github.com/argoproj-labs/argocd-operator && \
cd argocd-operator && \
-git checkout release-0.11 && \
+git checkout release-0.16 && \
+make deploy IMG=quay.io/argoprojlabs/argocd-operator:v0.15.0
-# Disable webhook by commenting out lines in config/default/kustomization.yaml
-sed -i 's|^- ../webhook|# - ../webhook|' config/default/kustomization.yaml && \
-sed -i 's|^- path: manager_webhook_patch.yaml|# - path: manager_webhook_patch.yaml|' config/default/kustomization.yaml && \
-
-# Change the image tag from v0.11.1 to v0.11.0 in config/manager/kustomization.yaml
-sed -i 's|newTag: v0.11.1|newTag: v0.11.0|' config/manager/kustomization.yaml && \
-
-# Install Prometheus CRDs
-kubectl create -f https://raw.githubusercontent.com/prometheus-community/helm-charts/main/charts/kube-prometheus-stack/charts/crds/crds/crd-alertmanagerconfigs.yaml || true && \
-kubectl create -f https://raw.githubusercontent.com/prometheus-community/helm-charts/main/charts/kube-prometheus-stack/charts/crds/crds/crd-alertmanagers.yaml || true && \
-kubectl create -f https://raw.githubusercontent.com/prometheus-community/helm-charts/main/charts/kube-prometheus-stack/charts/crds/crds/crd-podmonitors.yaml || true && \
-kubectl create -f https://raw.githubusercontent.com/prometheus-community/helm-charts/main/charts/kube-prometheus-stack/charts/crds/crds/crd-probes.yaml || true && \
-kubectl create -f https://raw.githubusercontent.com/prometheus-community/helm-charts/main/charts/kube-prometheus-stack/charts/crds/crds/crd-prometheuses.yaml || true && \
-kubectl create -f https://raw.githubusercontent.com/prometheus-community/helm-charts/main/charts/kube-prometheus-stack/charts/crds/crds/crd-prometheusrules.yaml || true && \
-kubectl create -f https://raw.githubusercontent.com/prometheus-community/helm-charts/main/charts/kube-prometheus-stack/charts/crds/crds/crd-servicemonitors.yaml || true && \
-kubectl create -f https://raw.githubusercontent.com/prometheus-community/helm-charts/main/charts/kube-prometheus-stack/charts/crds/crds/crd-thanosrulers.yaml || true && \
-
-# Install ArgoCD Operator CRDs and components
-kubectl kustomize config/default | kubectl create -f - || true
rm -Rf ../argocd-operator/
\ No newline at end of file
diff --git a/scripts/scm-manager/init-scmm.sh b/scripts/scm-manager/init-scmm.sh
index 7013ef930..de958a691 100755
--- a/scripts/scm-manager/init-scmm.sh
+++ b/scripts/scm-manager/init-scmm.sh
@@ -34,16 +34,8 @@ function initSCMM() {
if [[ ${SCM_PROVIDER} == "scm-manager" ]]; then
configureScmmManager
fi
-
- if [[ $CONTENT_EXAMPLES == true ]]; then
- pushHelmChartRepo "3rd-party-dependencies/spring-boot-helm-chart"
- pushHelmChartRepoWithDependency "3rd-party-dependencies/spring-boot-helm-chart-with-dependency"
- pushRepoMirror "${GITOPS_BUILD_LIB_REPO}" "3rd-party-dependencies/gitops-build-lib"
- pushRepoMirror "${CES_BUILD_LIB_REPO}" "3rd-party-dependencies/ces-build-lib" 'develop'
- fi
}
-
function pushHelmChartRepo() {
TARGET_REPO_SCMM="$1"
@@ -179,63 +171,6 @@ function configureScmmManager() {
addUser "${METRICS_USERNAME}" "${METRICS_PASSWORD}" "changeme@test.local"
setPermissionForUser "${METRICS_USERNAME}" "metrics:read"
- USE_CENTRAL_SCM=$([[ -n "${CENTRAL_SCM_URL// /}" ]] && echo true || echo false)
- echo "Using central SCM: ${USE_CENTRAL_SCM}, URL: '${CENTRAL_SCM_URL}'"
-
- ### ArgoCD Repos
- if [[ $INSTALL_ARGOCD == true ]]; then
-
- addRepo "${NAME_PREFIX}argocd" "argocd" "GitOps repo for administration of ArgoCD" "$USE_CENTRAL_SCM"
- setPermission "${NAME_PREFIX}argocd" "argocd" "${GITOPS_USERNAME}" "WRITE"
-
- addRepo "${NAME_PREFIX}argocd" "cluster-resources" "GitOps repo for basic cluster-resources" "$USE_CENTRAL_SCM"
- setPermission "${NAME_PREFIX}argocd" "cluster-resources" "${GITOPS_USERNAME}" "WRITE"
-
- setPermissionForNamespace "${NAME_PREFIX}argocd" "${GITOPS_USERNAME}" "CI-SERVER"
-
- if [[ $USE_CENTRAL_SCM == true ]]; then
- addRepo "${NAME_PREFIX}argocd" "argocd" "Bootstrap repo for applications"
- setPermission "${NAME_PREFIX}argocd" "argocd" "${GITOPS_USERNAME}" "WRITE"
- fi
- fi
-
- if [[ $CONTENT_EXAMPLES == true ]]; then
- addRepo "${NAME_PREFIX}argocd" "nginx-helm-jenkins" "3rd Party app (NGINX) with helm, templated in Jenkins (gitops-build-lib)"
- setPermission "${NAME_PREFIX}argocd" "nginx-helm-jenkins" "${GITOPS_USERNAME}" "WRITE"
-
- addRepo "${NAME_PREFIX}argocd" "petclinic-plain" "Java app with plain k8s resources"
- setPermission "${NAME_PREFIX}argocd" "petclinic-plain" "${GITOPS_USERNAME}" "WRITE"
-
- addRepo "${NAME_PREFIX}argocd" "petclinic-helm" "Java app with custom helm chart"
- setPermission "${NAME_PREFIX}argocd" "petclinic-helm" "${GITOPS_USERNAME}" "WRITE"
-
- addRepo "${NAME_PREFIX}argocd" "example-apps" "GitOps repo for examples of end-user applications"
- setPermission "${NAME_PREFIX}argocd" "example-apps" "${GITOPS_USERNAME}" "WRITE"
-
- ### Repos with replicated dependencies
- addRepo "3rd-party-dependencies" "spring-boot-helm-chart"
- setPermission "3rd-party-dependencies" "spring-boot-helm-chart" "${GITOPS_USERNAME}" "WRITE"
-
- addRepo "3rd-party-dependencies" "spring-boot-helm-chart-with-dependency"
- setPermission "3rd-party-dependencies" "spring-boot-helm-chart-with-dependency" "${GITOPS_USERNAME}" "WRITE"
-
- addRepo "3rd-party-dependencies" "gitops-build-lib" "Jenkins pipeline shared library for automating deployments via GitOps "
- setPermission "3rd-party-dependencies" "gitops-build-lib" "${GITOPS_USERNAME}" "WRITE"
-
- addRepo "3rd-party-dependencies" "ces-build-lib" "Jenkins pipeline shared library adding features for Maven, Gradle, Docker, SonarQube, Git and others"
- setPermission "3rd-party-dependencies" "ces-build-lib" "${GITOPS_USERNAME}" "WRITE"
-
- ### Exercise Repos
- addRepo "${NAME_PREFIX}exercises" "petclinic-helm"
- setPermission "${NAME_PREFIX}exercises" "petclinic-helm" "${GITOPS_USERNAME}" "WRITE"
-
- addRepo "${NAME_PREFIX}exercises" "nginx-validation"
- setPermission "${NAME_PREFIX}exercises" "nginx-validation" "${GITOPS_USERNAME}" "WRITE"
-
- addRepo "${NAME_PREFIX}exercises" "broken-application"
- setPermission "${NAME_PREFIX}exercises" "broken-application" "${GITOPS_USERNAME}" "WRITE"
- fi
-
# Install necessary plugins
installScmmPlugins
diff --git a/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy b/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy
index 1d55b4695..ebda4feae 100644
--- a/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy
@@ -14,6 +14,7 @@ import com.cloudogu.gitops.destroy.Destroyer
import com.cloudogu.gitops.utils.CommandExecutor
import com.cloudogu.gitops.utils.FileSystemUtils
import com.cloudogu.gitops.utils.K8sClient
+import com.cloudogu.gitops.utils.NetworkingUtils
import groovy.util.logging.Slf4j
import groovy.yaml.YamlSlurper
import io.micronaut.context.ApplicationContext
diff --git a/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScripted.groovy b/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScripted.groovy
index f7ced089a..03751179f 100644
--- a/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScripted.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScripted.groovy
@@ -12,13 +12,9 @@ import com.cloudogu.gitops.features.argocd.ArgoCD
import com.cloudogu.gitops.features.deployment.ArgoCdApplicationStrategy
import com.cloudogu.gitops.features.deployment.Deployer
import com.cloudogu.gitops.features.deployment.HelmStrategy
-import com.cloudogu.gitops.jenkins.GlobalPropertyManager
-import com.cloudogu.gitops.jenkins.JenkinsApiClient
-import com.cloudogu.gitops.jenkins.JobManager
-import com.cloudogu.gitops.jenkins.PrometheusConfigurator
-import com.cloudogu.gitops.jenkins.UserManager
-import com.cloudogu.gitops.scmm.ScmmRepoProvider
-import com.cloudogu.gitops.scmm.api.ScmmApiClient
+import com.cloudogu.gitops.features.git.GitHandler
+import com.cloudogu.gitops.git.GitRepoFactory
+import com.cloudogu.gitops.jenkins.*
import com.cloudogu.gitops.utils.*
import groovy.util.logging.Slf4j
import io.micronaut.context.ApplicationContext
@@ -47,6 +43,7 @@ class GitopsPlaygroundCliMainScripted {
def fileSystemUtils = new FileSystemUtils()
def executor = new CommandExecutor()
+ def networkingUtils = new NetworkingUtils()
def k8sClient = new K8sClient(executor, fileSystemUtils, new Provider() {
@Override
Config get() {
@@ -57,52 +54,46 @@ class GitopsPlaygroundCliMainScripted {
def httpClientFactory = new HttpClientFactory()
- def scmmRepoProvider = new ScmmRepoProvider(config, fileSystemUtils)
+ def scmmRepoProvider = new GitRepoFactory(config, fileSystemUtils)
- def insecureSslContextProvider = new Provider() {
- @Override
- HttpClientFactory.InsecureSslContext get() {
- return httpClientFactory.insecureSslContext()
- }
- }
- def httpClientScmm = httpClientFactory.okHttpClientScmm(httpClientFactory.createLoggingInterceptor(), config, insecureSslContextProvider)
- def scmmApiClient = new ScmmApiClient(config, httpClientScmm)
+ def helmStrategy = new HelmStrategy(config, helmClient)
+
+ def gitHandler = new GitHandler(config, helmStrategy, fileSystemUtils, k8sClient, networkingUtils)
def jenkinsApiClient = new JenkinsApiClient(config,
- httpClientFactory.okHttpClientJenkins(httpClientFactory.createLoggingInterceptor(), config, insecureSslContextProvider))
+ httpClientFactory.okHttpClientJenkins(config))
context.registerSingleton(k8sClient)
if (config.application.destroy) {
context.registerSingleton(new Destroyer([
- new ArgoCDDestructionHandler(config, k8sClient, scmmRepoProvider, helmClient, fileSystemUtils),
- new ScmmDestructionHandler(config, scmmApiClient),
+ new ArgoCDDestructionHandler(config, k8sClient, scmmRepoProvider, helmClient, fileSystemUtils, gitHandler),
+ new ScmmDestructionHandler(config),
new JenkinsDestructionHandler(new JobManager(jenkinsApiClient), config, new GlobalPropertyManager(jenkinsApiClient))
]))
} else {
- def helmStrategy = new HelmStrategy(config, helmClient)
-
- def deployer = new Deployer(config, new ArgoCdApplicationStrategy(config, fileSystemUtils, scmmRepoProvider), helmStrategy)
+ def deployer = new Deployer(config, new ArgoCdApplicationStrategy(config, fileSystemUtils, scmmRepoProvider, gitHandler), helmStrategy)
- def airGappedUtils = new AirGappedUtils(config, scmmRepoProvider, scmmApiClient, fileSystemUtils, helmClient)
- def networkingUtils = new NetworkingUtils()
+ def airGappedUtils = new AirGappedUtils(config, scmmRepoProvider, fileSystemUtils, helmClient, gitHandler)
def jenkins = new Jenkins(config, executor, fileSystemUtils, new GlobalPropertyManager(jenkinsApiClient),
new JobManager(jenkinsApiClient), new UserManager(jenkinsApiClient),
- new PrometheusConfigurator(jenkinsApiClient), helmStrategy, k8sClient, networkingUtils)
+ new PrometheusConfigurator(jenkinsApiClient), helmStrategy, k8sClient, networkingUtils,gitHandler)
+ // 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 ScmManager(config, executor, fileSystemUtils, helmStrategy, k8sClient, networkingUtils),
+ new ScmManagerSetup(config, executor, fileSystemUtils, helmStrategy, k8sClient, networkingUtils),
+ gitHandler,
jenkins,
- new ArgoCD(config, k8sClient, helmClient, fileSystemUtils, scmmRepoProvider),
- new IngressNginx(config, fileSystemUtils, deployer, k8sClient, airGappedUtils),
- new CertManager(config, fileSystemUtils, deployer, k8sClient, airGappedUtils),
- new Mailhog(config, fileSystemUtils, deployer, k8sClient, airGappedUtils),
- new PrometheusStack(config, fileSystemUtils, deployer, k8sClient, airGappedUtils, scmmRepoProvider),
- new ExternalSecretsOperator(config, fileSystemUtils, deployer, k8sClient, airGappedUtils),
- new Vault(config, fileSystemUtils, k8sClient, deployer, airGappedUtils),
- new Content(config, k8sClient, scmmRepoProvider, scmmApiClient, jenkins),
+ new ArgoCD(config, k8sClient, helmClient, fileSystemUtils, scmmRepoProvider, 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 ExternalSecretsOperator(config, fileSystemUtils, deployer, k8sClient, airGappedUtils, gitHandler),
+ new Vault(config, fileSystemUtils, k8sClient, deployer, airGappedUtils, gitHandler),
+ new ContentLoader(config, k8sClient, scmmRepoProvider, jenkins, gitHandler),
]))
}
}
diff --git a/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy b/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy
index 7d0c54a4a..a5b0c773f 100644
--- a/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy
@@ -1,5 +1,6 @@
package com.cloudogu.gitops.config
+
import com.cloudogu.gitops.utils.FileSystemUtils
import groovy.util.logging.Slf4j
@@ -23,7 +24,7 @@ class ApplicationConfigurator {
addAdditionalApplicationConfig(newConfig)
addNamePrefix(newConfig)
- addScmmConfig(newConfig)
+ addScmConfig(newConfig)
addRegistryConfig(newConfig)
@@ -56,13 +57,6 @@ class ApplicationConfigurator {
if (newConfig.features.ingressNginx.active && !newConfig.application.baseUrl) {
log.warn("Ingress-controller is activated without baseUrl parameter. Services will not be accessible by hostnames. To avoid this use baseUrl with ingress. ")
}
- if (newConfig.content.examples) {
- if (!newConfig.registry.active) {
- throw new RuntimeException("content.examples requires either registry.active or registry.url")
- }
- String prefix = newConfig.application.namePrefix
- newConfig.content.namespaces += [prefix + "example-apps-staging", prefix + "example-apps-production"]
- }
}
private void addNamePrefix(Config newConfig) {
@@ -117,40 +111,38 @@ class ApplicationConfigurator {
}
}
- private void addScmmConfig(Config newConfig) {
- log.debug("Adding additional config for SCM-Manager")
+ private void addScmConfig(Config newConfig) {
+ log.debug("Adding additional config for SCM")
- newConfig.scmm.gitOpsUsername = "${newConfig.application.namePrefix}gitops"
-
- if (newConfig.scmm.url) {
+ if (newConfig.scm.scmManager.url) {
log.debug("Setting external scmm config")
- newConfig.scmm.internal = false
- newConfig.scmm.urlForJenkins = newConfig.scmm.url
+ newConfig.scm.scmManager.internal = false
+ newConfig.scm.scmManager.urlForJenkins = newConfig.scm.scmManager.url
} else {
log.debug("Setting configs for internal SCM-Manager")
// We use the K8s service as default name here, because it is the only option:
- // "scmm.localhost" will not work inside the Pods and k3d-container IP + Port (e.g. 172.x.y.z:9091)
+ // "scmm.localhost" will not work inside the Pods and k3d-container IP + Port (e.g. 172.x.y.z:9091)
// will not work on Windows and MacOS.
- newConfig.scmm.urlForJenkins =
+ newConfig.scm.scmManager.urlForJenkins =
"http://scmm.${newConfig.application.namePrefix}scm-manager.svc.cluster.local/scm"
- // More internal fields are set lazily in ScmManger.groovy (after SCMM is deployed and ports are known)
+ // More internal fields are set lazily in ScmManger.groovy (after SCMM is deployed and ports are known)
}
// We probably could get rid of some of the complexity by refactoring url, host and ingress into a single var
if (newConfig.application.baseUrl) {
- newConfig.scmm.ingress = new URL(injectSubdomain("scmm",
+ //TODO check, do we need ingerss? During ScmManager setup --> redesign by oop concept
+ newConfig.scm.scmManager.ingress = new URL(injectSubdomain("scmm",
newConfig.application.baseUrl as String, newConfig.application.urlSeparatorHyphen as Boolean)).host
}
// When specific user/pw are not set, set them to global values
- if (newConfig.scmm.password === Config.DEFAULT_ADMIN_PW) {
- newConfig.scmm.password = newConfig.application.password
+ if (newConfig.scm.scmManager.password === Config.DEFAULT_ADMIN_PW) {
+ newConfig.scm.scmManager.password = newConfig.application.password
}
- if (newConfig.scmm.username === Config.DEFAULT_ADMIN_USER) {
- newConfig.scmm.username = newConfig.application.username
+ if (newConfig.scm.scmManager.username === Config.DEFAULT_ADMIN_USER) {
+ newConfig.scm.scmManager.username = newConfig.application.username
}
-
}
private void addJenkinsConfig(Config newConfig) {
@@ -159,13 +151,13 @@ class ApplicationConfigurator {
log.debug("Setting external jenkins config")
newConfig.jenkins.active = true
newConfig.jenkins.internal = false
- newConfig.jenkins.urlForScmm = newConfig.jenkins.url
+ newConfig.jenkins.urlForScm = newConfig.jenkins.url
} else if (newConfig.jenkins.active) {
log.debug("Setting configs for internal jenkins")
// We use the K8s service as default name here, because it is the only option:
// "jenkins.localhost" will not work inside the Pods and k3d-container IP + Port (e.g. 172.x.y.z:9090)
// will not work on Windows and MacOS.
- newConfig.jenkins.urlForScmm = "http://jenkins.${newConfig.application.namePrefix}jenkins.svc.cluster.local"
+ newConfig.jenkins.urlForScm = "http://jenkins.${newConfig.application.namePrefix}jenkins.svc.cluster.local"
// More internal fields are set lazily in Jenkins.groovy (after Jenkins is deployed and ports are known)
} else {
@@ -215,41 +207,26 @@ class ApplicationConfigurator {
log.debug("Setting Vault URL ${vault.url}")
}
- if (!newConfig.features.exampleApps.petclinic.baseDomain) {
- // This param only requires the host / domain
- newConfig.features.exampleApps.petclinic.baseDomain =
- new URL(injectSubdomain('petclinic', baseUrl, urlSeparatorHyphen)).host
- log.debug("Setting Petclinic URL ${newConfig.features.exampleApps.petclinic.baseDomain}")
- }
- if (!newConfig.features.exampleApps.nginx.baseDomain) {
- // This param only requires the host / domain
- newConfig.features.exampleApps.nginx.baseDomain =
- new URL(injectSubdomain('nginx', baseUrl, urlSeparatorHyphen)).host
- log.debug("Setting Nginx URL ${newConfig.features.exampleApps.nginx.baseDomain}")
- }
}
+ // TODO: Anna check all condig.multitenant.*
void setMultiTenantModeConfig(Config newConfig) {
if (newConfig.multiTenant.useDedicatedInstance) {
if (!newConfig.application.namePrefix) {
throw new RuntimeException('To enable Central Multi-Tenant mode, you must define a name prefix to distinguish between instances.')
}
- if (!newConfig.multiTenant.username || !newConfig.multiTenant.password) {
- throw new RuntimeException('To use Central Multi Tenant mode define the username and password for the central SCM instance.')
- }
-
if (!newConfig.features.argocd.operator) {
newConfig.features.argocd.operator = true
}
// Removes trailing slash from the input URL to avoid duplicated slashes in further URL handling
- if (newConfig.multiTenant.centralScmUrl) {
- String urlString = newConfig.multiTenant.centralScmUrl.toString()
+ if (newConfig.multiTenant.scmManager.url) {
+ String urlString = newConfig.multiTenant.scmManager.url.toString()
if (urlString.endsWith("/")) {
urlString = urlString[0..-2]
}
- newConfig.multiTenant.centralScmUrl= urlString
+ newConfig.multiTenant.scmManager.url = urlString
}
//Disabling IngressNginx in DedicatedInstances Mode for now.
@@ -304,7 +281,7 @@ class ApplicationConfigurator {
static void validateContent(Config config) {
config.content.repos.each { repo ->
-
+
if (!repo.url) {
throw new RuntimeException("content.repos requires a url parameter.")
}
@@ -345,8 +322,8 @@ class ApplicationConfigurator {
private void validateScmmAndJenkinsAreBothSet(Config configToSet) {
if (configToSet.jenkins.active &&
- (configToSet.scmm.url && !configToSet.jenkins.url ||
- !configToSet.scmm.url && configToSet.jenkins.url)) {
+ (configToSet.scm.scmManager.url && !configToSet.jenkins.url ||
+ !configToSet.scm.scmManager.url && configToSet.jenkins.url)) {
throw new RuntimeException('When setting jenkins URL, scmm URL must also be set and the other way round')
}
}
diff --git a/src/main/groovy/com/cloudogu/gitops/config/Config.groovy b/src/main/groovy/com/cloudogu/gitops/config/Config.groovy
index 2c39d8f00..59f44cd51 100644
--- a/src/main/groovy/com/cloudogu/gitops/config/Config.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/config/Config.groovy
@@ -1,6 +1,6 @@
package com.cloudogu.gitops.config
-import com.cloudogu.gitops.utils.NetworkingUtils
+import com.cloudogu.gitops.features.git.config.ScmTenantSchema
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonPropertyDescription
import com.fasterxml.jackson.core.JsonGenerator
@@ -65,11 +65,11 @@ class Config {
@JsonPropertyDescription(MULTITENANT_DESCRIPTION)
@Mixin
- MultiTentantSchema multiTenant = new MultiTentantSchema()
+ MultiTenantSchema multiTenant = new MultiTenantSchema()
- @JsonPropertyDescription(SCMM_DESCRIPTION)
+ @JsonPropertyDescription(SCM_DESCRIPTION)
@Mixin
- ScmmSchema scmm = new ScmmSchema()
+ ScmTenantSchema scm = new ScmTenantSchema()
@JsonPropertyDescription(APPLICATION_DESCRIPTION)
@Mixin
@@ -229,7 +229,7 @@ class Config {
This is the URL configured in SCMM inside the Jenkins Plugin, e.g. at http://scmm.localhost/scm/admin/settings/jenkins
See addJenkinsConfig() and the comment at scmm.urlForJenkins */
- String urlForScmm = ''
+ String urlForScm = ''
String ingress = ''
// Bash image used with internal Jenkins only
String internalBashImage = 'bash:5'
@@ -289,94 +289,6 @@ class Config {
version: '5.8.43')
}
- static class ScmmSchema {
- Boolean internal = true
- String gitOpsUsername = ''
- /* When installing from via Docker we have to distinguish scmm.url (which is a local IP address) from
- the SCMM URL used by jenkins.
-
- This is necessary to make the build on push feature (webhooks from SCMM to Jenkins that trigger builds) work
- in k3d.
- The webhook contains repository URLs that start with the "Base URL" Setting of SCMM.
- Jenkins checks these repo URLs and triggers all builds that match repo URLs.
-
- This value is set as "Base URL" in SCMM Settings and in Jenkins Job.
-
- See ApplicationConfigurator.addScmmConfig() and the comment at jenkins.urlForScmm */
- String urlForJenkins = ''
- @JsonIgnore String getHost() { return NetworkingUtils.getHost(url)}
- @JsonIgnore String getProtocol() { return NetworkingUtils.getProtocol(url)}
- String ingress = ''
-
- @Option(names = ['--scmm-skip-restart'], description = SCMM_SKIP_RESTART_DESCRIPTION)
- @JsonPropertyDescription(SCMM_SKIP_RESTART_DESCRIPTION)
- Boolean skipRestart = false
-
- @Option(names = ['--scmm-skip-plugins'], description = SCMM_SKIP_PLUGINS_DESCRIPTION)
- @JsonPropertyDescription(SCMM_SKIP_PLUGINS_DESCRIPTION)
- Boolean skipPlugins = false
-
- @Option(names = ['--scmm-url'], description = SCMM_URL_DESCRIPTION)
- @JsonPropertyDescription(SCMM_URL_DESCRIPTION)
- String url = ''
-
- @Option(names = ['--scmm-username'], description = SCMM_USERNAME_DESCRIPTION)
- @JsonPropertyDescription(SCMM_USERNAME_DESCRIPTION)
- String username = DEFAULT_ADMIN_USER
-
- @Option(names = ['--scmm-password'], description = SCMM_PASSWORD_DESCRIPTION)
- @JsonPropertyDescription(SCMM_PASSWORD_DESCRIPTION)
- String password = DEFAULT_ADMIN_PW
-
- @JsonPropertyDescription(HELM_CONFIG_DESCRIPTION)
- HelmConfigWithValues helm = new HelmConfigWithValues(
- chart: 'scm-manager',
- repoURL: 'https://packages.scm-manager.org/repository/helm-v2-releases/',
- version: '3.11.0',
- values: [:]
- )
-
- @Option(names = ['--scm-root-path'], description = SCM_ROOT_PATH_DESCRIPTION)
- @JsonPropertyDescription(SCM_ROOT_PATH_DESCRIPTION)
- String rootPath = 'repo'
-
- @Option(names = ['--scm-provider'], description = SCM_PROVIDER_DESCRIPTION)
- @JsonPropertyDescription(SCM_PROVIDER_DESCRIPTION)
- String provider = 'scm-manager'
-
- }
-
- static class MultiTentantSchema {
-
- @Option(names = ['--dedicated-internal'], description = CENTRAL_SCM_INTERNAL_DESCRIPTION)
- @JsonPropertyDescription(CENTRAL_SCM_INTERNAL_DESCRIPTION)
- Boolean internal = false
-
- @Option(names = ['--dedicated-instance'], description = CENTRAL_USEDEDICATED_DESCRIPTION)
- @JsonPropertyDescription(CENTRAL_USEDEDICATED_DESCRIPTION)
- Boolean useDedicatedInstance = false
-
- @Option(names = ['--central-scm-url'], description = CENTRAL_MGMT_REPO_DESCRIPTION)
- @JsonPropertyDescription(CENTRAL_MGMT_REPO_DESCRIPTION)
- String centralScmUrl = ''
-
- @Option(names = ['--central-scm-username'], description = CENTRAL_SCMM_USERNAME_DESCRIPTION)
- @JsonPropertyDescription(CENTRAL_SCMM_USERNAME_DESCRIPTION)
- String username = ''
-
- @Option(names = ['--central-scm-password'], description = CENTRAL_SCMM_PASSWORD_DESCRIPTION)
- @JsonPropertyDescription(CENTRAL_SCMM_PASSWORD_DESCRIPTION)
- String password = ''
-
- @Option(names = ['--central-argocd-namespace'], description = CENTRAL_ARGOCD_NAMESPACE_DESCRIPTION)
- @JsonPropertyDescription(CENTRAL_ARGOCD_NAMESPACE_DESCRIPTION)
- String centralArgocdNamespace = 'argocd'
-
- @Option(names = ['--central-scm-namespace'], description = CENTRAL_ARGOCD_NAMESPACE_DESCRIPTION)
- @JsonPropertyDescription(CENTRAL_ARGOCD_NAMESPACE_DESCRIPTION)
- String centralSCMamespace = 'scm-manager'
- }
-
static class ApplicationSchema {
Boolean runningInsideK8s = false
String namePrefixForEnvVars = ''
diff --git a/src/main/groovy/com/cloudogu/gitops/config/ConfigConstants.groovy b/src/main/groovy/com/cloudogu/gitops/config/ConfigConstants.groovy
index a9a1acdde..05e6777fc 100644
--- a/src/main/groovy/com/cloudogu/gitops/config/ConfigConstants.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/config/ConfigConstants.groovy
@@ -27,10 +27,10 @@ interface ConfigConstants {
String CONTENT_DESCRIPTION = 'Config parameters for content, i.e. end-user or tenant applications as opposed to cluster-resources'
- // Content
+ // ContentLoader
String CONTENT_EXAMPLES_DESCRIPTION = 'Deploy example content: source repos, GitOps repos, Jenkins Job, Argo CD apps/project'
String CONTENT_NAMESPACES_DESCRIPTION = 'Additional kubernetes namespaces. These are authorized to Argo CD, supplied with image pull secrets, monitored by prometheus, etc. Namespaces can be templates, e.g. ${config.application.namePrefix}staging'
- String CONTENT_REPO_DESCRIPTION = "Content repos to push into target environment"
+ String CONTENT_REPO_DESCRIPTION = "ContentLoader repos to push into target environment"
String CONTENT_REPO_URL_DESCRIPTION = "URL of the content repo. Mandatory for each type."
String CONTENT_REPO_PATH_DESCRIPTION = "Path within the content repo to process"
String CONTENT_REPO_REF_DESCRIPTION = "Reference for a specific branch, tag, or commit. Emtpy defaults to default branch of the repo. With type MIRROR: ref must not be a commit hash; Choosing a ref only mirrors the ref but does not delete other branches/tags!"
@@ -38,7 +38,7 @@ interface ConfigConstants {
String CONTENT_REPO_USERNAME_DESCRIPTION = "Username to authenticate against content repo"
String CONTENT_REPO_PASSWORD_DESCRIPTION = "Password to authenticate against content repo"
String CONTENT_REPO_TEMPLATING_DESCRIPTION = "When true, template all files ending in .ftl within the repo"
- String CONTENT_REPO_TYPE_DESCRIPTION = "Content Repos can either be:\ncopied (only the files, starting on ref, starting at path within the repo. Requires target)\n, mirrored (FORCE pushes ref or the whole git repo if no ref set). Requires target, does not allow path and template.)\nfolderBased (folder structure is interpreted as repos. That is, root folder becomes namespace in SCM, sub folders become repository names in SCM, files are copied. Requires target.)"
+ String CONTENT_REPO_TYPE_DESCRIPTION = "ContentLoader Repos can either be:\ncopied (only the files, starting on ref, starting at path within the repo. Requires target)\n, mirrored (FORCE pushes ref or the whole git repo if no ref set). Requires target, does not allow path and template.)\nfolderBased (folder structure is interpreted as repos. That is, root folder becomes namespace in SCM, sub folders become repository names in SCM, files are copied. Requires target.)"
String CONTENT_REPO_TARGET_DESCRIPTION = "Target repo for the repository in the for of namespace/name. Must contain one slash to separate namespace from name."
String CONTENT_REPO_TARGET_OVERWRITE_MODE_DESCRIPTION = "This defines, how customer repos will be updated.\nINIT - push only if repo does not exist.\nRESET - delete all files after cloning source - files not in content are deleted\nUPGRADE - clone and copy - existing files will be overwritten, files not in content are kept. For type: MIRROR reset and upgrade have same result: in both cases source repo will be force pushed to target repo."
String CONTENT_REPO_CREATE_JENKINS_JOB_DESCRIPTION = "If true, creates a Jenkins job, if jenkinsfile exists in one of the content repo's branches."
@@ -58,30 +58,16 @@ interface ConfigConstants {
String JENKINS_ADDITIONAL_ENVS_DESCRIPTION = 'Set additional environments to Jenkins'
// group scmm
- String SCMM_DESCRIPTION = 'Config parameters for SCMManager (Git repository Server, https://scm-manager.org/)'
- String SCMM_SKIP_RESTART_DESCRIPTION = 'Skips restarting SCM-Manager after plugin installation. Use with caution! If the plugins are not installed up front, the installation will likely fail. The intended use case for this is after the first installation, for config changes only. Do not use on first installation or upgrades.\''
- String SCMM_SKIP_PLUGINS_DESCRIPTION = 'Skips plugin installation. Use with caution! If the plugins are not installed up front, the installation will likely fail. The intended use case for this is after the first installation, for config changes only. Do not use on first installation or upgrades.'
- String SCMM_URL_DESCRIPTION = 'The host of your external scm-manager'
- String SCMM_USERNAME_DESCRIPTION = 'Mandatory when scmm-url is set'
- String SCMM_PASSWORD_DESCRIPTION = 'Mandatory when scmm-url is set'
+ String SCM_DESCRIPTION = 'Config parameters for Scm'
String GIT_NAME_DESCRIPTION = 'Sets git author and committer name used for initial commits'
String GIT_EMAIL_DESCRIPTION = 'Sets git author and committer email used for initial commits'
- String SCM_ROOT_PATH_DESCRIPTION = 'Sets the root path for the Git Repositories. In SCM-Manager it is always "repo"'
- String SCM_PROVIDER_DESCRIPTION = 'Sets the scm Provider. Possible Options are "scm-manager" and "gitlab"'
//MutliTentant
- String CENTRAL_USEDEDICATED_DESCRIPTION = "Toggles the Dedicated Instances Mode"
- String CENTRAL_SCM_INTERNAL_DESCRIPTION = 'SCM for Central Management is running on the same cluster, so k8s internal URLs can be used for access'
String MULTITENANT_DESCRIPTION = 'Multi Tenant Configs'
- String CENTRAL_MGMT_REPO_DESCRIPTION = 'URL for the centralized Management Repo'
- String CENTRAL_SCMM_USERNAME_DESCRIPTION = 'CENTRAL SCMM USERNAME'
- String CENTRAL_SCMM_PASSWORD_DESCRIPTION = 'CENTRAL SCMM Password'
- String CENTRAL_ARGOCD_NAMESPACE_DESCRIPTION = 'CENTRAL Argocd Repo Namespace'
// group remote
String REMOTE_DESCRIPTION = 'Expose services as LoadBalancers'
String INSECURE_DESCRIPTION = 'Sets insecure-mode in cURL which skips cert validation'
- String LOCAL_HELM_CHART_FOLDER_DESCRIPTION = 'A local folder (within the GOP image mostly) where the local mirrors of all helm charts are loaded from when mirror-Repos is active. This is mostly needed for development.'
// group tool configuration
String APPLICATION_DESCRIPTION = 'Application configuration parameter for GOP'
diff --git a/src/main/groovy/com/cloudogu/gitops/config/Credentials.groovy b/src/main/groovy/com/cloudogu/gitops/config/Credentials.groovy
new file mode 100644
index 000000000..57dd3629f
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/config/Credentials.groovy
@@ -0,0 +1,11 @@
+package com.cloudogu.gitops.config
+
+class Credentials {
+ String username
+ String password
+
+ Credentials(String username, String password) {
+ this.username = username
+ this.password = password
+ }
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/config/MultiTenantSchema.groovy b/src/main/groovy/com/cloudogu/gitops/config/MultiTenantSchema.groovy
new file mode 100644
index 000000000..d2a7646b3
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/config/MultiTenantSchema.groovy
@@ -0,0 +1,42 @@
+package com.cloudogu.gitops.config
+
+import com.cloudogu.gitops.features.git.config.ScmCentralSchema.GitlabCentralConfig
+import com.cloudogu.gitops.features.git.config.ScmCentralSchema.ScmManagerCentralConfig
+import com.cloudogu.gitops.features.git.config.util.ScmProviderType
+import com.fasterxml.jackson.annotation.JsonPropertyDescription
+import picocli.CommandLine.Mixin
+import picocli.CommandLine.Option
+
+class MultiTenantSchema {
+
+ static final String SCM_PROVIDER_TYPE_DESCRIPTION = 'The SCM provider type. Possible values: SCM_MANAGER, GITLAB'
+ static final String GITLAB_CONFIG_DESCRIPTION = 'Config for GITLAB'
+ static final String SCMM_CONFIG_DESCRIPTION = 'Config for GITLAB'
+ static final String CENTRAL_ARGOCD_NAMESPACE_DESCRIPTION = 'Namespace for the centralized Argocd'
+ static final String CENTRAL_USEDEDICATED_DESCRIPTION = 'Toggles the Dedicated Instances Mode. See docs for more info'
+
+ @Option(
+ names = ['--central-scm-provider'],
+ description = SCM_PROVIDER_TYPE_DESCRIPTION,
+ defaultValue = "SCM_MANAGER"
+ )
+ @JsonPropertyDescription(SCM_PROVIDER_TYPE_DESCRIPTION)
+ ScmProviderType scmProviderType = ScmProviderType.SCM_MANAGER
+
+ @JsonPropertyDescription(GITLAB_CONFIG_DESCRIPTION)
+ @Mixin
+ GitlabCentralConfig gitlab
+
+ @JsonPropertyDescription(SCMM_CONFIG_DESCRIPTION)
+ @Mixin
+ ScmManagerCentralConfig scmManager
+
+ @Option(names = ['--central-argocd-namespace'], description = CENTRAL_ARGOCD_NAMESPACE_DESCRIPTION)
+ @JsonPropertyDescription(CENTRAL_ARGOCD_NAMESPACE_DESCRIPTION)
+ String centralArgocdNamespace = 'argocd'
+
+ @Option(names = ['--dedicated-instance'], description = CENTRAL_USEDEDICATED_DESCRIPTION)
+ @JsonPropertyDescription(CENTRAL_USEDEDICATED_DESCRIPTION)
+ Boolean useDedicatedInstance = false
+
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/dependencyinjection/HttpClientFactory.groovy b/src/main/groovy/com/cloudogu/gitops/dependencyinjection/HttpClientFactory.groovy
index 036b07185..585a1fa85 100644
--- a/src/main/groovy/com/cloudogu/gitops/dependencyinjection/HttpClientFactory.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/dependencyinjection/HttpClientFactory.groovy
@@ -1,13 +1,12 @@
package com.cloudogu.gitops.dependencyinjection
import com.cloudogu.gitops.config.Config
+import com.cloudogu.gitops.config.Credentials
+import com.cloudogu.gitops.git.providers.scmmanager.api.AuthorizationInterceptor
import com.cloudogu.gitops.okhttp.RetryInterceptor
-import com.cloudogu.gitops.scmm.api.AuthorizationInterceptor
import groovy.transform.TupleConstructor
import io.micronaut.context.annotation.Factory
-import io.micronaut.context.annotation.Prototype
import jakarta.inject.Named
-import jakarta.inject.Provider
import jakarta.inject.Singleton
import okhttp3.JavaNetCookieJar
import okhttp3.OkHttpClient
@@ -24,40 +23,38 @@ import java.security.cert.X509Certificate
@Factory
class HttpClientFactory {
- @Singleton
- @Named("jenkins")
- OkHttpClient okHttpClientJenkins(HttpLoggingInterceptor httpLoggingInterceptor, Config config, Provider insecureSslContextProvider) {
+
+ static OkHttpClient buildOkHttpClient(Credentials credentials, Boolean isInsecure) {
def builder = new OkHttpClient.Builder()
- .cookieJar(new JavaNetCookieJar(new CookieManager()))
- .addInterceptor(httpLoggingInterceptor)
+ .addInterceptor(new AuthorizationInterceptor(credentials.username, credentials.password))
+ .addInterceptor(createLoggingInterceptor())
.addInterceptor(new RetryInterceptor())
- if (config.application.insecure) {
- def context = insecureSslContextProvider.get()
+ if (isInsecure) {
+ def context = insecureSslContext()
builder.sslSocketFactory(context.socketFactory, context.trustManager)
}
return builder.build()
}
-
+
@Singleton
- @Named("scmm")
- OkHttpClient okHttpClientScmm(HttpLoggingInterceptor loggingInterceptor, Config config, Provider insecureSslContext) {
+ @Named("jenkins")
+ OkHttpClient okHttpClientJenkins(Config config) {
def builder = new OkHttpClient.Builder()
- .addInterceptor(new AuthorizationInterceptor(config.scmm.username, config.scmm.password))
- .addInterceptor(loggingInterceptor)
+ .cookieJar(new JavaNetCookieJar(new CookieManager()))
+ .addInterceptor(createLoggingInterceptor())
.addInterceptor(new RetryInterceptor())
if (config.application.insecure) {
- def context = insecureSslContext.get()
+ def context = insecureSslContext()
builder.sslSocketFactory(context.socketFactory, context.trustManager)
}
return builder.build()
}
- @Singleton
- HttpLoggingInterceptor createLoggingInterceptor() {
+ static HttpLoggingInterceptor createLoggingInterceptor() {
def logger = LoggerFactory.getLogger("com.cloudogu.gitops.HttpClient")
def ret = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@@ -73,8 +70,7 @@ class HttpClientFactory {
return ret
}
- @Prototype
- InsecureSslContext insecureSslContext() {
+ static InsecureSslContext insecureSslContext() {
def noCheckTrustManager = new X509TrustManager() {
@Override
void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
@@ -100,4 +96,4 @@ class HttpClientFactory {
final SSLSocketFactory socketFactory
final X509TrustManager trustManager
}
-}
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/destroy/ArgoCDDestructionHandler.groovy b/src/main/groovy/com/cloudogu/gitops/destroy/ArgoCDDestructionHandler.groovy
index 8e81468d6..5d2f3f8b1 100644
--- a/src/main/groovy/com/cloudogu/gitops/destroy/ArgoCDDestructionHandler.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/destroy/ArgoCDDestructionHandler.groovy
@@ -1,8 +1,9 @@
package com.cloudogu.gitops.destroy
import com.cloudogu.gitops.config.Config
-import com.cloudogu.gitops.scmm.ScmmRepo
-import com.cloudogu.gitops.scmm.ScmmRepoProvider
+import com.cloudogu.gitops.features.git.GitHandler
+import com.cloudogu.gitops.git.GitRepo
+import com.cloudogu.gitops.git.GitRepoFactory
import com.cloudogu.gitops.utils.FileSystemUtils
import com.cloudogu.gitops.utils.HelmClient
import com.cloudogu.gitops.utils.K8sClient
@@ -15,29 +16,31 @@ import java.nio.file.Path
@Order(100)
class ArgoCDDestructionHandler implements DestructionHandler {
private K8sClient k8sClient
- private ScmmRepoProvider repoProvider
+ private GitRepoFactory repoProvider
private HelmClient helmClient
private Config config
private FileSystemUtils fileSystemUtils
-
+ private GitHandler gitHandler
ArgoCDDestructionHandler(
Config config,
K8sClient k8sClient,
- ScmmRepoProvider repoProvider,
+ GitRepoFactory repoProvider,
HelmClient helmClient,
- FileSystemUtils fileSystemUtils
+ FileSystemUtils fileSystemUtils,
+ GitHandler gitHandler
) {
this.k8sClient = k8sClient
this.repoProvider = repoProvider
this.helmClient = helmClient
this.config = config
this.fileSystemUtils = fileSystemUtils
+ this.gitHandler = gitHandler
}
@Override
void destroy() {
- def repo = repoProvider.getRepo("argocd/argocd")
+ def repo = repoProvider.getRepo("argocd/argocd", gitHandler.resourcesScm)
repo.cloneRepo()
for (def app in k8sClient.getCustomResource("app")) {
@@ -82,10 +85,10 @@ class ArgoCDDestructionHandler implements DestructionHandler {
k8sClient.delete("app", 'argocd', "argocd")
k8sClient.delete('secret', 'default', 'jenkins-credentials')
- k8sClient.delete('secret', 'default', 'argocd-repo-creds-scmm')
+ k8sClient.delete('secret', 'default', 'argocd-repo-creds-scm')
}
- void installArgoCDViaHelm(ScmmRepo repo) {
+ void installArgoCDViaHelm(GitRepo repo) {
// this is a hack to be able to uninstall using helm
def namePrefix = config.application.namePrefix
@@ -98,4 +101,4 @@ class ArgoCDDestructionHandler implements DestructionHandler {
helmClient.dependencyBuild(umbrellaChartPath)
helmClient.upgrade('argocd', umbrellaChartPath, [namespace: "${namePrefix}argocd"])
}
-}
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/destroy/ScmmDestructionHandler.groovy b/src/main/groovy/com/cloudogu/gitops/destroy/ScmmDestructionHandler.groovy
index 6c34b5f83..8fa0aa603 100644
--- a/src/main/groovy/com/cloudogu/gitops/destroy/ScmmDestructionHandler.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/destroy/ScmmDestructionHandler.groovy
@@ -1,19 +1,18 @@
package com.cloudogu.gitops.destroy
import com.cloudogu.gitops.config.Config
-import com.cloudogu.gitops.scmm.api.ScmmApiClient
+import com.cloudogu.gitops.git.providers.scmmanager.api.ScmManagerApiClient
import io.micronaut.core.annotation.Order
import jakarta.inject.Singleton
@Singleton
@Order(200)
class ScmmDestructionHandler implements DestructionHandler {
- private ScmmApiClient scmmApiClient
+ private ScmManagerApiClient scmmApiClient
private Config config
ScmmDestructionHandler(
- Config config,
- ScmmApiClient scmmApiClient
+ Config config
) {
this.config = config
this.scmmApiClient = scmmApiClient
@@ -53,4 +52,4 @@ class ScmmDestructionHandler implements DestructionHandler {
throw new RuntimeException("Could not delete user $name (${response.code()} ${response.message()}): ${response.errorBody().string()}")
}
}
-}
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/features/CertManager.groovy b/src/main/groovy/com/cloudogu/gitops/features/CertManager.groovy
index 46c96da35..66b7759c0 100644
--- a/src/main/groovy/com/cloudogu/gitops/features/CertManager.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/features/CertManager.groovy
@@ -4,7 +4,7 @@ import com.cloudogu.gitops.Feature
import com.cloudogu.gitops.FeatureWithImage
import com.cloudogu.gitops.config.Config
import com.cloudogu.gitops.features.deployment.DeploymentStrategy
-import com.cloudogu.gitops.scmm.ScmUrlResolver
+import com.cloudogu.gitops.features.git.GitHandler
import com.cloudogu.gitops.utils.AirGappedUtils
import com.cloudogu.gitops.utils.FileSystemUtils
import com.cloudogu.gitops.utils.K8sClient
@@ -30,19 +30,22 @@ class CertManager extends Feature implements FeatureWithImage {
final K8sClient k8sClient
final Config config
final String namespace = "${config.application.namePrefix}cert-manager"
+ GitHandler gitHandler
CertManager(
Config config,
FileSystemUtils fileSystemUtils,
DeploymentStrategy deployer,
K8sClient k8sClient,
- AirGappedUtils airGappedUtils
+ AirGappedUtils airGappedUtils,
+ GitHandler gitHandler
) {
this.deployer = deployer
this.config = config
this.fileSystemUtils = fileSystemUtils
this.k8sClient = k8sClient
this.airGappedUtils = airGappedUtils
+ this.gitHandler = gitHandler
}
@Override
@@ -61,7 +64,6 @@ class CertManager extends Feature implements FeatureWithImage {
.getStaticModels(),
]) as Map
-
def valuesFromConfig = config.features.certManager.helm.values
def mergedMap = MapUtils.deepMerge(valuesFromConfig, templatedMap)
@@ -79,7 +81,7 @@ class CertManager extends Feature implements FeatureWithImage {
'Chart.yaml'))['version']
deployer.deployFeature(
- ScmUrlResolver.scmmRepoUrl(config, repoNamespaceAndName),
+ gitHandler.getResourcesScm().repoUrl(repoNamespaceAndName),
'cert-manager',
'.',
certManagerVersion,
diff --git a/src/main/groovy/com/cloudogu/gitops/features/Content.groovy b/src/main/groovy/com/cloudogu/gitops/features/ContentLoader.groovy
similarity index 96%
rename from src/main/groovy/com/cloudogu/gitops/features/Content.groovy
rename to src/main/groovy/com/cloudogu/gitops/features/ContentLoader.groovy
index 8920b9329..b3a9a957f 100644
--- a/src/main/groovy/com/cloudogu/gitops/features/Content.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/features/ContentLoader.groovy
@@ -3,9 +3,9 @@ package com.cloudogu.gitops.features
import com.cloudogu.gitops.Feature
import com.cloudogu.gitops.config.Config
import com.cloudogu.gitops.config.Config.OverwriteMode
-import com.cloudogu.gitops.scmm.ScmmRepo
-import com.cloudogu.gitops.scmm.ScmmRepoProvider
-import com.cloudogu.gitops.scmm.api.ScmmApiClient
+import com.cloudogu.gitops.features.git.GitHandler
+import com.cloudogu.gitops.git.GitRepo
+import com.cloudogu.gitops.git.GitRepoFactory
import com.cloudogu.gitops.utils.FileSystemUtils
import com.cloudogu.gitops.utils.K8sClient
import com.cloudogu.gitops.utils.TemplatingEngine
@@ -34,12 +34,11 @@ import static com.cloudogu.gitops.config.Config.ContentSchema.ContentRepositoryS
@Singleton
@Order(999)
// We want to evaluate content last, to allow for changing all other repos
-class Content extends Feature {
+class ContentLoader extends Feature {
private Config config
private K8sClient k8sClient
- private ScmmRepoProvider repoProvider
- private ScmmApiClient scmmApiClient
+ private GitRepoFactory repoProvider
private Jenkins jenkins
// set by lazy initialisation
private TemplatingEngine templatingEngine
@@ -47,14 +46,16 @@ class Content extends Feature {
private List cachedRepoCoordinates = new ArrayList<>()
private File mergedReposFolder
- Content(
- Config config, K8sClient k8sClient, ScmmRepoProvider repoProvider, ScmmApiClient scmmApiClient, Jenkins jenkins
+ private GitHandler gitHandler
+
+ ContentLoader(
+ Config config, K8sClient k8sClient, GitRepoFactory repoProvider, Jenkins jenkins, GitHandler gitHandler
) {
this.config = config
this.k8sClient = k8sClient
this.repoProvider = repoProvider
- this.scmmApiClient = scmmApiClient
this.jenkins = jenkins
+ this.gitHandler = gitHandler
}
@Override
@@ -239,7 +240,6 @@ class Content extends Feature {
}
}
-
private void cloneToLocalFolder(ContentRepositorySchema repoConfig, File repoTmpDir) {
def cloneCommand = gitClone()
@@ -303,8 +303,9 @@ class Content extends Feature {
private void pushTargetRepos(List repoCoordinates) {
repoCoordinates.each { repoCoordinate ->
- ScmmRepo targetRepo = repoProvider.getRepo(repoCoordinate.fullRepoName)
- def isNewRepo = targetRepo.create('', scmmApiClient, false)
+ GitRepo targetRepo = repoProvider.getRepo(repoCoordinate.fullRepoName,this.gitHandler.tenant)
+ boolean isNewRepo = targetRepo.createRepositoryAndSetPermission(repoCoordinate.fullRepoName, "", false)
+
if (isValidForPush(isNewRepo, repoCoordinate)) {
targetRepo.cloneRepo()
@@ -333,7 +334,7 @@ class Content extends Feature {
* Copies repoCoordinate to targetRepo, commits and pushes
* Same logic for both FOLDER_BASED and COPY repo types.
*/
- private static void handleRepoCopyingOrFolderBased(RepoCoordinate repoCoordinate, ScmmRepo targetRepo, boolean isNewRepo) {
+ private static void handleRepoCopyingOrFolderBased(RepoCoordinate repoCoordinate, GitRepo targetRepo, boolean isNewRepo) {
if (!isNewRepo) {
clearTargetRepoIfApplicable(repoCoordinate, targetRepo)
}
@@ -364,7 +365,7 @@ class Content extends Feature {
refSpec
}
- private static void clearTargetRepoIfApplicable(RepoCoordinate repoCoordinate, ScmmRepo targetRepo) {
+ private static void clearTargetRepoIfApplicable(RepoCoordinate repoCoordinate, GitRepo targetRepo) {
if (OverwriteMode.INIT != repoCoordinate.repoConfig.overwriteMode) {
if (OverwriteMode.RESET == repoCoordinate.repoConfig.overwriteMode) {
log.info("OverwriteMode ${OverwriteMode.RESET} set for repo '${repoCoordinate.fullRepoName}': " +
@@ -380,7 +381,7 @@ class Content extends Feature {
/**
* Force pushes repoCoordinate.repoConfig.ref or all refs to targetRepo
*/
- private static void handleRepoMirroring(RepoCoordinate repoCoordinate, ScmmRepo targetRepo) {
+ private static void handleRepoMirroring(RepoCoordinate repoCoordinate, GitRepo targetRepo) {
try (def targetGit = Git.open(new File(targetRepo.absoluteLocalRepoTmpDir))) {
def remoteUrl = targetGit.repository.config.getString('remote', 'origin', 'url')
@@ -423,7 +424,7 @@ class Content extends Feature {
}
}
- private void createJenkinsJobIfApplicable(RepoCoordinate repoCoordinate, ScmmRepo repo) {
+ private void createJenkinsJobIfApplicable(RepoCoordinate repoCoordinate, GitRepo repo) {
if (repoCoordinate.repoConfig.createJenkinsJob && jenkins.isEnabled()) {
if (existFileInSomeBranch(repo.absoluteLocalRepoTmpDir, 'Jenkinsfile')) {
jenkins.createJenkinsjob(repoCoordinate.namespace, repoCoordinate.namespace)
diff --git a/src/main/groovy/com/cloudogu/gitops/features/ExternalSecretsOperator.groovy b/src/main/groovy/com/cloudogu/gitops/features/ExternalSecretsOperator.groovy
index 25a1ff91b..57fe90849 100644
--- a/src/main/groovy/com/cloudogu/gitops/features/ExternalSecretsOperator.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/features/ExternalSecretsOperator.groovy
@@ -4,7 +4,7 @@ import com.cloudogu.gitops.Feature
import com.cloudogu.gitops.FeatureWithImage
import com.cloudogu.gitops.config.Config
import com.cloudogu.gitops.features.deployment.DeploymentStrategy
-import com.cloudogu.gitops.scmm.ScmUrlResolver
+import com.cloudogu.gitops.features.git.GitHandler
import com.cloudogu.gitops.utils.AirGappedUtils
import com.cloudogu.gitops.utils.FileSystemUtils
import com.cloudogu.gitops.utils.K8sClient
@@ -31,19 +31,22 @@ class ExternalSecretsOperator extends Feature implements FeatureWithImage {
private FileSystemUtils fileSystemUtils
private DeploymentStrategy deployer
private AirGappedUtils airGappedUtils
+ private GitHandler gitHandler
ExternalSecretsOperator(
Config config,
FileSystemUtils fileSystemUtils,
DeploymentStrategy deployer,
K8sClient k8sClient,
- AirGappedUtils airGappedUtils
+ AirGappedUtils airGappedUtils,
+ GitHandler gitHandler
) {
this.deployer = deployer
this.config = config
this.fileSystemUtils = fileSystemUtils
this.k8sClient = k8sClient
this.airGappedUtils = airGappedUtils
+ this.gitHandler=gitHandler
}
@Override
@@ -59,7 +62,7 @@ class ExternalSecretsOperator extends Feature implements FeatureWithImage {
// Allow for using static classes inside the templates
statics: new DefaultObjectWrapperBuilder(freemarker.template.Configuration.VERSION_2_3_32).build().getStaticModels()
])
-
+
def helmConfig = config.features.secrets.externalSecrets.helm
def mergedMap = MapUtils.deepMerge(helmConfig.values, templatedMap)
def tempValuesPath = fileSystemUtils.writeTempFile(mergedMap)
@@ -75,7 +78,7 @@ class ExternalSecretsOperator extends Feature implements FeatureWithImage {
'Chart.yaml'))['version']
deployer.deployFeature(
- ScmUrlResolver.scmmRepoUrl(config, repoNamespaceAndName),
+ this.gitHandler.resourcesScm.repoUrl(repoNamespaceAndName),
"external-secrets",
'.',
externalSecretsVersion,
diff --git a/src/main/groovy/com/cloudogu/gitops/features/IngressNginx.groovy b/src/main/groovy/com/cloudogu/gitops/features/IngressNginx.groovy
index f72aa906f..a01473971 100644
--- a/src/main/groovy/com/cloudogu/gitops/features/IngressNginx.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/features/IngressNginx.groovy
@@ -4,7 +4,7 @@ import com.cloudogu.gitops.Feature
import com.cloudogu.gitops.FeatureWithImage
import com.cloudogu.gitops.config.Config
import com.cloudogu.gitops.features.deployment.DeploymentStrategy
-import com.cloudogu.gitops.scmm.ScmUrlResolver
+import com.cloudogu.gitops.features.git.GitHandler
import com.cloudogu.gitops.utils.AirGappedUtils
import com.cloudogu.gitops.utils.FileSystemUtils
import com.cloudogu.gitops.utils.K8sClient
@@ -31,19 +31,22 @@ class IngressNginx extends Feature implements FeatureWithImage {
private FileSystemUtils fileSystemUtils
private DeploymentStrategy deployer
private AirGappedUtils airGappedUtils
+ private GitHandler gitHandler
IngressNginx(
Config config,
FileSystemUtils fileSystemUtils,
DeploymentStrategy deployer,
K8sClient k8sClient,
- AirGappedUtils airGappedUtils
+ AirGappedUtils airGappedUtils,
+ GitHandler gitHandler
) {
this.deployer = deployer
this.config = config
this.fileSystemUtils = fileSystemUtils
this.k8sClient = k8sClient
this.airGappedUtils = airGappedUtils
+ this.gitHandler = gitHandler
}
@Override
@@ -74,7 +77,7 @@ class IngressNginx extends Feature implements FeatureWithImage {
'Chart.yaml'))['version']
deployer.deployFeature(
- ScmUrlResolver.scmmRepoUrl(config, repoNamespaceAndName),
+ gitHandler.resourcesScm.repoUrl(repoNamespaceAndName),
'ingress-nginx',
'.',
ingressNginxVersion,
diff --git a/src/main/groovy/com/cloudogu/gitops/features/Jenkins.groovy b/src/main/groovy/com/cloudogu/gitops/features/Jenkins.groovy
index 1fc412ea2..0bb492d4b 100644
--- a/src/main/groovy/com/cloudogu/gitops/features/Jenkins.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/features/Jenkins.groovy
@@ -1,19 +1,16 @@
package com.cloudogu.gitops.features
import com.cloudogu.gitops.Feature
-import com.cloudogu.gitops.config.ApplicationConfigurator
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.GitHandler
+import com.cloudogu.gitops.features.git.config.util.ScmProviderType
import com.cloudogu.gitops.jenkins.GlobalPropertyManager
import com.cloudogu.gitops.jenkins.JobManager
import com.cloudogu.gitops.jenkins.PrometheusConfigurator
import com.cloudogu.gitops.jenkins.UserManager
-import com.cloudogu.gitops.utils.CommandExecutor
-import com.cloudogu.gitops.utils.FileSystemUtils
-import com.cloudogu.gitops.utils.K8sClient
-import com.cloudogu.gitops.utils.MapUtils
-import com.cloudogu.gitops.utils.NetworkingUtils
+import com.cloudogu.gitops.utils.*
import freemarker.template.Configuration
import freemarker.template.DefaultObjectWrapperBuilder
import groovy.util.logging.Slf4j
@@ -38,6 +35,7 @@ class Jenkins extends Feature {
private DeploymentStrategy deployer
private K8sClient k8sClient
private NetworkingUtils networkingUtils
+ private GitHandler gitHandler
Jenkins(
Config config,
@@ -49,7 +47,8 @@ class Jenkins extends Feature {
PrometheusConfigurator prometheusConfigurator,
HelmStrategy deployer,
K8sClient k8sClient,
- NetworkingUtils networkingUtils
+ NetworkingUtils networkingUtils,
+ GitHandler gitHandler
) {
this.config = config
this.commandExecutor = commandExecutor
@@ -61,8 +60,9 @@ class Jenkins extends Feature {
this.deployer = deployer
this.k8sClient = k8sClient
this.networkingUtils = networkingUtils
+ this.gitHandler = gitHandler
- if(config.jenkins.internal) {
+ if (config.jenkins.internal) {
this.namespace = "${config.application.namePrefix}jenkins"
}
}
@@ -72,6 +72,7 @@ class Jenkins extends Feature {
return config.jenkins.active
}
+
@Override
void enable() {
@@ -85,7 +86,6 @@ class Jenkins extends Feature {
def nodeName = k8sClient.waitForNode().replace('node/', '')
k8sClient.label('node', nodeName, new Tuple2('node', 'jenkins'))
-
k8sClient.createSecret('generic', 'jenkins-credentials', namespace,
new Tuple2('jenkins-admin-user', config.jenkins.username),
new Tuple2('jenkins-admin-password', config.jenkins.password))
@@ -137,9 +137,9 @@ class Jenkins extends Feature {
JENKINS_PASSWORD : config.jenkins.password,
// Used indirectly in utils.sh 😬
REMOTE_CLUSTER : config.application.remote,
- SCMM_URL : config.scmm.urlForJenkins,
- SCMM_PASSWORD : config.scmm.password,
- SCM_PROVIDER : config.scmm.provider,
+ SCMM_URL : this.gitHandler.tenant.url,
+ SCMM_PASSWORD : this.gitHandler.tenant.credentials.password,
+ SCM_PROVIDER : config.scm.scmProviderType,
INSTALL_ARGOCD : config.features.argocd.active,
NAME_PREFIX : config.application.namePrefix,
INSECURE : config.application.insecure,
@@ -147,7 +147,7 @@ class Jenkins extends Feature {
SKIP_PLUGINS : config.jenkins.skipPlugins
])
- globalPropertyManager.setGlobalProperty("${config.application.namePrefixForEnvVars}SCMM_URL", config.scmm.urlForJenkins)
+ globalPropertyManager.setGlobalProperty("${config.application.namePrefixForEnvVars}SCM_URL", this.gitHandler.tenant.url)
if (config.jenkins.additionalEnvs) {
for (entry in (config.jenkins.additionalEnvs as Map).entrySet()) {
@@ -186,40 +186,34 @@ class Jenkins extends Feature {
prometheusConfigurator.enableAuthentication()
}
- if (config.content.examples) {
-
- String jobName = "example-apps"
- String namespace = "argocd"
- createJenkinsjob(namespace, jobName)
-
- }
-
}
void createJenkinsjob(String namespace, String repoName) {
- def credentialId = "scmm-user"
+ def credentialId = "scm-user"
String prefixedNamespace = "${config.application.namePrefix}${namespace}"
String jobName = "${config.application.namePrefix}${repoName}"
+
jobManager.createJob(jobName,
- config.scmm.urlForJenkins,
+ this.gitHandler.tenant.url,
prefixedNamespace,
credentialId)
- if (config.scmm.provider == 'scm-manager') {
+
+ if (config.scm.scmProviderType == ScmProviderType.SCM_MANAGER) {
jobManager.createCredential(
jobName,
credentialId,
"${config.application.namePrefix}gitops",
- "${config.scmm.password}",
+ "${config.scm.getScmManager().password}",
'credentials for accessing scm-manager')
}
- if (config.scmm.provider == 'gitlab') {
+ if (config.scm.scmProviderType == ScmProviderType.GITLAB) {
jobManager.createCredential(
jobName,
credentialId,
- "${config.scmm.username}",
- "${config.scmm.password}",
+ "${config.scm.getGitlab().username}",
+ "${config.scm.getGitlab().password}",
'credentials for accessing gitlab')
}
diff --git a/src/main/groovy/com/cloudogu/gitops/features/Mailhog.groovy b/src/main/groovy/com/cloudogu/gitops/features/Mailhog.groovy
index a6681e122..80ec1c0b3 100644
--- a/src/main/groovy/com/cloudogu/gitops/features/Mailhog.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/features/Mailhog.groovy
@@ -3,9 +3,8 @@ package com.cloudogu.gitops.features
import com.cloudogu.gitops.Feature
import com.cloudogu.gitops.FeatureWithImage
import com.cloudogu.gitops.config.Config
-
import com.cloudogu.gitops.features.deployment.DeploymentStrategy
-import com.cloudogu.gitops.scmm.ScmUrlResolver
+import com.cloudogu.gitops.features.git.GitHandler
import com.cloudogu.gitops.utils.AirGappedUtils
import com.cloudogu.gitops.utils.FileSystemUtils
import com.cloudogu.gitops.utils.K8sClient
@@ -29,19 +28,21 @@ class Mailhog extends Feature implements FeatureWithImage {
String namespace = "${config.application.namePrefix}monitoring"
Config config
K8sClient k8sClient
-
+
private String username
private String password
private FileSystemUtils fileSystemUtils
private DeploymentStrategy deployer
private AirGappedUtils airGappedUtils
+ private GitHandler gitHandler
Mailhog(
Config config,
FileSystemUtils fileSystemUtils,
DeploymentStrategy deployer,
K8sClient k8sClient,
- AirGappedUtils airGappedUtils
+ AirGappedUtils airGappedUtils,
+ GitHandler gitHandler
) {
this.deployer = deployer
this.config = config
@@ -49,6 +50,7 @@ class Mailhog extends Feature implements FeatureWithImage {
this.k8sClient = k8sClient
this.fileSystemUtils = fileSystemUtils
this.airGappedUtils = airGappedUtils
+ this.gitHandler = gitHandler
}
@@ -64,12 +66,12 @@ class Mailhog extends Feature implements FeatureWithImage {
def templatedMap = templateToMap(HELM_VALUES_PATH, [
mail : [
// Note that passing the URL object here leads to problems in Graal Native image, see Git history
- host: config.features.mail.mailhogUrl ? new URL(config.features.mail.mailhogUrl ).host : "",
+ host: config.features.mail.mailhogUrl ? new URL(config.features.mail.mailhogUrl).host : "",
],
passwordCrypt: bcryptMailhogPassword,
- config : config,
+ config : config,
// Allow for using static classes inside the templates
- statics: new DefaultObjectWrapperBuilder(freemarker.template.Configuration.VERSION_2_3_32).build().getStaticModels()
+ statics : new DefaultObjectWrapperBuilder(freemarker.template.Configuration.VERSION_2_3_32).build().getStaticModels()
])
def helmConfig = config.features.mail.helm
@@ -88,7 +90,7 @@ class Mailhog extends Feature implements FeatureWithImage {
'Chart.yaml'))['version']
deployer.deployFeature(
- ScmUrlResolver.scmmRepoUrl(config, repoNamespaceAndName),
+ "${this.gitHandler.resourcesScm.repoUrl(repoNamespaceAndName)}",
'mailhog',
'.',
mailhogVersion,
@@ -97,10 +99,10 @@ class Mailhog extends Feature implements FeatureWithImage {
tempValuesPath, DeploymentStrategy.RepoType.GIT)
} else {
deployer.deployFeature(
- helmConfig.repoURL ,
+ helmConfig.repoURL,
'mailhog',
- helmConfig.chart ,
- helmConfig.version ,
+ helmConfig.chart,
+ helmConfig.version,
namespace,
'mailhog',
tempValuesPath)
diff --git a/src/main/groovy/com/cloudogu/gitops/features/PrometheusStack.groovy b/src/main/groovy/com/cloudogu/gitops/features/PrometheusStack.groovy
index 2cf7e2d20..bccf68b6c 100644
--- a/src/main/groovy/com/cloudogu/gitops/features/PrometheusStack.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/features/PrometheusStack.groovy
@@ -4,10 +4,10 @@ import com.cloudogu.gitops.Feature
import com.cloudogu.gitops.FeatureWithImage
import com.cloudogu.gitops.config.Config
import com.cloudogu.gitops.features.deployment.DeploymentStrategy
-import com.cloudogu.gitops.scmm.ScmmRepo
-import com.cloudogu.gitops.scmm.ScmmRepoProvider
+import com.cloudogu.gitops.features.git.GitHandler
+import com.cloudogu.gitops.git.GitRepo
+import com.cloudogu.gitops.git.GitRepoFactory
import com.cloudogu.gitops.utils.*
-import com.cloudogu.gitops.scmm.ScmUrlResolver
import freemarker.template.DefaultObjectWrapperBuilder
import groovy.util.logging.Slf4j
import groovy.yaml.YamlSlurper
@@ -31,10 +31,11 @@ class PrometheusStack extends Feature implements FeatureWithImage {
Config config
K8sClient k8sClient
- ScmmRepoProvider scmmRepoProvider
+ GitRepoFactory scmRepoProvider
private FileSystemUtils fileSystemUtils
private DeploymentStrategy deployer
private AirGappedUtils airGappedUtils
+ private GitHandler gitHandler
PrometheusStack(
Config config,
@@ -42,14 +43,16 @@ class PrometheusStack extends Feature implements FeatureWithImage {
DeploymentStrategy deployer,
K8sClient k8sClient,
AirGappedUtils airGappedUtils,
- ScmmRepoProvider scmmRepoProvider
+ GitRepoFactory scmRepoProvider,
+ GitHandler gitHandler
) {
this.deployer = deployer
this.config = config
this.fileSystemUtils = fileSystemUtils
this.k8sClient = k8sClient
this.airGappedUtils = airGappedUtils
- this.scmmRepoProvider = scmmRepoProvider
+ this.scmRepoProvider = scmRepoProvider
+ this.gitHandler = gitHandler
}
@Override
@@ -68,7 +71,7 @@ class PrometheusStack extends Feature implements FeatureWithImage {
Map templateModel = buildTemplateValues(config, uid)
- def values = templateToMap(HELM_VALUES_PATH, templateModel)
+ def values = templateToMap(HELM_VALUES_PATH, templateModel)
def helmConfig = config.features.monitoring.helm
def mergedMap = MapUtils.deepMerge(helmConfig.values, values)
@@ -99,7 +102,7 @@ class PrometheusStack extends Feature implements FeatureWithImage {
}
if (config.application.namespaceIsolation || config.application.netpols) {
- ScmmRepo clusterResourcesRepo = scmmRepoProvider.getRepo('argocd/cluster-resources', config.multiTenant.useDedicatedInstance)
+ GitRepo clusterResourcesRepo = scmRepoProvider.getRepo('argocd/cluster-resources', this.gitHandler.resourcesScm)
clusterResourcesRepo.cloneRepo()
for (String currentNamespace : config.application.namespaces.activeNamespaces) {
@@ -134,7 +137,7 @@ class PrometheusStack extends Feature implements FeatureWithImage {
'Chart.yaml'))['version']
deployer.deployFeature(
- ScmUrlResolver.scmmRepoUrl(config, repoNamespaceAndName),
+ this.gitHandler.resourcesScm.repoUrl(repoNamespaceAndName),
'prometheusstack',
'.',
prometheusVersion,
@@ -154,27 +157,27 @@ class PrometheusStack extends Feature implements FeatureWithImage {
}
}
- private Map buildTemplateValues(Config config, String uid){
+ private Map buildTemplateValues(Config config, String uid) {
def model = [
monitoring: [grafana: [host: config.features.monitoring.grafanaUrl ? new URL(config.features.monitoring.grafanaUrl).host : ""]],
namespaces: (config.application.namespaces.activeNamespaces ?: []) as LinkedHashSet,
- scmm : scmConfigurationMetrics(),
+ scm : scmConfigurationMetrics(),
jenkins : jenkinsConfigurationMetrics(),
uid : uid,
config : config,
// Allow for using static classes inside the templates
- statics : new DefaultObjectWrapperBuilder(freemarker.template.Configuration.VERSION_2_3_32).build().getStaticModels()
+ statics : new DefaultObjectWrapperBuilder(freemarker.template.Configuration.VERSION_2_3_32).build().getStaticModels()
] as Map
return model
}
private Map scmConfigurationMetrics() {
- def uri = ScmUrlResolver.scmmBaseUri(config).resolve("api/v2/metrics/prometheus")
+ def uri = this.gitHandler.resourcesScm.prometheusMetricsEndpoint()
[
- protocol: uri.scheme ?: "",
- host : uri.authority ?: "",
- path : uri.path ?: ""
+ protocol: uri?.scheme ?: "",
+ host : uri?.authority ?: "",
+ path : uri?.path ?: ""
]
}
diff --git a/src/main/groovy/com/cloudogu/gitops/features/Registry.groovy b/src/main/groovy/com/cloudogu/gitops/features/Registry.groovy
index 85ccfac1f..80af58847 100644
--- a/src/main/groovy/com/cloudogu/gitops/features/Registry.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/features/Registry.groovy
@@ -15,7 +15,7 @@ import java.nio.file.Path
@Slf4j
@Singleton
-@Order(50)
+@Order(40)
class Registry extends Feature {
/**
diff --git a/src/main/groovy/com/cloudogu/gitops/features/ScmManager.groovy b/src/main/groovy/com/cloudogu/gitops/features/ScmManager.groovy
deleted file mode 100644
index 66e52c8a5..000000000
--- a/src/main/groovy/com/cloudogu/gitops/features/ScmManager.groovy
+++ /dev/null
@@ -1,303 +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.utils.*
-import groovy.util.logging.Slf4j
-import io.micronaut.core.annotation.Order
-import jakarta.inject.Singleton
-import org.gitlab4j.api.GitLabApi
-import org.gitlab4j.api.models.Group
-import org.gitlab4j.api.models.Project
-
-import java.util.function.Supplier
-import java.util.logging.Level
-
-@Slf4j
-@Singleton
-@Order(60)
-class ScmManager 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 GitLabApi gitlabApi
- private K8sClient k8sClient
- private NetworkingUtils networkingUtils
- String centralSCMUrl
-
- ScmManager(
- 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.gitlabApi = new GitLabApi(config.scmm.url, config.scmm.password)
- this.gitlabApi.enableRequestResponseLogging(Level.ALL)
- this.k8sClient = k8sClient
- this.networkingUtils = networkingUtils
- this.centralSCMUrl = config.multiTenant.centralScmUrl
-
- if(config.scmm.internal) {
- this.namespace = "${config.application.namePrefix}scm-manager"
- }
- }
-
- @Override
- boolean isEnabled() {
- return true // For now, we either deploy an internal or configure an external instance
- }
-
- @Override
- void enable() {
- if (config.multiTenant.useDedicatedInstance) {
- this.centralSCMUrl = !config.multiTenant.internal ? config.multiTenant.centralScmUrl : "http://scmm.scm-manager.svc.cluster.local/scm"
- }
-
- if (config.scmm.internal) {
- String releaseName = 'scmm'
-
- k8sClient.createNamespace(namespace)
-
- def helmConfig = config.scmm.helm
-
- def templatedMap = templateToMap(HELM_VALUES_PATH, [
- host : config.scmm.ingress,
- remote : config.application.remote,
- username : config.scmm.username,
- password : config.scmm.password,
- helm : config.scmm.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.scmm.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.scmm.url = networkingUtils.createUrl(clusterBindAddress, port, contentPath)
-
- if (config.multiTenant.useDedicatedInstance && config.multiTenant.internal) {
- log.debug("Setting internal configs for local single node cluster with internal central scmm. Waiting for NodePort...")
- def portCentralScm = k8sClient.waitForNodePort(releaseName, "scm-manager")
- centralSCMUrl = networkingUtils.createUrl(clusterBindAddress, portCentralScm, contentPath)
- }
- }
- }
-
- // NOTE: This code is experimental and not intended for production use.
- // Please use with caution and ensure proper testing before deployment.
-
- if (config.scmm.provider == "gitlab") {
- configureGitlab()
- }
-
- 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.scmm.gitOpsUsername,
- TRACE : config.application.trace,
- SCMM_URL : config.scmm.url,
- SCMM_USERNAME : config.scmm.username,
- SCMM_PASSWORD : config.scmm.password,
- JENKINS_URL : config.jenkins.url,
- INTERNAL_SCMM : config.scmm.internal,
- JENKINS_URL_FOR_SCMM : config.jenkins.urlForScmm,
- SCMM_URL_FOR_JENKINS : config.scmm.urlForJenkins,
- // Used indirectly in utils.sh 😬
- REMOTE_CLUSTER : config.application.remote,
- INSTALL_ARGOCD : config.features.argocd.active,
- SPRING_BOOT_HELM_CHART_COMMIT: config.repositories.springBootHelmChart.ref,
- SPRING_BOOT_HELM_CHART_REPO : config.repositories.springBootHelmChart.url,
- GITOPS_BUILD_LIB_REPO : config.repositories.gitopsBuildLib.url,
- CES_BUILD_LIB_REPO : config.repositories.cesBuildLib.url,
- NAME_PREFIX : config.application.namePrefix,
- INSECURE : config.application.insecure,
- SCM_ROOT_PATH : config.scmm.rootPath,
- SCM_PROVIDER : config.scmm.provider,
- CONTENT_EXAMPLES : config.content.examples,
- SKIP_RESTART : config.scmm.skipRestart,
- SKIP_PLUGINS : config.scmm.skipPlugins,
- CENTRAL_SCM_URL : centralSCMUrl,
- CENTRAL_SCM_USERNAME : config.multiTenant.username,
- CENTRAL_SCM_PASSWORD : config.multiTenant.password
- ])
- }
-
- void configureGitlab() {
- log.info("Gitlab init")
-
- createGroups()
- }
-
-
- void createGroups() {
- log.info("Creating Gitlab Groups")
- def mainGroupName = "${config.application.namePrefix}scm".toString()
- Group mainSCMGroup = this.gitlabApi.groupApi.getGroup(mainGroupName)
- if (!mainSCMGroup) {
- def tempGroup = new Group()
- .withName(mainGroupName)
- .withPath(mainGroupName.toLowerCase())
- .withParentId(null)
-
- mainSCMGroup = this.gitlabApi.groupApi.addGroup(tempGroup)
- }
-
-
- String argoCDGroupName = 'argocd'
- Optional argoCDGroup = getGroup("${mainGroupName}/${argoCDGroupName}")
- if (argoCDGroup.isEmpty()) {
- def tempGroup = new Group()
- .withName(argoCDGroupName)
- .withPath(argoCDGroupName.toLowerCase())
- .withParentId(mainSCMGroup.id)
-
- argoCDGroup = addGroup(tempGroup)
- }
-
- argoCDGroup.ifPresent(this.&createArgoCDRepos)
-
- String dependencysGroupName = '3rd-party-dependencies'
- Optional dependencysGroup = getGroup("${mainGroupName}/${dependencysGroupName}")
- if (dependencysGroup.isEmpty()) {
- def tempGroup = new Group()
- .withName(dependencysGroupName)
- .withPath(dependencysGroupName.toLowerCase())
- .withParentId(mainSCMGroup.id)
-
- addGroup(tempGroup)
- }
-
- String exercisesGroupName = 'exercises'
- Optional exercisesGroup = getGroup("${mainGroupName}/${exercisesGroupName}")
- if (exercisesGroup.isEmpty()) {
- def tempGroup = new Group()
- .withName(exercisesGroupName)
- .withPath(exercisesGroupName.toLowerCase())
- .withParentId(mainSCMGroup.id)
-
- exercisesGroup = addGroup(tempGroup)
- }
-
- exercisesGroup.ifPresent(this.&createExercisesRepos)
- }
-
- void createExercisesRepos(Group exercisesGroup) {
- log.info("Creating GitlabRepos for ${exercisesGroup}")
- createRepo("petclinic-helm", "petclinic-helm", exercisesGroup)
- createRepo("nginx-validation", "nginx-validation", exercisesGroup)
- createRepo("broken-application", "broken-application", exercisesGroup)
- }
-
- void createArgoCDRepos(Group argoCDGroup) {
- log.info("Creating GitlabRepos for ${argoCDGroup}")
- createRepo("cluster-resources", "GitOps repo for basic cluster-resources", argoCDGroup)
- createRepo("petclinic-helm", "Java app with custom helm chart", argoCDGroup)
- createRepo("petclinic-plain", "Java app with plain k8s resources", argoCDGroup)
- createRepo("nginx-helm-jenkins", "3rd Party app (NGINX) with helm, templated in Jenkins (gitops-build-lib)", argoCDGroup)
- createRepo("argocd", "GitOps repo for administration of ArgoCD", argoCDGroup)
- createRepo("example-apps", "GitOps repo for examples of end-user applications", argoCDGroup)
-
- }
-
-
- void removeBranchProtection(Project project) {
- try {
- this.gitlabApi.getProtectedBranchesApi().unprotectBranch(project.getId(), project.getDefaultBranch())
- log.info("Unprotected default branch: " + project.getDefaultBranch())
- } catch (Exception ex) {
- log.error("Failed Unprotecting branch for repo ${project}")
- }
- }
-
-
- void createRepo(String name, String description, Group parentGroup) {
-
- Optional project = getProject("${parentGroup.getFullPath()}/${name}".toString())
- if (project.isEmpty()) {
- Project projectSpec = new Project()
- .withName(name)
- .withDescription(description)
- .withIssuesEnabled(true)
- .withMergeRequestsEnabled(true)
- .withWikiEnabled(true)
- .withSnippetsEnabled(true)
- .withPublic(false)
- .withNamespaceId(parentGroup.getId())
- .withInitializeWithReadme(true)
-
- log.info("Project ${projectSpec} created!")
- project = Optional.ofNullable(this.gitlabApi.projectApi.createProject(projectSpec))
- }
- removeBranchProtection(project.get())
- }
-
- //to bundle the 3 functions down below
- private Optional executeGitlabApiCall(Supplier apiCall) {
- try {
- return Optional.ofNullable(apiCall.get())
- } catch (Exception e) {
- return Optional.empty()
- }
- }
-
- private Optional getGroup(String groupName) {
- try {
- return Optional.ofNullable(this.gitlabApi.groupApi.getGroup(groupName))
- } catch (Exception e) {
- return Optional.empty()
- }
- }
-
- private Optional addGroup(Group group) {
- try {
- return Optional.ofNullable(this.gitlabApi.groupApi.addGroup(group))
- } catch (Exception e) {
- return Optional.empty()
- }
- }
-
- private Optional getProject(String projectPath) {
- try {
- return Optional.ofNullable(this.gitlabApi.projectApi.getProject(projectPath))
- } catch (Exception e) {
- return Optional.empty()
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/features/ScmManagerSetup.groovy b/src/main/groovy/com/cloudogu/gitops/features/ScmManagerSetup.groovy
new file mode 100644
index 000000000..e0772da95
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/features/ScmManagerSetup.groovy
@@ -0,0 +1,143 @@
+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
+ }
+
+ @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,
+ SPRING_BOOT_HELM_CHART_COMMIT: config.repositories.springBootHelmChart.ref,
+ SPRING_BOOT_HELM_CHART_REPO : config.repositories.springBootHelmChart.url,
+ GITOPS_BUILD_LIB_REPO : config.repositories.gitopsBuildLib.url,
+ CES_BUILD_LIB_REPO : config.repositories.cesBuildLib.url,
+ 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/Vault.groovy b/src/main/groovy/com/cloudogu/gitops/features/Vault.groovy
index e7ccd9254..a55b782cc 100644
--- a/src/main/groovy/com/cloudogu/gitops/features/Vault.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/features/Vault.groovy
@@ -3,9 +3,8 @@ package com.cloudogu.gitops.features
import com.cloudogu.gitops.Feature
import com.cloudogu.gitops.FeatureWithImage
import com.cloudogu.gitops.config.Config
-
import com.cloudogu.gitops.features.deployment.DeploymentStrategy
-import com.cloudogu.gitops.scmm.ScmUrlResolver
+import com.cloudogu.gitops.features.git.GitHandler
import com.cloudogu.gitops.utils.*
import freemarker.template.DefaultObjectWrapperBuilder
import groovy.util.logging.Slf4j
@@ -22,26 +21,29 @@ class Vault extends Feature implements FeatureWithImage {
static final String VAULT_START_SCRIPT_PATH = '/applications/cluster-resources/secrets/vault/dev-post-start.ftl.sh'
static final String HELM_VALUES_PATH = 'applications/cluster-resources/secrets/vault/values.ftl.yaml'
- String namespace = "${config.application.namePrefix}secrets"
+ String namespace = "${config.application.namePrefix}secrets"
Config config
K8sClient k8sClient
private FileSystemUtils fileSystemUtils
private DeploymentStrategy deployer
private AirGappedUtils airGappedUtils
+ private GitHandler gitHandler
Vault(
Config config,
FileSystemUtils fileSystemUtils,
K8sClient k8sClient,
DeploymentStrategy deployer,
- AirGappedUtils airGappedUtils
+ AirGappedUtils airGappedUtils,
+ GitHandler gitHandler
) {
this.deployer = deployer
this.config = config
this.fileSystemUtils = fileSystemUtils
this.k8sClient = k8sClient
this.airGappedUtils = airGappedUtils
+ this.gitHandler = gitHandler
}
@Override
@@ -133,7 +135,7 @@ class Vault extends Feature implements FeatureWithImage {
'Chart.yaml'))['version']
deployer.deployFeature(
- ScmUrlResolver.scmmRepoUrl(config, repoNamespaceAndName),
+ this.gitHandler.resourcesScm.repoUrl(repoNamespaceAndName),
'vault',
'.',
vaultVersion,
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 826bafb6d..243ecf2d6 100644
--- a/src/main/groovy/com/cloudogu/gitops/features/argocd/ArgoCD.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/features/argocd/ArgoCD.groovy
@@ -2,20 +2,17 @@ package com.cloudogu.gitops.features.argocd
import com.cloudogu.gitops.Feature
import com.cloudogu.gitops.config.Config
-import com.cloudogu.gitops.kubernetes.argocd.ArgoApplication
+import com.cloudogu.gitops.features.git.GitHandler
+import com.cloudogu.gitops.git.GitRepoFactory
+import com.cloudogu.gitops.git.providers.GitProvider
import com.cloudogu.gitops.kubernetes.rbac.RbacDefinition
import com.cloudogu.gitops.kubernetes.rbac.Role
-import com.cloudogu.gitops.scmm.ScmUrlResolver
-import com.cloudogu.gitops.scmm.ScmmRepoProvider
import com.cloudogu.gitops.utils.FileSystemUtils
import com.cloudogu.gitops.utils.HelmClient
import com.cloudogu.gitops.utils.K8sClient
-import com.cloudogu.gitops.utils.TemplatingEngine
import groovy.util.logging.Slf4j
import io.micronaut.core.annotation.Order
import jakarta.inject.Singleton
-import org.eclipse.jgit.api.CloneCommand
-import org.eclipse.jgit.api.Git
import org.springframework.security.crypto.bcrypt.BCrypt
import java.nio.file.Path
@@ -41,33 +38,31 @@ class ArgoCD extends Feature {
protected RepoInitializationAction argocdRepoInitializationAction
protected RepoInitializationAction clusterResourcesInitializationAction
- protected RepoInitializationAction exampleAppsInitializationAction
- protected RepoInitializationAction nginxHelmJenkinsInitializationAction
- protected RepoInitializationAction nginxValidationInitializationAction
- protected RepoInitializationAction brokenApplicationInitializationAction
protected RepoInitializationAction tenantBootstrapInitializationAction
protected File remotePetClinicRepoTmpDir
- protected List petClinicInitializationActions = []
protected K8sClient k8sClient
protected HelmClient helmClient
protected FileSystemUtils fileSystemUtils
- private ScmmRepoProvider repoProvider
+ private GitRepoFactory repoProvider
+
+ GitHandler gitHandler
ArgoCD(
Config config,
K8sClient k8sClient,
HelmClient helmClient,
FileSystemUtils fileSystemUtils,
- ScmmRepoProvider repoProvider
+ GitRepoFactory repoProvider,
+ GitHandler gitHandler
) {
this.repoProvider = repoProvider
this.config = config
this.k8sClient = k8sClient
this.helmClient = helmClient
this.fileSystemUtils = fileSystemUtils
-
+ this.gitHandler = gitHandler
this.password = this.config.application.password
}
@@ -78,25 +73,11 @@ class ArgoCD extends Feature {
@Override
void enable() {
- initRepos()
-
- log.debug('Cloning Repositories')
- if (config.content.examples) {
- def petclinicInitAction = createRepoInitializationAction('applications/argocd/petclinic/plain-k8s', 'argocd/petclinic-plain')
- petClinicInitializationActions += petclinicInitAction
- gitRepos += petclinicInitAction
-
- petclinicInitAction = createRepoInitializationAction('applications/argocd/petclinic/helm', 'argocd/petclinic-helm')
- petClinicInitializationActions += petclinicInitAction
- gitRepos += petclinicInitAction
-
- petclinicInitAction = createRepoInitializationAction('exercises/petclinic-helm', 'exercises/petclinic-helm')
- petClinicInitializationActions += petclinicInitAction
- gitRepos += petclinicInitAction
-
- cloneRemotePetclinicRepo()
- }
+ initTenantRepos()
+ initCentralRepos()
+
+ log.debug('Cloning Repositories')
gitRepos.forEach(repoInitializationAction -> {
repoInitializationAction.initLocalRepo()
@@ -104,11 +85,6 @@ class ArgoCD extends Feature {
prepareGitOpsRepos()
- if (config.content.examples) {
- prepareApplicationNginxHelmJenkins()
- preparePetClinicRepos()
- }
-
gitRepos.forEach(repoInitializationAction -> {
repoInitializationAction.repo.commitAndPush('Initial Commit')
})
@@ -164,50 +140,25 @@ class ArgoCD extends Feature {
new Tuple2('owner', 'helm'), new Tuple2('name', 'argocd'))
}
- protected initRepos() {
- argocdRepoInitializationAction = createRepoInitializationAction('argocd/argocd', 'argocd/argocd', config.multiTenant.useDedicatedInstance)
-
- clusterResourcesInitializationAction = createRepoInitializationAction('argocd/cluster-resources', 'argocd/cluster-resources', config.multiTenant.useDedicatedInstance)
- gitRepos += clusterResourcesInitializationAction
+ protected initTenantRepos() {
+ if (!config.multiTenant.useDedicatedInstance) {
+ argocdRepoInitializationAction = createRepoInitializationAction('argocd/argocd', 'argocd/argocd', this.gitHandler.tenant)
- if (config.multiTenant.useDedicatedInstance) {
- tenantBootstrapInitializationAction = createRepoInitializationAction('argocd/argocd/multiTenant/tenant', 'argocd/argocd')
+ clusterResourcesInitializationAction = createRepoInitializationAction('argocd/cluster-resources', 'argocd/cluster-resources', this.gitHandler.tenant)
+ gitRepos += clusterResourcesInitializationAction
+ } else {
+ tenantBootstrapInitializationAction = createRepoInitializationAction('argocd/argocd/multiTenant/tenant', 'argocd/argocd', this.gitHandler.tenant)
gitRepos += tenantBootstrapInitializationAction
}
-
- if (config.content.examples) {
- exampleAppsInitializationAction = createRepoInitializationAction('argocd/example-apps', 'argocd/example-apps')
- gitRepos += exampleAppsInitializationAction
-
- nginxHelmJenkinsInitializationAction = createRepoInitializationAction('applications/argocd/nginx/helm-jenkins', 'argocd/nginx-helm-jenkins')
- gitRepos += nginxHelmJenkinsInitializationAction
-
- nginxValidationInitializationAction = createRepoInitializationAction('exercises/nginx-validation', 'exercises/nginx-validation')
- gitRepos += nginxValidationInitializationAction
-
- brokenApplicationInitializationAction = createRepoInitializationAction('exercises/broken-application', 'exercises/broken-application')
- gitRepos += brokenApplicationInitializationAction
-
- remotePetClinicRepoTmpDir = File.createTempDir('gitops-playground-petclinic')
- }
}
- private void cloneRemotePetclinicRepo() {
- log.debug("Cloning petclinic base repo, revision ${config.repositories.springPetclinic.ref}," +
- " from ${config.repositories.springPetclinic.url}")
- Git git = gitClone()
- .setURI(config.repositories.springPetclinic.url)
- .setDirectory(remotePetClinicRepoTmpDir)
- .call()
- git.checkout().setName(config.repositories.springPetclinic.ref).call()
- log.debug('Finished cloning petclinic base repo')
- }
+ protected initCentralRepos() {
+ if (config.multiTenant.useDedicatedInstance) {
+ argocdRepoInitializationAction = createRepoInitializationAction('argocd/argocd', 'argocd/argocd', true)
- /**
- * Overwrite for testing purposes
- */
- protected CloneCommand gitClone() {
- Git.cloneRepository()
+ clusterResourcesInitializationAction = createRepoInitializationAction('argocd/cluster-resources', 'argocd/cluster-resources', true)
+ gitRepos += clusterResourcesInitializationAction
+ }
}
private void prepareGitOpsRepos() {
@@ -225,57 +176,17 @@ class ArgoCD extends Feature {
FileSystemUtils.deleteFile clusterResourcesInitializationAction.repo.getAbsoluteLocalRepoTmpDir() + MONITORING_RESOURCES_PATH + 'ingress-nginx-dashboard-requests-handling.yaml'
}
- if (!config.scmm.internal) {
- String externalScmmUrl = ScmUrlResolver.externalHost(config)
- log.debug("Configuring all yaml files in gitops repos to use the external scmm url: ${externalScmmUrl}")
- replaceFileContentInYamls(new File(clusterResourcesInitializationAction.repo.getAbsoluteLocalRepoTmpDir()), scmmUrlInternal, externalScmmUrl)
-
+ //TODO Anna do we need this? Or just pass the correct URL directly?
+ /*if (!config.scm.isInternal) {
+ String externalScmUrl = ScmmRepo.createScmmUrl(config)
+ log.debug("Configuring all yaml files in gitops repos to use the external scm url: ${externalScmUrl}")
+ replaceFileContentInYamls(new File(clusterResourcesInitializationAction.repo.getAbsoluteLocalRepoTmpDir()), scmmUrlInternal, externalScmUrl)
+
if (config.content.examples) {
- replaceFileContentInYamls(new File(exampleAppsInitializationAction.repo.getAbsoluteLocalRepoTmpDir()), scmmUrlInternal, externalScmmUrl)
+ replaceFileContentInYamls(new File(exampleAppsInitializationAction.repo.getAbsoluteLocalRepoTmpDir()), scmmUrlInternal, externalScmUrl)
}
- }
+ } */
- if (config.content.examples) {
- fileSystemUtils.copyDirectory("${fileSystemUtils.rootDir}/applications/argocd/nginx/helm-umbrella",
- Path.of(exampleAppsInitializationAction.repo.getAbsoluteLocalRepoTmpDir(), 'apps/nginx-helm-umbrella/').toString())
- exampleAppsInitializationAction.replaceTemplates()
-
- //generating the bootstrap application in a app of app pattern for example apps in the /argocd applications folder
- if (config.multiTenant.useDedicatedInstance) {
- new ArgoApplication(
- 'example-apps',
- ScmUrlResolver.tenantBaseUrl(config)+'argocd/example-apps',
- namespace,
- namespace,
- 'argocd/',
- config.application.getTenantName())
- .generate(tenantBootstrapInitializationAction.repo, 'applications')
- }
- }
- }
-
- private void prepareApplicationNginxHelmJenkins() {
- if (!config.features.secrets.active) {
- // External Secrets are not needed in example
- FileSystemUtils.deleteFile nginxHelmJenkinsInitializationAction.repo.getAbsoluteLocalRepoTmpDir() + '/k8s/staging/external-secret.yaml'
- FileSystemUtils.deleteFile nginxHelmJenkinsInitializationAction.repo.getAbsoluteLocalRepoTmpDir() + '/k8s/production/external-secret.yaml'
- }
- }
-
- private void preparePetClinicRepos() {
- for (def repoInitAction : petClinicInitializationActions) {
- def tmpDir = repoInitAction.repo.getAbsoluteLocalRepoTmpDir()
-
- log.debug("Copying original petclinic files for petclinic repo: $tmpDir")
- fileSystemUtils.copyDirectory(remotePetClinicRepoTmpDir.toString(), tmpDir, new FileSystemUtils.IgnoreDotGitFolderFilter())
- fileSystemUtils.deleteEmptyFiles(Path.of(tmpDir), ~/k8s\/.*\.yaml/)
-
- new TemplatingEngine().template(
- new File("${fileSystemUtils.getRootDir()}/applications/argocd/petclinic/Dockerfile.ftl"),
- new File("${tmpDir}/Dockerfile"),
- [baseImage: config.images.petclinic as String]
- )
- }
}
private void deployWithHelm() {
@@ -434,33 +345,32 @@ class ArgoCD extends Feature {
protected void createSCMCredentialsSecret() {
- log.debug('Creating repo credential secret that is used by argocd to access repos in SCM-Manager')
+ log.debug("Creating repo credential secret that is used by argocd to access repos in ${config.scm.scmProviderType.toString()}")
// Create secret imperatively here instead of values.yaml, because we don't want it to show in git repo
- def repoTemplateSecretName = 'argocd-repo-creds-scmm'
+ def repoTemplateSecretName = 'argocd-repo-creds-scm'
- String scmmUrlForArgoCD = config.scmm.internal ? scmmUrlInternal : ScmUrlResolver.externalHost(config)
k8sClient.createSecret('generic', repoTemplateSecretName, namespace,
- new Tuple2('url', scmmUrlForArgoCD),
- new Tuple2('username', config.scmm.username),
- new Tuple2('password', config.scmm.password)
+ new Tuple2('url', this.gitHandler.tenant.url),
+ new Tuple2('username', this.gitHandler.tenant.credentials.username),
+ new Tuple2('password', this.gitHandler.tenant.credentials.password)
)
k8sClient.label('secret', repoTemplateSecretName, namespace,
- new Tuple2(' argocd.argoproj.io/secret-type', 'repo-creds'))
+ new Tuple2('argocd.argoproj.io/secret-type', 'repo-creds'))
if (config.multiTenant.useDedicatedInstance) {
- log.debug('Creating central repo credential secret that is used by argocd to access repos in SCM-Manager')
+ log.debug("Creating central repo credential secret that is used by argocd to access repos in ${config.scm.scmProviderType.toString()}")
// Create secret imperatively here instead of values.yaml, because we don't want it to show in git repo
- def centralRepoTemplateSecretName = 'argocd-repo-creds-central-scmm'
+ def centralRepoTemplateSecretName = 'argocd-repo-creds-central-scm'
k8sClient.createSecret('generic', centralRepoTemplateSecretName, config.multiTenant.centralArgocdNamespace,
- new Tuple2('url', config.multiTenant.centralScmUrl),
- new Tuple2('username', config.multiTenant.username),
- new Tuple2('password', config.multiTenant.password)
+ new Tuple2('url', this.gitHandler.central.url),
+ new Tuple2('username', this.gitHandler.central.credentials.username),
+ new Tuple2('password', this.gitHandler.central.credentials.password)
)
k8sClient.label('secret', centralRepoTemplateSecretName, config.multiTenant.centralArgocdNamespace,
- new Tuple2(' argocd.argoproj.io/secret-type', 'repo-creds'))
+ new Tuple2('argocd.argoproj.io/secret-type', 'repo-creds'))
}
}
@@ -488,34 +398,31 @@ class ArgoCD extends Feature {
FileSystemUtils.deleteDir argocdRepoInitializationAction.repo.getAbsoluteLocalRepoTmpDir() + '/multiTenant'
}
+ /* TODO do we need this?
if (!config.scmm.internal) {
String externalScmmUrl = ScmUrlResolver.externalHost(config)
log.debug("Configuring all yaml files in argocd repo to use the external scmm url: ${externalScmmUrl}")
replaceFileContentInYamls(new File(argocdRepoInitializationAction.repo.getAbsoluteLocalRepoTmpDir()), scmmUrlInternal, externalScmmUrl)
}
+ */
if (!config.application.netpols) {
log.debug("Deleting argocd netpols.")
FileSystemUtils.deleteFile argocdRepoInitializationAction.repo.getAbsoluteLocalRepoTmpDir() + '/argocd/templates/allow-namespaces.yaml'
}
- if (!config.content.examples) {
- FileSystemUtils.deleteFile argocdRepoInitializationAction.repo.getAbsoluteLocalRepoTmpDir() + '/applications/example-apps.yaml'
- FileSystemUtils.deleteFile argocdRepoInitializationAction.repo.getAbsoluteLocalRepoTmpDir() + '/projects/example-apps.yaml'
- }
-
argocdRepoInitializationAction.repo.commitAndPush("Initial Commit")
}
- protected RepoInitializationAction createRepoInitializationAction(String localSrcDir, String scmmRepoTarget) {
- new RepoInitializationAction(config, repoProvider.getRepo(scmmRepoTarget), localSrcDir)
+ protected RepoInitializationAction createRepoInitializationAction(String localSrcDir, String scmRepoTarget, Boolean isCentral) {
+ GitProvider provider = (Boolean.TRUE == isCentral) ? gitHandler.central : gitHandler.tenant
+ new RepoInitializationAction(config, repoProvider.getRepo(scmRepoTarget, provider), this.gitHandler, localSrcDir)
}
- protected RepoInitializationAction createRepoInitializationAction(String localSrcDir, String scmmRepoTarget, Boolean isCentralRepo) {
- new RepoInitializationAction(config, repoProvider.getRepo(scmmRepoTarget, isCentralRepo), localSrcDir)
+ protected RepoInitializationAction createRepoInitializationAction(String localSrcDir, String scmRepoTarget, GitProvider gitProvider) {
+ new RepoInitializationAction(config, repoProvider.getRepo(scmRepoTarget, gitProvider), this.gitHandler, localSrcDir)
}
-
private void replaceFileContentInYamls(File folder, String from, String to) {
fileSystemUtils.getAllFilesFromDirectoryWithEnding(folder.absolutePath, ".yaml").forEach(file -> {
fileSystemUtils.replaceFileContent(file.absolutePath, from, to)
diff --git a/src/main/groovy/com/cloudogu/gitops/features/argocd/RepoInitializationAction.groovy b/src/main/groovy/com/cloudogu/gitops/features/argocd/RepoInitializationAction.groovy
index 285f48d5e..f6eba87df 100644
--- a/src/main/groovy/com/cloudogu/gitops/features/argocd/RepoInitializationAction.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/features/argocd/RepoInitializationAction.groovy
@@ -1,19 +1,21 @@
package com.cloudogu.gitops.features.argocd
import com.cloudogu.gitops.config.Config
-import com.cloudogu.gitops.scmm.ScmUrlResolver
-import com.cloudogu.gitops.scmm.ScmmRepo
+import com.cloudogu.gitops.features.git.GitHandler
+import com.cloudogu.gitops.git.GitRepo
import freemarker.template.DefaultObjectWrapperBuilder
class RepoInitializationAction {
- private ScmmRepo repo
+ private GitRepo repo
private String copyFromDirectory
private Config config
+ private GitHandler gitHandler
- RepoInitializationAction(Config config, ScmmRepo repo, String copyFromDirectory) {
+ RepoInitializationAction(Config config, GitRepo repo,GitHandler gitHandler, String copyFromDirectory) {
this.config = config
this.repo = repo
this.copyFromDirectory = copyFromDirectory
+ this.gitHandler = gitHandler
}
/**
@@ -30,32 +32,27 @@ class RepoInitializationAction {
repo.replaceTemplates(templateModel)
}
- ScmmRepo getRepo() {
+ GitRepo getRepo() {
return repo
}
- private static Map buildTemplateValues(Config config){
+ private Map buildTemplateValues(Config config) {
def model = [
- tenantName: tenantName(config.application.namePrefix),
- argocd : [host: config.features.argocd.url ? new URL(config.features.argocd.url).host : ""],
- scmm : [
- baseUrl : config.scmm.internal ? "http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local/scm" : ScmUrlResolver.externalHost(config),
- host : config.scmm.internal ? "http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local" : config.scmm.host,
- protocol : config.scmm.internal ? 'http' : config.scmm.protocol,
- repoUrl : ScmUrlResolver.tenantBaseUrl(config),
- centralScmmUrl: !config.multiTenant.internal ? config.multiTenant.centralScmUrl : "http://scmm.scm-manager.svc.cluster.local/scm"
+ tenantName: config.application.tenantName,
+ argocd : [host: config.features.argocd.url ? new URL(config.features.argocd.url).host : ""], //TODO move this to argocd class and get the url from there
+ scm : [
+ baseUrl : this.repo.gitProvider.url,
+ host : this.repo.gitProvider.host,
+ protocol: this.repo.gitProvider.protocol,
+ repoUrl : this.repo.gitProvider.repoPrefix(),
+ centralScmUrl: this.gitHandler.central?.repoPrefix() ?: ''
],
config : config,
// Allow for using static classes inside the templates
- statics : new DefaultObjectWrapperBuilder(freemarker.template.Configuration.VERSION_2_3_32).build().getStaticModels()
+ statics : new DefaultObjectWrapperBuilder(freemarker.template.Configuration.VERSION_2_3_32).build().getStaticModels()
] as Map
return model
}
- private static String tenantName(String namePrefix) {
- if (!namePrefix) return ""
- return namePrefix.replaceAll(/-$/, "")
- }
-
}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/features/deployment/ArgoCdApplicationStrategy.groovy b/src/main/groovy/com/cloudogu/gitops/features/deployment/ArgoCdApplicationStrategy.groovy
index 5782fbdbf..cb3d3b2d5 100644
--- a/src/main/groovy/com/cloudogu/gitops/features/deployment/ArgoCdApplicationStrategy.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/features/deployment/ArgoCdApplicationStrategy.groovy
@@ -1,8 +1,9 @@
package com.cloudogu.gitops.features.deployment
import com.cloudogu.gitops.config.Config
-import com.cloudogu.gitops.scmm.ScmmRepo
-import com.cloudogu.gitops.scmm.ScmmRepoProvider
+import com.cloudogu.gitops.features.git.GitHandler
+import com.cloudogu.gitops.git.GitRepo
+import com.cloudogu.gitops.git.GitRepoFactory
import com.cloudogu.gitops.utils.FileSystemUtils
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper
@@ -16,16 +17,20 @@ import java.nio.file.Path
class ArgoCdApplicationStrategy implements DeploymentStrategy {
private FileSystemUtils fileSystemUtils
private Config config
- private final ScmmRepoProvider scmmRepoProvider
+ private final GitRepoFactory gitRepoProvider
+
+ private GitHandler gitHandler
ArgoCdApplicationStrategy(
Config config,
FileSystemUtils fileSystemUtils,
- ScmmRepoProvider scmmRepoProvider
+ GitRepoFactory gitRepoProvider,
+ GitHandler gitHandler
) {
- this.scmmRepoProvider = scmmRepoProvider
+ this.gitRepoProvider = gitRepoProvider
this.fileSystemUtils = fileSystemUtils
this.config = config
+ this.gitHandler = gitHandler
}
@Override
@@ -37,7 +42,7 @@ class ArgoCdApplicationStrategy implements DeploymentStrategy {
def namePrefix = config.application.namePrefix
def shallCreateNamespace = config.features['argocd']['operator'] ? "CreateNamespace=false" : "CreateNamespace=true"
- ScmmRepo clusterResourcesRepo = scmmRepoProvider.getRepo('argocd/cluster-resources', config.multiTenant.useDedicatedInstance)
+ GitRepo clusterResourcesRepo = gitRepoProvider.getRepo('argocd/cluster-resources', this.gitHandler.resourcesScm)
clusterResourcesRepo.cloneRepo()
// Inline values from tmpHelmValues file into ArgoCD Application YAML
@@ -72,10 +77,10 @@ class ArgoCdApplicationStrategy implements DeploymentStrategy {
],
project : project,
sources : [[
- repoURL : repoURL,
+ repoURL : repoURL,
"${chooseKeyChartOrPath(repoType)}": chartOrPath,
- targetRevision : version,
- helm : [
+ targetRevision : version,
+ helm : [
releaseName: releaseName,
values : inlineValues
]
diff --git a/src/main/groovy/com/cloudogu/gitops/features/git/GitHandler.groovy b/src/main/groovy/com/cloudogu/gitops/features/git/GitHandler.groovy
new file mode 100644
index 000000000..0cd89931d
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/features/git/GitHandler.groovy
@@ -0,0 +1,152 @@
+package com.cloudogu.gitops.features.git
+
+import com.cloudogu.gitops.Feature
+import com.cloudogu.gitops.config.Config
+import com.cloudogu.gitops.features.deployment.HelmStrategy
+import com.cloudogu.gitops.features.git.config.util.ScmProviderType
+import com.cloudogu.gitops.git.providers.GitProvider
+import com.cloudogu.gitops.git.providers.gitlab.Gitlab
+import com.cloudogu.gitops.git.providers.scmmanager.ScmManager
+import com.cloudogu.gitops.utils.FileSystemUtils
+import com.cloudogu.gitops.utils.K8sClient
+import com.cloudogu.gitops.utils.NetworkingUtils
+import groovy.util.logging.Slf4j
+import io.micronaut.core.annotation.Order
+import jakarta.inject.Singleton
+
+@Slf4j
+@Singleton
+@Order(60)
+class GitHandler extends Feature {
+
+ Config config
+
+ NetworkingUtils networkingUtils
+ HelmStrategy helmStrategy
+ FileSystemUtils fileSystemUtils
+ K8sClient k8sClient
+
+ GitProvider tenant
+ GitProvider central
+
+
+ GitHandler(Config config, HelmStrategy helmStrategy, FileSystemUtils fileSystemUtils, K8sClient k8sClient, NetworkingUtils networkingUtils) {
+ this.config = config
+ this.helmStrategy = helmStrategy
+ this.fileSystemUtils = fileSystemUtils
+ this.k8sClient = k8sClient
+ this.networkingUtils = networkingUtils
+ }
+
+ @Override
+ boolean isEnabled() {
+ return true
+ }
+
+ void validate() {
+ if (config.scm.scmManager.url) {
+ config.scm.scmManager.internal = false
+ config.scm.scmManager.urlForJenkins = config.scm.scmManager.url
+ } else {
+ log.debug("Setting configs for internal SCM-Manager")
+ // We use the K8s service as default name here, because it is the only option:
+ // "scmm.localhost" will not work inside the Pods and k3d-container IP + Port (e.g. 172.x.y.z:9091)
+ // will not work on Windows and MacOS.
+ config.scm.scmManager.urlForJenkins =
+ "http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local/scm"
+
+ // More internal fields are set lazily in ScmManger.groovy (after SCMM is deployed and ports are known)
+ }
+ config.scm.scmManager.gitOpsUsername="${config.application.namePrefix}gitops"
+
+ if (config.scm.gitlab.url) {
+ config.scm.scmProviderType = ScmProviderType.GITLAB
+ config.scm.scmManager = null
+ if (!config.scm.gitlab.password || !config.scm.gitlab.parentGroupId) {
+ throw new RuntimeException('SCMProviderType is Gitlab but no PAT Token is given')
+ }
+ }
+ }
+
+ //Retrieves the appropriate SCM for cluster resources depending on whether the environment is multi-tenant or not.
+ GitProvider getResourcesScm() {
+ if (central) {
+ return central
+ } else if (tenant) {
+ return tenant
+ } else {
+ throw new IllegalStateException("No SCM provider found.")
+ }
+ }
+
+ @Override
+ void enable() {
+ //TenantSCM
+ switch (config.scm.scmProviderType) {
+ case ScmProviderType.GITLAB:
+ this.tenant = new Gitlab(this.config, this.config.scm.gitlab)
+ break
+ 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.setup() setup will be here in future
+ break
+ default:
+ throw new IllegalArgumentException("Unsupported SCM provider found in TenantSCM")
+ }
+
+ if (config.multiTenant.useDedicatedInstance) {
+ switch (config.multiTenant.scmProviderType) {
+ case ScmProviderType.GITLAB:
+ 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)
+ break
+ default:
+ throw new IllegalArgumentException("Unsupported SCM-Central provider: ${config.scm.scmProviderType}")
+ }
+ }
+
+ //can be removed if we combine argocd and cluster-resources
+ final String namePrefix = (config?.application?.namePrefix ?: "").trim()
+ if (this.central) {
+ setupRepos(this.central, namePrefix)
+ setupRepos(this.tenant, namePrefix, false)
+ } else {
+ setupRepos(this.tenant, namePrefix, true)
+ }
+ create3thPartyDependencies(this.tenant, namePrefix)
+ }
+
+ // includeClusterResources = true => also create the argocd/cluster-resources repository
+ static void setupRepos(GitProvider gitProvider, String namePrefix = "", boolean includeClusterResources = true) {
+ gitProvider.createRepository(
+ withOrgPrefix(namePrefix, "argocd/argocd"),
+ "GitOps repo for administration of ArgoCD"
+ )
+ if (includeClusterResources) {
+ gitProvider.createRepository(
+ withOrgPrefix(namePrefix, "argocd/cluster-resources"),
+ "GitOps repo for basic cluster-resources"
+ )
+ }
+ }
+
+ static create3thPartyDependencies(GitProvider gitProvider, String namePrefix = "") {
+ gitProvider.createRepository(withOrgPrefix(namePrefix, "3rd-party-dependencies/spring-boot-helm-chart"), "spring-boot-helm-chart")
+ gitProvider.createRepository(withOrgPrefix(namePrefix, "3rd-party-dependencies/spring-boot-helm-chart-with-dependency"), "spring-boot-helm-chart-with-dependency")
+ gitProvider.createRepository(withOrgPrefix(namePrefix, "3rd-party-dependencies/gitops-build-lib"), "Jenkins pipeline shared library for automating deployments via GitOps")
+ gitProvider.createRepository(withOrgPrefix(namePrefix, "3rd-party-dependencies/ces-build-lib"), "Jenkins pipeline shared library adding features for Maven, Gradle, Docker, SonarQube, Git and others")
+ }
+
+ /**
+ * Adds a prefix to the group/namespace part (before the first '/'):
+ * Example: "argocd/argocd" + "foo-" => "foo-argocd/argocd"
+ */
+ static String withOrgPrefix(String prefix, String repoPath) {
+ if (!prefix) return repoPath
+ return prefix + repoPath
+ }
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/features/git/config/ScmCentralSchema.groovy b/src/main/groovy/com/cloudogu/gitops/features/git/config/ScmCentralSchema.groovy
new file mode 100644
index 000000000..660af0327
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/features/git/config/ScmCentralSchema.groovy
@@ -0,0 +1,93 @@
+package com.cloudogu.gitops.features.git.config
+
+import com.cloudogu.gitops.config.Config
+import com.cloudogu.gitops.config.Credentials
+import com.cloudogu.gitops.features.git.config.util.GitlabConfig
+import com.cloudogu.gitops.features.git.config.util.ScmManagerConfig
+import com.fasterxml.jackson.annotation.JsonPropertyDescription
+import picocli.CommandLine.Option
+
+class ScmCentralSchema {
+
+ static class GitlabCentralConfig implements GitlabConfig {
+
+ public static final String CENTRAL_GITLAB_URL_DESCRIPTION = "URL for external Gitlab"
+ public static final String CENTRAL_GITLAB_USERNAME_DESCRIPTION = "GitLab username for API access. Must be 'oauth2' when using Personal Access Token (PAT) authentication"
+ public static final String CENTRAL_GITLAB_PASSWORD_DESCRIPTION = "Password for SCM Manager authentication"
+ public static final String CENTRAL_GITLAB_PARENTGROUP_ID_DESCRIPTION = "Main Group for Gitlab where the GOP creates it's groups/repos"
+
+ // Only supports external Gitlab for now
+ Boolean internal = false
+
+ @Option(names = ['--central-gitlab-url'], description = CENTRAL_GITLAB_URL_DESCRIPTION)
+ @JsonPropertyDescription(CENTRAL_GITLAB_URL_DESCRIPTION)
+ String url = ''
+
+ @Option(names = ['--central-gitlab-username'], description = CENTRAL_GITLAB_USERNAME_DESCRIPTION)
+ @JsonPropertyDescription(CENTRAL_GITLAB_USERNAME_DESCRIPTION)
+ String username = 'oauth2.0'
+
+ @Option(names = ['--central-gitlab-token'], description = CENTRAL_GITLAB_PASSWORD_DESCRIPTION)
+ @JsonPropertyDescription(CENTRAL_GITLAB_PASSWORD_DESCRIPTION)
+ String password = ''
+
+ @Option(names = ['--central-gitlab-group-id'], description = CENTRAL_GITLAB_PARENTGROUP_ID_DESCRIPTION)
+ @JsonPropertyDescription(CENTRAL_GITLAB_PARENTGROUP_ID_DESCRIPTION)
+ String parentGroupId = ''
+
+ Credentials getCredentials() {
+ return new Credentials(username, password)
+ }
+ }
+
+ static class ScmManagerCentralConfig implements ScmManagerConfig {
+
+ public static final String CENTRAL_SCMM_INTERNAL_DESCRIPTION = 'SCM for Central Management is running on the same cluster, so k8s internal URLs can be used for access'
+ public static final String CENTRAL_SCMM_URL_DESCRIPTION = 'URL for the centralized Management Repo'
+ public static final String CENTRAL_SCMM_USERNAME_DESCRIPTION = 'CENTRAL SCMM username'
+ public static final String CENTRAL_SCMM_PASSWORD_DESCRIPTION = 'CENTRAL SCMM password'
+ public static final String CENTRAL_SCMM_PATH_DESCRIPTION = 'Root path for SCM Manager'
+ public static final String CENTRAL_SCMM_NAMESPACE_DESCRIPTION = 'Namespace where to find the Central SCMM'
+
+ @Option(names = ['--central-scmm-internal'], description = CENTRAL_SCMM_INTERNAL_DESCRIPTION)
+ @JsonPropertyDescription(CENTRAL_SCMM_INTERNAL_DESCRIPTION)
+ Boolean internal = false
+
+ @Option(names = ['--central-scmm-url'], description = CENTRAL_SCMM_URL_DESCRIPTION)
+ @JsonPropertyDescription(CENTRAL_SCMM_URL_DESCRIPTION)
+ String url = ''
+
+ @Option(names = ['--central-scmm-username'], description = CENTRAL_SCMM_USERNAME_DESCRIPTION)
+ @JsonPropertyDescription(CENTRAL_SCMM_USERNAME_DESCRIPTION)
+ String username = ''
+
+ @Option(names = ['--central-scmm-password'], description = CENTRAL_SCMM_PASSWORD_DESCRIPTION)
+ @JsonPropertyDescription(CENTRAL_SCMM_PASSWORD_DESCRIPTION)
+ String password = ''
+
+ @Option(names = ['--central-scmm-root-path'], description = CENTRAL_SCMM_PATH_DESCRIPTION)
+ @JsonPropertyDescription(CENTRAL_SCMM_PATH_DESCRIPTION)
+ String rootPath = 'repo'
+
+ @Option(names = ['--central-scmm-namespace'], description = CENTRAL_SCMM_NAMESPACE_DESCRIPTION)
+ @JsonPropertyDescription(CENTRAL_SCMM_NAMESPACE_DESCRIPTION)
+ String namespace = 'scm-manager'
+
+ @Override
+ String getIngress() {
+ return null //Needed for setup
+ }
+
+ @Override
+ Config.HelmConfigWithValues getHelm() {
+ return null //Needed for setup
+ }
+
+ Credentials getCredentials() {
+ return new Credentials(username, password)
+ }
+
+ String gitOpsUsername = ''
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/features/git/config/ScmTenantSchema.groovy b/src/main/groovy/com/cloudogu/gitops/features/git/config/ScmTenantSchema.groovy
new file mode 100644
index 000000000..0e1f1b353
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/features/git/config/ScmTenantSchema.groovy
@@ -0,0 +1,155 @@
+package com.cloudogu.gitops.features.git.config
+
+import com.cloudogu.gitops.config.Config
+import com.cloudogu.gitops.config.Credentials
+import com.cloudogu.gitops.features.git.config.util.GitlabConfig
+import com.cloudogu.gitops.features.git.config.util.ScmManagerConfig
+import com.cloudogu.gitops.features.git.config.util.ScmProviderType
+import com.cloudogu.gitops.utils.NetworkingUtils
+import com.fasterxml.jackson.annotation.JsonIgnore
+import com.fasterxml.jackson.annotation.JsonMerge
+import com.fasterxml.jackson.annotation.JsonPropertyDescription
+import picocli.CommandLine.Mixin
+import picocli.CommandLine.Option
+
+import static com.cloudogu.gitops.config.ConfigConstants.HELM_CONFIG_DESCRIPTION
+
+class ScmTenantSchema {
+
+ static final String GITLAB_CONFIG_DESCRIPTION = 'Config for GITLAB'
+ static final String SCMM_CONFIG_DESCRIPTION = 'Config for GITLAB'
+ static final String SCM_PROVIDER_TYPE_DESCRIPTION = 'The SCM provider type. Possible values: SCM_MANAGER, GITLAB'
+ static final String GITOPSUSERNAME_DESCRIPTION = 'Username for the Gitops User'
+
+ @Option(
+ names = ['--scm-provider'],
+ description = SCM_PROVIDER_TYPE_DESCRIPTION,
+ defaultValue = "SCM_MANAGER"
+ )
+ @JsonPropertyDescription(SCM_PROVIDER_TYPE_DESCRIPTION)
+ ScmProviderType scmProviderType = ScmProviderType.SCM_MANAGER
+
+ @JsonPropertyDescription(GITLAB_CONFIG_DESCRIPTION)
+ @Mixin
+ GitlabTenantConfig gitlab
+
+ @JsonPropertyDescription(SCMM_CONFIG_DESCRIPTION)
+ @Mixin
+ ScmManagerTenantConfig scmManager
+
+ @JsonIgnore
+ Boolean internal = { ->
+ return (gitlab.internal || scmManager.internal)
+ }
+
+
+ static class GitlabTenantConfig implements GitlabConfig {
+
+ static final String GITLAB_INTERNAL_DESCRIPTION = 'True if Gitlab is running in the same K8s cluster. For now we only support access by external URL'
+ static final String GITLAB_URL_DESCRIPTION = "Base URL for the Gitlab instance"
+ static final String GITLAB_USERNAME_DESCRIPTION = 'Defaults to: oauth2.0 when PAT token is given.'
+ static final String GITLAB_TOKEN_DESCRIPTION = 'PAT Token for the account. Needs read/write repo permissions. See docs for mor information'
+ static final String GITLAB_PARENT_GROUP_ID = 'Number for the Gitlab Group where the repos and subgroups should be created'
+
+ @JsonPropertyDescription(GITLAB_INTERNAL_DESCRIPTION)
+ Boolean internal = false
+
+ @Option(names = ['--gitlab-url'], description = GITLAB_URL_DESCRIPTION)
+ @JsonPropertyDescription(GITLAB_URL_DESCRIPTION)
+ String url = ''
+
+ @Option(names = ['--gitlab-username'], description = GITLAB_USERNAME_DESCRIPTION)
+ @JsonPropertyDescription(GITLAB_USERNAME_DESCRIPTION)
+ String username = 'oauth2.0'
+
+ @Option(names = ['--gitlab-token'], description = GITLAB_TOKEN_DESCRIPTION)
+ @JsonPropertyDescription(GITLAB_TOKEN_DESCRIPTION)
+ String password = ''
+
+ @Option(names = ['--gitlab-parent-id'], description = GITLAB_PARENT_GROUP_ID)
+ @JsonPropertyDescription(GITLAB_PARENT_GROUP_ID)
+ String parentGroupId = ''
+
+ Credentials getCredentials() {
+ return new Credentials(username, password)
+ }
+
+ }
+
+ static class ScmManagerTenantConfig implements ScmManagerConfig {
+
+ static final String SCMM_SKIP_RESTART_DESCRIPTION = 'Skips restarting SCM-Manager after plugin installation. Use with caution! If the plugins are not installed up front, the installation will likely fail. The intended use case for this is after the first installation, for config changes only. Do not use on first installation or upgrades.\''
+ static final String SCMM_SKIP_PLUGINS_DESCRIPTION = 'Skips plugin installation. Use with caution! If the plugins are not installed up front, the installation will likely fail. The intended use case for this is after the first installation, for config changes only. Do not use on first installation or upgrades.'
+ static final String SCMM_URL_DESCRIPTION = 'The host of your external scm-manager'
+ static final String SCMM_USERNAME_DESCRIPTION = 'Mandatory when scmm-url is set'
+ static final String SCMM_PASSWORD_DESCRIPTION = 'Mandatory when scmm-url is set'
+ static final String SCMM_ROOT_PATH_DESCRIPTION = 'Sets the root path for the Git Repositories. In SCM-Manager it is always "repo"'
+ static final String SCMM_NAMESPACE_DESCRIPTION = 'Namespace where SCM-Manager should run'
+
+ Boolean internal = true
+
+ @Option(names = ['--scmm-url'], description = SCMM_URL_DESCRIPTION)
+ @JsonPropertyDescription(SCMM_URL_DESCRIPTION)
+ String url = ''
+
+ @Option(names = ['--scmm-namespace'], description = SCMM_NAMESPACE_DESCRIPTION)
+ @JsonPropertyDescription(SCMM_NAMESPACE_DESCRIPTION)
+ String namespace = 'scm-manager'
+
+ @Option(names = ['--scmm-username'], description = SCMM_USERNAME_DESCRIPTION)
+ @JsonPropertyDescription(SCMM_USERNAME_DESCRIPTION)
+ String username = Config.DEFAULT_ADMIN_USER
+
+ @Option(names = ['--scmm-password'], description = SCMM_PASSWORD_DESCRIPTION)
+ @JsonPropertyDescription(SCMM_PASSWORD_DESCRIPTION)
+ String password = Config.DEFAULT_ADMIN_PW
+
+ @JsonPropertyDescription(HELM_CONFIG_DESCRIPTION)
+ @JsonMerge
+ Config.HelmConfigWithValues helm = new Config.HelmConfigWithValues(
+ chart: 'scm-manager',
+ repoURL: 'https://packages.scm-manager.org/repository/helm-v2-releases/',
+ version: '3.11.0',
+ values: [:]
+ )
+
+ @Option(names = ['--scmm-root-path'], description = SCMM_ROOT_PATH_DESCRIPTION)
+ @JsonPropertyDescription(SCMM_ROOT_PATH_DESCRIPTION)
+ String rootPath = 'repo'
+
+ /* When installing from via Docker we have to distinguish scmm.url (which is a local IP address) from
+ the SCMM URL used by jenkins.
+
+ This is necessary to make the build on push feature (webhooks from SCMM to Jenkins that trigger builds) work
+ in k3d.
+ The webhook contains repository URLs that start with the "Base URL" Setting of SCMM.
+ Jenkins checks these repo URLs and triggers all builds that match repo URLs.
+
+ This value is set as "Base URL" in SCMM Settings and in Jenkins Job.
+
+ See ApplicationConfigurator.addScmmConfig() and the comment at jenkins.urlForScmm */
+
+ String urlForJenkins = ''
+
+ @JsonIgnore
+ String getHost() { return NetworkingUtils.getHost(url) }
+
+ @JsonIgnore
+ String getProtocol() { return NetworkingUtils.getProtocol(url) }
+ String ingress = ''
+
+ @Option(names = ['--scmm-skip-restart'], description = SCMM_SKIP_RESTART_DESCRIPTION)
+ @JsonPropertyDescription(SCMM_SKIP_RESTART_DESCRIPTION)
+ Boolean skipRestart = false
+
+ @Option(names = ['--scmm-skip-plugins'], description = SCMM_SKIP_PLUGINS_DESCRIPTION)
+ @JsonPropertyDescription(SCMM_SKIP_PLUGINS_DESCRIPTION)
+ Boolean skipPlugins = false
+
+ String gitOpsUsername = ''
+
+ Credentials getCredentials() {
+ return new Credentials(username, password)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/features/git/config/util/GitlabConfig.groovy b/src/main/groovy/com/cloudogu/gitops/features/git/config/util/GitlabConfig.groovy
new file mode 100644
index 000000000..ecffa3d47
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/features/git/config/util/GitlabConfig.groovy
@@ -0,0 +1,12 @@
+package com.cloudogu.gitops.features.git.config.util
+
+import com.cloudogu.gitops.config.Credentials
+
+interface GitlabConfig {
+ String url
+ String parentGroupId
+ String defaultVisibility
+ String gitOpsUsername
+
+ Credentials getCredentials()
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/features/git/config/util/ScmManagerConfig.groovy b/src/main/groovy/com/cloudogu/gitops/features/git/config/util/ScmManagerConfig.groovy
new file mode 100644
index 000000000..ffdd63813
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/features/git/config/util/ScmManagerConfig.groovy
@@ -0,0 +1,20 @@
+package com.cloudogu.gitops.features.git.config.util
+
+import com.cloudogu.gitops.config.Config
+import com.cloudogu.gitops.config.Credentials
+
+
+interface ScmManagerConfig {
+ Boolean getInternal()
+
+ String getUrl()
+ String getUsername()
+ String getPassword()
+ String getNamespace()
+ String getIngress()
+ Config.HelmConfigWithValues getHelm()
+ String getRootPath()
+ String getGitOpsUsername()
+
+ Credentials getCredentials()
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/features/git/config/util/ScmProviderType.groovy b/src/main/groovy/com/cloudogu/gitops/features/git/config/util/ScmProviderType.groovy
new file mode 100644
index 000000000..5685e2e71
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/features/git/config/util/ScmProviderType.groovy
@@ -0,0 +1,6 @@
+package com.cloudogu.gitops.features.git.config.util
+
+enum ScmProviderType {
+ GITLAB,
+ SCM_MANAGER
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/git/GitRepo.groovy b/src/main/groovy/com/cloudogu/gitops/git/GitRepo.groovy
new file mode 100644
index 000000000..7543f32eb
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/git/GitRepo.groovy
@@ -0,0 +1,213 @@
+package com.cloudogu.gitops.git
+
+import com.cloudogu.gitops.config.Config
+import com.cloudogu.gitops.git.jgit.helpers.InsecureCredentialProvider
+import com.cloudogu.gitops.git.providers.AccessRole
+import com.cloudogu.gitops.git.providers.GitProvider
+import com.cloudogu.gitops.git.providers.RepoUrlScope
+import com.cloudogu.gitops.git.providers.Scope
+import com.cloudogu.gitops.utils.FileSystemUtils
+import com.cloudogu.gitops.utils.TemplatingEngine
+import groovy.util.logging.Slf4j
+import org.eclipse.jgit.api.Git
+import org.eclipse.jgit.api.PushCommand
+import org.eclipse.jgit.transport.ChainingCredentialsProvider
+import org.eclipse.jgit.transport.CredentialsProvider
+import org.eclipse.jgit.transport.RefSpec
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider
+
+@Slf4j
+class GitRepo {
+
+ static final String NAMESPACE_3RD_PARTY_DEPENDENCIES = '3rd-party-dependencies'
+
+ private final Config config
+ public GitProvider gitProvider
+ private final FileSystemUtils fileSystemUtils
+
+ private final String repoTarget
+ private final boolean insecure
+ private final String gitName
+ private final String gitEmail
+
+ private Git gitMemoization
+ private final String absoluteLocalRepoTmpDir
+
+ GitRepo(Config config,
+ GitProvider gitProvider,
+ String repoTarget,
+ FileSystemUtils fileSystemUtils) {
+ def tmpDir = File.createTempDir()
+ tmpDir.deleteOnExit()
+ this.absoluteLocalRepoTmpDir = tmpDir.absolutePath
+ this.config = config
+ this.gitProvider = gitProvider
+ this.fileSystemUtils = fileSystemUtils
+
+ this.repoTarget = repoTarget.startsWith(NAMESPACE_3RD_PARTY_DEPENDENCIES) ? repoTarget :
+ "${config.application.namePrefix}${repoTarget}"
+
+ this.insecure = config.application.insecure
+ this.gitName = config.application.gitName
+ this.gitEmail = config.application.gitEmail
+ }
+
+ String getRepoTarget() {
+ return repoTarget
+ }
+
+ boolean createRepositoryAndSetPermission(String repoTarget, String description, boolean initialize = true) {
+ def isNewRepo = this.gitProvider.createRepository(repoTarget, description, initialize)
+ if (isNewRepo && gitProvider.getGitOpsUsername()) {
+ gitProvider.setRepositoryPermission(
+ repoTarget,
+ gitProvider.getGitOpsUsername(),
+ AccessRole.WRITE,
+ Scope.USER
+ )
+ }
+ return isNewRepo
+
+ }
+
+ String getAbsoluteLocalRepoTmpDir() {
+ return absoluteLocalRepoTmpDir
+ }
+
+ void cloneRepo() {
+ def cloneUrl = getGitRepositoryUrl()
+ log.debug("Cloning ${repoTarget}, Origin: ${cloneUrl}")
+ Git.cloneRepository()
+ .setURI(cloneUrl)
+ .setDirectory(new File(absoluteLocalRepoTmpDir))
+ .setCredentialsProvider(getCredentialProvider())
+ .call()
+ }
+
+ void commitAndPush(String message, String tag) {
+ commitAndPush(message, tag, 'HEAD:refs/heads/main')
+ }
+
+
+ void commitAndPush(String commitMessage, String tag, String refSpec) {
+ log.debug("Adding files to ${repoTarget}")
+ def git = getGit()
+ git.add().addFilepattern(".").call()
+
+ if (git.status().call().hasUncommittedChanges()) {
+ log.debug("Commiting ${repoTarget}")
+ git.commit()
+ .setSign(false)
+ .setMessage(commitMessage)
+ .setAuthor(gitName, gitEmail)
+ .setCommitter(gitName, gitEmail)
+ .call()
+
+ def pushCommand = createPushCommand(refSpec)
+
+ if (tag) {
+ log.debug("Setting tag '${tag}' on repo: ${repoTarget}")
+ // Delete existing tags first to get idempotence
+ git.tagDelete().setTags(tag).call()
+ git.tag()
+ .setName(tag)
+ .call()
+ pushCommand.setPushTags()
+ }
+
+ log.debug("Pushing repo: ${repoTarget}, refSpec: ${refSpec}")
+ pushCommand.call()
+ } else {
+ log.debug("No changes after add, nothing to commit or push on repo: ${repoTarget}")
+ }
+ }
+
+
+ void commitAndPush(String commitMessage) {
+ commitAndPush(commitMessage, null, 'HEAD:refs/heads/main')
+ }
+ /**
+ * Push all refs, i.e. all tags and branches
+ */
+
+ void pushAll(boolean force) {
+ createPushCommand('refs/*:refs/*').setForce(force).call()
+ }
+
+
+ void pushRef(String ref, boolean force) {
+ pushRef(ref, ref, force)
+ }
+
+
+ void pushRef(String ref, String targetRef, boolean force) {
+ createPushCommand("${ref}:${targetRef}").setForce(force).call()
+ }
+
+
+ /**
+ * Delete all files in this repository
+ */
+ void clearRepo() {
+ fileSystemUtils.deleteFilesExcept(new File(absoluteLocalRepoTmpDir), ".git")
+ }
+
+
+ void copyDirectoryContents(String srcDir) {
+ copyDirectoryContents(srcDir, (FileFilter) null)
+ }
+
+
+ void copyDirectoryContents(String srcDir, FileFilter fileFilter) {
+ if (!srcDir) {
+ log.warn("Source directory is not defined. Nothing to copy?")
+ return
+ }
+
+ log.debug("Initializing repo $repoTarget from $srcDir")
+ String absoluteSrcDirLocation = new File(srcDir).isAbsolute()
+ ? srcDir
+ : "${fileSystemUtils.getRootDir()}/${srcDir}"
+ fileSystemUtils.copyDirectory(absoluteSrcDirLocation, absoluteLocalRepoTmpDir, fileFilter)
+ }
+
+
+ void writeFile(String path, String content) {
+ def file = new File("$absoluteLocalRepoTmpDir/$path")
+ fileSystemUtils.createDirectory(file.parent)
+ file.createNewFile()
+ file.text = content
+ }
+
+
+ void replaceTemplates(Map parameters) {
+ new TemplatingEngine().replaceTemplates(new File(absoluteLocalRepoTmpDir), parameters)
+ }
+
+ private PushCommand createPushCommand(String refSpec) {
+ getGit()
+ .push()
+ .setRemote(getGitRepositoryUrl())
+ .setRefSpecs(new RefSpec(refSpec))
+ .setCredentialsProvider(getCredentialProvider())
+ }
+
+ String getGitRepositoryUrl() {
+ return this.gitProvider.repoUrl(repoTarget, RepoUrlScope.CLIENT)
+ }
+
+ private Git getGit() {
+ if (gitMemoization != null) {
+ return gitMemoization
+ }
+
+ return gitMemoization = Git.open(new File(absoluteLocalRepoTmpDir))
+ }
+
+ private CredentialsProvider getCredentialProvider() {
+ def auth = this.gitProvider.getCredentials()
+ def passwordAuthentication = new UsernamePasswordCredentialsProvider(auth.username, auth.password)
+ return insecure ? new ChainingCredentialsProvider(new InsecureCredentialProvider(), passwordAuthentication) : passwordAuthentication
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/git/GitRepoFactory.groovy b/src/main/groovy/com/cloudogu/gitops/git/GitRepoFactory.groovy
new file mode 100644
index 000000000..31e0b3221
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/git/GitRepoFactory.groovy
@@ -0,0 +1,22 @@
+package com.cloudogu.gitops.git
+
+import com.cloudogu.gitops.config.Config
+import com.cloudogu.gitops.git.providers.GitProvider
+import com.cloudogu.gitops.utils.FileSystemUtils
+import jakarta.inject.Singleton
+
+@Singleton
+class GitRepoFactory {
+ protected final Config config
+ protected final FileSystemUtils fileSystemUtils
+
+ GitRepoFactory(Config config, FileSystemUtils fileSystemUtils) {
+ this.fileSystemUtils = fileSystemUtils
+ this.config = config
+ }
+
+ GitRepo getRepo(String repoTarget, GitProvider gitProvider) {
+ return new GitRepo(config, gitProvider, repoTarget, fileSystemUtils)
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/scmm/jgit/InsecureCredentialProvider.groovy b/src/main/groovy/com/cloudogu/gitops/git/jgit/helpers/InsecureCredentialProvider.groovy
similarity index 97%
rename from src/main/groovy/com/cloudogu/gitops/scmm/jgit/InsecureCredentialProvider.groovy
rename to src/main/groovy/com/cloudogu/gitops/git/jgit/helpers/InsecureCredentialProvider.groovy
index 8009eaf04..6dfc9c866 100644
--- a/src/main/groovy/com/cloudogu/gitops/scmm/jgit/InsecureCredentialProvider.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/git/jgit/helpers/InsecureCredentialProvider.groovy
@@ -1,4 +1,4 @@
-package com.cloudogu.gitops.scmm.jgit
+package com.cloudogu.gitops.git.jgit.helpers
import org.eclipse.jgit.errors.UnsupportedCredentialItem
import org.eclipse.jgit.transport.CredentialItem
@@ -47,4 +47,4 @@ class InsecureCredentialProvider extends CredentialsProvider {
return true
}
-}
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/git/providers/GitProvider.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/GitProvider.groovy
new file mode 100644
index 000000000..6d2568e51
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/git/providers/GitProvider.groovy
@@ -0,0 +1,67 @@
+package com.cloudogu.gitops.git.providers
+
+import com.cloudogu.gitops.config.Credentials
+
+interface GitProvider {
+
+ default boolean createRepository(String repoTarget, String description) {
+ return createRepository(repoTarget, description, true);
+ }
+
+ boolean createRepository(String repoTarget, String description, boolean initialize)
+
+ void setRepositoryPermission(String repoTarget, String principal, AccessRole role, Scope scope)
+
+ default String repoUrl(String repoTarget) {
+ return repoUrl(repoTarget, RepoUrlScope.IN_CLUSTER);
+ }
+
+ String repoUrl(String repoTarget, RepoUrlScope scope);
+
+ String repoPrefix()
+
+ Credentials getCredentials()
+
+ URI prometheusMetricsEndpoint()
+
+ //TODO implement
+ void deleteRepository(String namespace, String repository, boolean prefixNamespace)
+
+ //TODO implement
+ void deleteUser(String name)
+
+ //TODO implement
+ void setDefaultBranch(String repoTarget, String branch)
+
+ String getUrl()
+
+ String getProtocol()
+
+ String getHost() //TODO? can we maybe get this via helper and config?
+
+ String getGitOpsUsername()
+
+}
+
+enum AccessRole {
+ READ, WRITE, MAINTAIN, ADMIN, OWNER
+}
+
+enum Scope {
+ USER, GROUP
+}
+
+/**
+ * IN_CLUSTER: URLs intended for workloads running inside the Kubernetes cluster
+ * (e.g., ArgoCD, Jobs, in-cluster automation).
+ *
+ * CLIENT : URLs intended for interactive or CI clients performing push/clone operations,
+ * regardless of their location.
+ * If the application itself runs inside Kubernetes, the Service DNS is used;
+ * otherwise, NodePort (for internal installations) or externalBase (for external ones)
+ * is selected automatically.
+ */
+enum RepoUrlScope {
+ IN_CLUSTER,
+ CLIENT
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/git/providers/gitlab/Gitlab.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/gitlab/Gitlab.groovy
new file mode 100644
index 000000000..48bdf7482
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/git/providers/gitlab/Gitlab.groovy
@@ -0,0 +1,390 @@
+package com.cloudogu.gitops.git.providers.gitlab
+
+import com.cloudogu.gitops.config.Config
+import com.cloudogu.gitops.config.Credentials
+import com.cloudogu.gitops.features.git.config.util.GitlabConfig
+import com.cloudogu.gitops.git.providers.AccessRole
+import com.cloudogu.gitops.git.providers.GitProvider
+import com.cloudogu.gitops.git.providers.RepoUrlScope
+import com.cloudogu.gitops.git.providers.Scope
+import groovy.util.logging.Slf4j
+import org.gitlab4j.api.GitLabApi
+import org.gitlab4j.api.GitLabApiException
+import org.gitlab4j.api.models.AccessLevel
+import org.gitlab4j.api.models.Group
+import org.gitlab4j.api.models.Project
+import org.gitlab4j.api.models.Visibility
+
+import java.util.logging.Level
+
+@Slf4j
+class Gitlab implements GitProvider {
+
+ private final Config config
+ private final GitLabApi api
+ private GitlabConfig gitlabConfig
+
+ Gitlab(Config config, GitlabConfig gitlabConfig) {
+ this.config = config
+ this.gitlabConfig = gitlabConfig
+ this.api = new GitLabApi(gitlabConfig.url, credentials.password)
+ this.api.enableRequestResponseLogging(Level.ALL)
+ }
+
+ @Override
+ boolean createRepository(String repoTarget, String description, boolean initialize) {
+ def repoNamespace = repoTarget.split('/', 2)[0]
+ def repoName = repoTarget.split('/', 2)[1]
+
+// def repoNamespacePrefixed = config.application.namePrefix + repoNamespace
+ // 1) Resolve parent by numeric ID (do NOT treat the ID as a path!)
+ Group parent = parentGroup()
+ String repoNamespacePath = repoNamespace.toLowerCase()
+ String projectPath = repoName.toLowerCase()
+
+ long subgroupId = ensureSubgroupUnderParentId(parent, repoNamespacePath)
+ String fullProjectPath = "${parentFullPath()}/${repoNamespacePath}/${projectPath}"
+
+
+ if (findProject(fullProjectPath).present) {
+ log.info("GitLab project already exists: ${fullProjectPath}")
+ return false
+ }
+
+ def project = new Project()
+ .withName(repoName)
+ .withPath(projectPath)
+ .withDescription(description ?: "")
+ .withIssuesEnabled(false)
+ .withMergeRequestsEnabled(false)
+ .withWikiEnabled(false)
+ .withSnippetsEnabled(false)
+ .withNamespaceId(subgroupId)
+ .withInitializeWithReadme(initialize)
+ project.visibility = toVisibility(gitlabConfig.defaultVisibility)
+
+ def created = api.projectApi.createProject(project)
+ log.info("Created GitLab project ${created.getPathWithNamespace()} (id=${created.id})")
+ return true
+ }
+
+ @Override
+ void setRepositoryPermission(String repoTarget, String principal, AccessRole role, Scope scope) {
+ String fullPath = resolveFullPath(repoTarget)
+ Project project = findProjectOrThrow(fullPath)
+ AccessLevel level = toAccessLevel(role, scope)
+ if (scope == Scope.GROUP) {
+ def group = api.groupApi.getGroups(principal)
+ .find { it.fullPath == principal || it.path == principal || it.name == principal }
+ if (!group) throw new IllegalArgumentException("Group '${principal}' not found")
+ api.projectApi.shareProject(project.id, group.id, level, null)
+ } else {
+ def user = api.userApi.findUsers(principal)
+ .find { it.username == principal || it.email == principal }
+ if (!user) throw new IllegalArgumentException("User '${principal}' not found")
+ api.projectApi.addMember(project.id, user.id, level)
+ }
+ }
+
+ @Override
+ String repoUrl(String repoTarget, RepoUrlScope scope) {
+ String base = gitlabConfig.url.strip()
+ return "${base}/${parentFullPath()}/${repoTarget}.git"
+ }
+
+ @Override
+ String repoPrefix() {
+ String base = gitlabConfig.url.strip()
+ def prefix = (config.application.namePrefix ?: "").strip()
+ return "${base}/${parentFullPath()}/${prefix}"
+
+ }
+
+ //TODo getCredentials
+ @Override
+ Credentials getCredentials() {
+ return this.gitlabConfig.credentials
+ }
+
+ @Override
+ String getProtocol() {
+ return gitlabConfig.url
+ }
+
+ String getHost() {
+ return gitlabConfig.url
+ }
+
+ @Override
+ String getGitOpsUsername() {
+ return gitlabConfig.gitOpsUsername
+ }
+
+ @Override
+ String getUrl() {
+ return this.gitlabConfig.url
+ }
+
+ //TODO do we dee
+ @Override
+ URI prometheusMetricsEndpoint() {
+ return null
+ }
+
+ //TODO implement
+ @Override
+ void deleteRepository(String namespace, String repository, boolean prefixNamespace) {
+
+ }
+
+ //TODO implement
+ @Override
+ void deleteUser(String name) {
+
+ }
+
+ //TODO implement
+ @Override
+ void setDefaultBranch(String repoTarget, String branch) {
+
+ }
+
+
+ private Group parentGroup() {
+ try {
+ return api.groupApi.getGroup(gitlabConfig.parentGroupId as Long)
+ } catch (GitLabApiException e) {
+ throw new IllegalStateException(
+ "Parent group '${gitlabConfig.parentGroupId}' not found or inaccessible: ${e.message}", e)
+ }
+ }
+
+
+ private String parentFullPath() {
+ parentGroup().fullPath
+ }
+
+ /** Ensure a single-level subgroup exists under 'parent'; return its namespace (group) ID. */
+ private long ensureSubgroupUnderParentId(Group parent, String segPath) {
+ // 1) Already there?
+ Group existing = findDirectSubgroupByPath(parent.id as Long, segPath)
+ if (existing != null) return existing.id as Long
+
+
+ // 2) Guard against project/subgroup name collision in the same parent
+ Project collision = findDirectProjectByPath(parent.id as Long, segPath)
+ if (collision != null) {
+ throw new IllegalStateException(
+ "Cannot create subgroup '${segPath}' under '${parent.fullPath}': " +
+ "a project with that path already exists at '${parent.fullPath}/${segPath}'. " +
+ "Rename/transfer the project first or choose a different subgroup name."
+ )
+ }
+
+ // 3) Create subgroup
+ Group toCreate = new Group()
+ .withName(segPath) // display name
+ .withPath(segPath) // (lowercase etc.)
+ .withParentId(parent.id)
+
+
+ try {
+ Group created = api.groupApi.addGroup(toCreate)
+ log.info("Created group {}", created.fullPath)
+ return created.id as Long
+ } catch (GitLabApiException e) {
+ // If someone created it in parallel, treat 400/409 as "exists" and re-fetch
+ if (e.httpStatus in [400, 409]) {
+ Group retry = findDirectSubgroupByPath(parent.id as Long, segPath)
+ if (retry != null) return retry.id as Long
+ }
+ def ve = e.hasValidationErrors() ? e.getValidationErrors() : null
+ log.error("addGroup failed (parent={}, segPath={}, status={}, message={}, validationErrors={})",
+ parent.fullPath, segPath, e.httpStatus, e.getMessage(), ve)
+ throw e
+ }
+ }
+
+
+ /** Find a direct subgroup of 'parentId' with the exact path . */
+ private Group findDirectSubgroupByPath(Long parentId, String segPath) {
+ // uses the overload: getSubGroups(Object idOrPath)
+ List subGroups = api.groupApi.getSubGroups(parentId)
+ return subGroups?.find { Group subGroup -> subGroup.path == segPath }
+ }
+
+
+ /** Find a direct project of 'parentId' with the exact path . */
+ private Project findDirectProjectByPath(Long parentId, String path) {
+ // uses the overload: getProjects(Object idOrPath)
+ List projects = api.groupApi.getProjects(parentId)
+ return projects?.find { Project project -> project.path == path }
+ }
+
+
+ // ---- Helpers ----
+ private Optional findProject(String fullPath) {
+ try {
+ return Optional.ofNullable(api.projectApi.getProject(fullPath))
+ } catch (Exception ignore) {
+ return Optional.empty()
+ }
+ }
+
+ private Project findProjectOrThrow(String fullPath) {
+ return findProject(fullPath).orElseThrow {
+ new IllegalStateException("GitLab project '${fullPath}' not found")
+ }
+ }
+
+ private String resolveFullPath(String repoTarget) {
+ if (!gitlabConfig.parentGroupId) {
+ throw new IllegalStateException("gitlab.parentGroup is not set")
+ }
+ return "${gitlabConfig.parentGroupId}/${repoTarget}"
+ }
+
+
+ private static Visibility toVisibility(String s) {
+ switch ((s ?: "private").toLowerCase()) {
+ case "public": return Visibility.PUBLIC
+ case "internal": return Visibility.INTERNAL
+ default: return Visibility.PRIVATE
+ }
+ }
+
+// provider-agnostic AccessRole → GitLab AccessLevel
+ private static AccessLevel toAccessLevel(AccessRole role, Scope scope) {
+ switch (role) {
+ case AccessRole.READ:
+ // GitLab: Guests usually can't read private repo code; Reporter can.
+ return AccessLevel.REPORTER
+ case AccessRole.WRITE:
+ // Typical push/merge permissions
+ return AccessLevel.DEVELOPER
+ case AccessRole.MAINTAIN:
+ return AccessLevel.MAINTAINER
+ case AccessRole.ADMIN:
+ // No separate project-level "admin" → cap at Maintainer
+ return AccessLevel.MAINTAINER
+ case AccessRole.OWNER:
+ // OWNER is meaningful for groups/namespaces; for users on a project we cap to MAINTAINER
+ return (scope == Scope.GROUP) ? AccessLevel.OWNER : AccessLevel.MAINTAINER
+ default:
+ throw new IllegalArgumentException("Unknown role: ${role}")
+ }
+ }
+
+
+ //TODO when git abctraction feature is ready, we will create before merge to main a branch, that
+ // contain this code as preservation for oop
+ /* ================================= SETUP CODE ====================================
+ void setup() {
+ log.info("Creating Gitlab Groups")
+ def mainGroupName = "${config.application.namePrefix}scm".toString()
+ Group mainSCMGroup = this.gitlabApi.groupApi.getGroup(mainGroupName)
+ if (!mainSCMGroup) {
+ def tempGroup = new Group()
+ .withName(mainGroupName)
+ .withPath(mainGroupName.toLowerCase())
+ .withParentId(null)
+
+ mainSCMGroup = this.gitlabApi.groupApi.addGroup(tempGroup)
+ }
+
+ String argoCDGroupName = 'argocd'
+ Optional argoCDGroup = getGroup("${mainGroupName}/${argoCDGroupName}")
+ if (argoCDGroup.isEmpty()) {
+ def tempGroup = new Group()
+ .withName(argoCDGroupName)
+ .withPath(argoCDGroupName.toLowerCase())
+ .withParentId(mainSCMGroup.id)
+
+ argoCDGroup = addGroup(tempGroup)
+ }
+
+ argoCDGroup.ifPresent(this.&createArgoCDRepos)
+
+ String dependencysGroupName = '3rd-party-dependencies'
+ Optional dependencysGroup = getGroup("${mainGroupName}/${dependencysGroupName}")
+ if (dependencysGroup.isEmpty()) {
+ def tempGroup = new Group()
+ .withName(dependencysGroupName)
+ .withPath(dependencysGroupName.toLowerCase())
+ .withParentId(mainSCMGroup.id)
+
+ addGroup(tempGroup)
+ }
+
+ String exercisesGroupName = 'exercises'
+ Optional exercisesGroup = getGroup("${mainGroupName}/${exercisesGroupName}")
+ if (exercisesGroup.isEmpty()) {
+ def tempGroup = new Group()
+ .withName(exercisesGroupName)
+ .withPath(exercisesGroupName.toLowerCase())
+ .withParentId(mainSCMGroup.id)
+
+ exercisesGroup = addGroup(tempGroup)
+ }
+
+ exercisesGroup.ifPresent(this.&createExercisesRepos)
+ }
+
+ void createRepo(String name, String description) {
+ Optional project = getProject("${parentGroup.getFullPath()}/${name}".toString())
+ if (project.isEmpty()) {
+ Project projectSpec = new Project()
+ .withName(name)
+ .withDescription(description)
+ .withIssuesEnabled(true)
+ .withMergeRequestsEnabled(true)
+ .withWikiEnabled(true)
+ .withSnippetsEnabled(true)
+ .withPublic(false)
+ .withNamespaceId(this.gitlabConfig.parentGroup.toLong())
+ .withInitializeWithReadme(true)
+
+ project = Optional.ofNullable(this.gitlabApi.projectApi.createProject(projectSpec))
+ log.info("Project ${projectSpec} created in Gitlab!")
+ }
+ removeBranchProtection(project.get())
+ }
+
+ void removeBranchProtection(Project project) {
+ try {
+ this.gitlabApi.getProtectedBranchesApi().unprotectBranch(project.getId(), project.getDefaultBranch())
+ log.debug("Unprotected default branch: " + project.getDefaultBranch())
+ } catch (Exception ex) {
+ log.error("Failed to unprotect default branch '${project.getDefaultBranch()}' for project '${project.getName()}' (ID: ${project.getId()})", ex)
+ }
+ }
+
+
+ private Optional getGroup(String groupName) {
+ try {
+ return Optional.ofNullable(this.gitlabApi.groupApi.getGroup(groupName))
+ } catch (Exception e) {
+ return Optional.empty()
+ }
+ }
+
+ private Optional addGroup(Group group) {
+ try {
+ return Optional.ofNullable(this.gitlabApi.groupApi.addGroup(group))
+ } catch (Exception e) {
+ return Optional.empty()
+ }
+ }
+
+ private Optional getProject(String projectPath) {
+ try {
+ return Optional.ofNullable(this.gitlabApi.projectApi.getProject(projectPath))
+ } catch (Exception e) {
+ return Optional.empty()
+
+
+ }
+ }
+ */
+
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/scmm/api/Permission.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/Permission.groovy
similarity index 91%
rename from src/main/groovy/com/cloudogu/gitops/scmm/api/Permission.groovy
rename to src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/Permission.groovy
index d70c99f97..ffe623bcd 100644
--- a/src/main/groovy/com/cloudogu/gitops/scmm/api/Permission.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/Permission.groovy
@@ -1,4 +1,4 @@
-package com.cloudogu.gitops.scmm.api
+package com.cloudogu.gitops.git.providers.scmmanager
class Permission {
final String name
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
new file mode 100644
index 000000000..16f1059ca
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManager.groovy
@@ -0,0 +1,284 @@
+package com.cloudogu.gitops.git.providers.scmmanager
+
+import com.cloudogu.gitops.config.Config
+import com.cloudogu.gitops.config.Credentials
+import com.cloudogu.gitops.features.git.config.util.ScmManagerConfig
+import com.cloudogu.gitops.git.providers.AccessRole
+import com.cloudogu.gitops.git.providers.GitProvider
+import com.cloudogu.gitops.git.providers.RepoUrlScope
+import com.cloudogu.gitops.git.providers.Scope
+import com.cloudogu.gitops.git.providers.scmmanager.api.Repository
+import com.cloudogu.gitops.git.providers.scmmanager.api.ScmManagerApiClient
+import com.cloudogu.gitops.utils.K8sClient
+import com.cloudogu.gitops.utils.NetworkingUtils
+import groovy.util.logging.Slf4j
+import retrofit2.Response
+
+@Slf4j
+class ScmManager implements GitProvider {
+
+ private final ScmManagerUrlResolver urls
+ private final ScmManagerApiClient apiClient
+ private final ScmManagerConfig scmmConfig
+
+ ScmManager(Config config, ScmManagerConfig scmmConfig, 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)
+ }
+
+
+ // --- Git operations ---
+ @Override
+ boolean createRepository(String repoTarget, String description, boolean initialize) {
+ def repoNamespace = repoTarget.split('/', 2)[0]
+ def repoName = repoTarget.split('/', 2)[1]
+ def repo = new Repository(repoNamespace, repoName, description ?: "")
+ Response response = apiClient.repositoryApi().create(repo, initialize).execute()
+ return handle201or409(response, "Repository ${repoNamespace}/${repoName}")
+ }
+
+ @Override
+ void setRepositoryPermission(String repoTarget, String principal, AccessRole role, Scope scope) {
+ def repoNamespace = repoTarget.split('/', 2)[0]
+ def repoName = repoTarget.split('/', 2)[1]
+
+ boolean isGroup = (scope == Scope.GROUP)
+ Permission.Role scmManagerRole = mapToScmManager(role)
+ def permission = new Permission(principal, scmManagerRole, isGroup)
+
+ Response response = apiClient.repositoryApi().createPermission(repoNamespace, repoName, permission).execute()
+ handle201or409(response, "Permission on ${repoNamespace}/${repoName}")
+ }
+
+ @Override
+ Credentials getCredentials() {
+ return this.scmmConfig.credentials
+ }
+
+
+ //TODO implement
+ @Override
+ void setDefaultBranch(String repoTarget, String branch) {
+
+ }
+
+ //TODO implement
+ @Override
+ void deleteRepository(String namespace, String repository, boolean prefixNamespace) {
+
+ }
+ //TODO implement
+ @Override
+ void deleteUser(String name) {
+
+ }
+
+ @Override
+ String getGitOpsUsername() {
+ return scmmConfig.gitOpsUsername
+ }
+
+ // --- In-cluster / Endpoints ---
+ /** In-cluster base …/scm (without trailing slash) */
+ @Override
+ String getUrl() {
+ return urls.inClusterBase().toString()
+ }
+
+ /** In-cluster repo prefix: …/scm//[] */
+ @Override
+ String repoPrefix() {
+ return urls.inClusterRepoPrefix()
+ }
+
+
+ /** …/scm/// */
+ @Override
+ String repoUrl(String repoTarget, RepoUrlScope scope) {
+ switch (scope) {
+ case RepoUrlScope.CLIENT:
+ return urls.clientRepoUrl(repoTarget)
+ case RepoUrlScope.IN_CLUSTER:
+ return urls.inClusterRepoUrl(repoTarget)
+ default:
+ return urls.inClusterRepoUrl(repoTarget)
+ }
+ }
+
+ @Override
+ String getProtocol() {
+ return urls.inClusterBase().scheme // e.g. "http"
+ }
+
+ @Override
+ String getHost() {
+ return urls.inClusterBase().host // e.g. "scmm.ns.svc.cluster.local"
+ }
+
+
+ /** …/scm/api/v2/metrics/prometheus — client-side, typically scraped externally */
+ @Override
+ URI prometheusMetricsEndpoint() {
+ return urls.prometheusEndpoint()
+ }
+
+ // --- helpers ---
+ private static Permission.Role mapToScmManager(AccessRole role) {
+ switch (role) {
+ case AccessRole.READ: return Permission.Role.READ
+ case AccessRole.WRITE: return Permission.Role.WRITE
+ case AccessRole.MAINTAIN:
+ // SCM-manager doesn't know MAINTAIN -> downgrade to WRITE
+ log.warn("SCM-Manager: Mapping MAINTAIN → WRITE")
+ return Permission.Role.WRITE
+ case AccessRole.ADMIN: return Permission.Role.OWNER
+ case AccessRole.OWNER: return Permission.Role.OWNER
+ }
+ }
+
+ private static boolean handle201or409(Response> response, String what) {
+ int code = response.code()
+ if (code == 409) {
+ log.debug("${what} already exists — ignoring (HTTP 409)")
+ return false
+ } else if (code != 201) {
+ throw new RuntimeException("Could not create ${what}" +
+ "HTTP Details: ${response.code()} ${response.message()}: ${response.errorBody().string()}")
+ }
+ return true// because its created
+ }
+
+ /** Test-only constructor (package-private on purpose). */
+ ScmManager(Config config, ScmManagerConfig scmmConfig,
+ ScmManagerUrlResolver urls,
+ ScmManagerApiClient apiClient) {
+ this.scmmConfig = Objects.requireNonNull(scmmConfig, "scmmConfig must not be null")
+ this.urls = Objects.requireNonNull(urls, "urls must not be null")
+ this.apiClient = apiClient ?: new ScmManagerApiClient(
+ urls.clientApiBase().toString(),
+ scmmConfig.credentials,
+ Objects.requireNonNull(config, "config must not be null").application.insecure
+ )
+ }
+
+ //TODO when git abctraction feature is ready, we will create before merge to main a branch, that
+ // contain this code as preservation for oop
+ /* ============================= SETUP FOR LATER ===========================================
+void waitForScmmAvailable(int timeoutSeconds = 60, int intervalMillis = 2000) {
+ long startTime = System.currentTimeMillis()
+ long timeoutMillis = timeoutSeconds * 1000L
+
+ while (System.currentTimeMillis() - startTime < timeoutMillis) {
+ try {
+ def call = this.scmmApiClient.generalApi().checkScmmAvailable()
+ def response = call.execute()
+
+ if (response.successful) {
+ return
+ } else {
+ println "SCM-Manager not ready yet: HTTP ${response.code()}"
+ }
+ } 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 setup(){
+ setupInternalScm(this.namespace)
+ setupHelm()
+ installScmmPlugins()
+ configureJenkinsPlugin()
+}
+
+void setupInternalScm(String namespace) {
+ this.namespace = namespace
+ setInternalUrl()
+}
+
+//TODO URL handling by object
+String setInternalUrl() {
+ this.url="http://scmm.${namespace}.svc.cluster.local/scm"
+}
+
+void setupHelm() {
+ def templatedMap = templateToMap(HELM_VALUES_PATH, [
+ host : scmmConfig.ingress,
+ remote : config.application.remote,
+ username : this.scmmConfig.credentials.username,
+ password : this.scmmConfig.credentials.password,
+ helm : this.scmmConfig.helm,
+ releaseName: releaseName
+ ])
+
+ def helmConfig = this.scmmConfig.helm
+ def mergedMap = MapUtils.deepMerge(helmConfig.values, templatedMap)
+ def tempValuesPath = fileSystemUtils.writeTempFile(mergedMap)
+
+ this.deployer.deployFeature(
+ helmConfig.repoURL,
+ 'scm-manager',
+ helmConfig.chart,
+ helmConfig.version,
+ namespace,
+ releaseName,
+ tempValuesPath
+ )
+ waitForScmmAvailable()
+}
+
+//TODO System.env to config Object
+def installScmmPlugins(Boolean restart = true) {
+
+ if (System.getenv('SKIP_PLUGINS')?.toLowerCase() == 'true') {
+ log.info("Skipping SCM plugin installation due to SKIP_PLUGINS=true")
+ return
+ }
+
+ if (System.getenv('SKIP_RESTART')?.toLowerCase() == 'true') {
+ log.info("Skipping SCMM restart due to SKIP_RESTART=true")
+ restart = false
+ }
+
+ 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"
+ ]
+ def jenkinsUrl = System.getenv('JENKINS_URL_FOR_SCMM')
+ if (jenkinsUrl) {
+ pluginNames.add("scm-jenkins-plugin")
+ }
+
+ for (String pluginName : pluginNames) {
+ log.info("Installing Plugin ${pluginName} ...")
+
+ try {
+ def response = scmmApiClient.pluginApi().install(pluginName, restart).execute()
+
+ if (!response.isSuccessful()) {
+ def message = "Installing Plugin '${pluginName}' failed with status: ${response.code()} - ${response.message()}"
+ log.error(message)
+ throw new RuntimeException(message)
+ } else {
+ log.info("Successfully installed plugin '${pluginName}'")
+ }
+ } catch (Exception e) {
+ log.error("Installing Plugin '${pluginName}' failed with error: ${e.message}", e)
+ throw new RuntimeException("Installing Plugin '${pluginName}' failed", e)
+ }
+ }
+}
+
+*/
+}
\ 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
new file mode 100644
index 000000000..033d02ba1
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/ScmManagerUrlResolver.groovy
@@ -0,0 +1,128 @@
+package com.cloudogu.gitops.git.providers.scmmanager
+
+import com.cloudogu.gitops.config.Config
+import com.cloudogu.gitops.features.git.config.util.ScmManagerConfig
+import com.cloudogu.gitops.utils.K8sClient
+import com.cloudogu.gitops.utils.NetworkingUtils
+import groovy.util.logging.Slf4j
+
+@Slf4j
+class ScmManagerUrlResolver {
+
+ private final Config config
+ private final ScmManagerConfig scmm
+ private final K8sClient k8s
+ private final NetworkingUtils net
+
+ private final String releaseName = 'scmm'
+ private URI cachedClusterBind
+
+ ScmManagerUrlResolver(Config config, ScmManagerConfig scmm, K8sClient k8s, NetworkingUtils net) {
+ this.config = config
+ this.scmm = scmm
+ this.k8s = k8s
+ this.net = net
+ }
+
+
+ // ---------- Public API used by ScmManager ----------
+
+ /** Client base …/scm (no trailing slash) */
+ URI clientBase() { noTrailSlash(ensureScm(clientBaseRaw())) }
+
+ /** Client API base …/scm/api/ */
+ URI clientApiBase() { withSlash(clientBase()).resolve("api/") }
+
+ /** Client repo base …/scm/repo (no trailing slash) */
+ URI clientRepoBase() { noTrailSlash(withSlash(clientBase()).resolve("${root()}/")) }
+
+
+ /** In-cluster base …/scm (no trailing slash) */
+ URI inClusterBase() { noTrailSlash(ensureScm(inClusterBaseRaw())) }
+
+ /** In-cluster repo prefix …/scm/repo/[] */
+ String inClusterRepoPrefix() {
+ def prefix = (config.application.namePrefix ?: "").strip()
+ def base = withSlash(inClusterBase())
+ def url = withSlash(base.resolve(root()))
+
+ return URI.create(url.toString() + prefix).toString()
+ }
+
+ /** In-cluster repo URL …/scm/repo// */
+ String inClusterRepoUrl(String repoTarget) {
+ def repo = repoTarget.strip()
+ noTrailSlash(withSlash(inClusterBase()).resolve("${root()}/${repo}/")).toString()
+ }
+
+ /** Client repo URL …/scm/repo// (no trailing slash) */
+ String clientRepoUrl(String repoTarget) {
+ def repo = repoTarget.strip()
+ noTrailSlash(withSlash(clientRepoBase()).resolve("${repo}/")).toString()
+ }
+
+ /** …/scm/api/v2/metrics/prometheus */
+ URI prometheusEndpoint() { withSlash(clientBase()).resolve("api/v2/metrics/prometheus") }
+
+ // ---------- Base resolution ----------
+
+ private URI clientBaseRaw() {
+ if (Boolean.TRUE == scmm.internal)
+ return config.application.runningInsideK8s ? serviceDnsBase() : nodePortBase()
+ return externalBase()
+ }
+
+ private URI inClusterBaseRaw() {
+ return scmm.internal ? serviceDnsBase() : externalBase()
+ }
+
+ private URI serviceDnsBase() {
+ System.out.println("serviceDnsBase namespace: " + scmm.namespace)
+ def namespace = (scmm.namespace ?: "scm-manager").strip()
+
+
+ URI.create("http://scmm.${namespace}.svc.cluster.local")
+ }
+
+ private URI externalBase() {
+ def url = (scmm.url ?: "").strip()
+ if (url) return URI.create(url)
+
+ def ingress = (scmm.ingress ?: "").strip()
+ if (ingress) return URI.create("http://${ingress}")
+ throw new IllegalArgumentException("Either scmm.url or scmm.ingress must be set when internal=false")
+ }
+
+ private URI nodePortBase() {
+ if (cachedClusterBind) return cachedClusterBind
+
+ def namespace = (scmm.namespace ?: "scm-manager").strip()
+
+ final def port = k8s.waitForNodePort(releaseName, namespace)
+ final def host = net.findClusterBindAddress()
+ cachedClusterBind = new URI("http://${host}:${port}")
+ return cachedClusterBind
+ }
+
+ // ---------- Helpers ----------
+
+ private String root() {
+ (scmm.rootPath ?: "repo").strip()
+ }
+
+ private static URI ensureScm(URI u) {
+ def us = withSlash(u)
+ def path = us.path ?: ""
+ path.endsWith("/scm/") ? us : us.resolve("scm/")
+ }
+
+ private static URI withSlash(URI u) {
+ def s = u.toString()
+ s.endsWith('/') ? u : URI.create(s + '/')
+ }
+
+ private static URI noTrailSlash(URI u) {
+ def s = u.toString()
+ s.endsWith('/') ? URI.create(s.substring(0, s.length() - 1)) : u
+ }
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/scmm/api/AuthorizationInterceptor.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/AuthorizationInterceptor.groovy
similarity index 91%
rename from src/main/groovy/com/cloudogu/gitops/scmm/api/AuthorizationInterceptor.groovy
rename to src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/AuthorizationInterceptor.groovy
index 2396a7b56..28d799bf3 100644
--- a/src/main/groovy/com/cloudogu/gitops/scmm/api/AuthorizationInterceptor.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/AuthorizationInterceptor.groovy
@@ -1,4 +1,4 @@
-package com.cloudogu.gitops.scmm.api
+package com.cloudogu.gitops.git.providers.scmmanager.api
import okhttp3.Credentials
@@ -23,4 +23,4 @@ class AuthorizationInterceptor implements Interceptor {
return chain.proceed(newRequest)
}
-}
+}
\ No newline at end of file
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..76450db8b
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/PluginApi.groovy
@@ -0,0 +1,19 @@
+package com.cloudogu.gitops.git.providers.scmmanager.api
+
+import okhttp3.ResponseBody
+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("api/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/scmm/api/Repository.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/Repository.groovy
similarity index 92%
rename from src/main/groovy/com/cloudogu/gitops/scmm/api/Repository.groovy
rename to src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/Repository.groovy
index bb9c5f62c..9dea6dd14 100644
--- a/src/main/groovy/com/cloudogu/gitops/scmm/api/Repository.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/Repository.groovy
@@ -1,4 +1,4 @@
-package com.cloudogu.gitops.scmm.api
+package com.cloudogu.gitops.git.providers.scmmanager.api
class Repository {
final String name
diff --git a/src/main/groovy/com/cloudogu/gitops/scmm/api/RepositoryApi.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/RepositoryApi.groovy
similarity index 85%
rename from src/main/groovy/com/cloudogu/gitops/scmm/api/RepositoryApi.groovy
rename to src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/RepositoryApi.groovy
index 6db6a981e..59871b48c 100644
--- a/src/main/groovy/com/cloudogu/gitops/scmm/api/RepositoryApi.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/RepositoryApi.groovy
@@ -1,5 +1,6 @@
-package com.cloudogu.gitops.scmm.api
+package com.cloudogu.gitops.git.providers.scmmanager.api
+import com.cloudogu.gitops.git.providers.scmmanager.Permission
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.*
@@ -15,4 +16,4 @@ interface RepositoryApi {
@POST("v2/repositories/{namespace}/{name}/permissions/")
@Headers("Content-Type: application/vnd.scmm-repositoryPermission+json")
Call createPermission(@Path("namespace") String namespace, @Path("name") String name, @Body Permission permission)
-}
+}
\ No newline at end of file
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
new file mode 100644
index 000000000..e974b3cd6
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/ScmManagerApi.groovy
@@ -0,0 +1,17 @@
+package com.cloudogu.gitops.git.providers.scmmanager.api
+
+import retrofit2.Call
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.Headers
+import retrofit2.http.PUT
+
+interface ScmManagerApi {
+
+ @GET("api/v2")
+ Call checkScmmAvailable()
+
+ @PUT("api/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
new file mode 100644
index 000000000..f6d058485
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/ScmManagerApiClient.groovy
@@ -0,0 +1,48 @@
+package com.cloudogu.gitops.git.providers.scmmanager.api
+
+
+import com.cloudogu.gitops.config.Credentials
+import com.cloudogu.gitops.dependencyinjection.HttpClientFactory
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.jackson.JacksonConverterFactory
+
+/**
+ * Parent class for all SCMM Apis that lazily creates the APIs
+ */
+class ScmManagerApiClient {
+ Credentials credentials
+ OkHttpClient okHttpClient
+ String url
+
+ ScmManagerApiClient(String url, Credentials credentials, Boolean isInsecure) {
+ this.url = url
+ this.credentials = credentials
+ this.okHttpClient = HttpClientFactory.buildOkHttpClient(credentials, isInsecure)
+ }
+
+ UsersApi usersApi() {
+ return retrofit().create(UsersApi)
+ }
+
+ RepositoryApi repositoryApi() {
+ return retrofit().create(RepositoryApi)
+ }
+
+ ScmManagerApi generalApi() {
+ return retrofit().create(ScmManagerApi)
+ }
+
+ PluginApi pluginApi() {
+ return retrofit().create(PluginApi)
+ }
+
+ protected Retrofit retrofit() {
+ return new Retrofit.Builder()
+ .baseUrl(this.url)
+ .client(okHttpClient)
+ // Converts HTTP body objects from groovy to JSON
+ .addConverterFactory(JacksonConverterFactory.create())
+ .build()
+ }
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/ScmManagerUser.groovy b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/ScmManagerUser.groovy
new file mode 100644
index 000000000..c33af243a
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/ScmManagerUser.groovy
@@ -0,0 +1,11 @@
+package com.cloudogu.gitops.git.providers.scmmanager.api
+
+class ScmManagerUser {
+ String name
+ String displayName
+ String mail
+ boolean external = false
+ String password
+ boolean active = true
+ Map _links = [:]
+}
\ No newline at end of file
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
new file mode 100644
index 000000000..671996d89
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/UsersApi.groovy
@@ -0,0 +1,18 @@
+package com.cloudogu.gitops.git.providers.scmmanager.api
+
+import okhttp3.ResponseBody
+import retrofit2.Call
+import retrofit2.http.Body
+import retrofit2.http.DELETE
+import retrofit2.http.Headers
+import retrofit2.http.POST
+import retrofit2.http.Path
+
+interface UsersApi {
+ @DELETE("v2/users/{id}")
+ Call delete(@Path("id") String id)
+
+ @Headers(["Content-Type: application/vnd.scmm-user+json;v=2"])
+ @POST("/api/v2/users")
+ Call addUser(@Body ScmManagerUser user)
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/kubernetes/argocd/ArgoApplication.groovy b/src/main/groovy/com/cloudogu/gitops/kubernetes/argocd/ArgoApplication.groovy
index 45addc202..686653777 100644
--- a/src/main/groovy/com/cloudogu/gitops/kubernetes/argocd/ArgoApplication.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/kubernetes/argocd/ArgoApplication.groovy
@@ -1,7 +1,7 @@
package com.cloudogu.gitops.kubernetes.argocd
-import com.cloudogu.gitops.scmm.ScmmRepo
+import com.cloudogu.gitops.git.GitRepo
import com.cloudogu.gitops.utils.TemplatingEngine
import groovy.util.logging.Slf4j
@@ -50,7 +50,7 @@ class ArgoApplication {
return new File(outputDir, filename)
}
- void generate(ScmmRepo repo, String subfolder) {
+ void generate(GitRepo repo, String subfolder) {
log.debug("Generating ArgoCDApplication for name='${name}', namespace='${namespace}''")
def outputDir = Path.of(repo.absoluteLocalRepoTmpDir, subfolder).toFile()
diff --git a/src/main/groovy/com/cloudogu/gitops/kubernetes/rbac/RbacDefinition.groovy b/src/main/groovy/com/cloudogu/gitops/kubernetes/rbac/RbacDefinition.groovy
index c86c693dd..adbba381d 100644
--- a/src/main/groovy/com/cloudogu/gitops/kubernetes/rbac/RbacDefinition.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/kubernetes/rbac/RbacDefinition.groovy
@@ -1,7 +1,7 @@
package com.cloudogu.gitops.kubernetes.rbac
import com.cloudogu.gitops.config.Config
-import com.cloudogu.gitops.scmm.ScmmRepo
+import com.cloudogu.gitops.git.GitRepo
import com.cloudogu.gitops.utils.TemplatingEngine
import groovy.util.logging.Slf4j
@@ -15,7 +15,7 @@ class RbacDefinition {
private String namespace
private List serviceAccounts = []
private String subfolder = "rbac"
- private ScmmRepo repo
+ private GitRepo repo
private Config config
private final TemplatingEngine templater = new TemplatingEngine()
@@ -48,7 +48,7 @@ class RbacDefinition {
return this
}
- RbacDefinition withRepo(ScmmRepo repo) {
+ RbacDefinition withRepo(GitRepo repo) {
this.repo = repo
return this
}
@@ -84,4 +84,4 @@ class RbacDefinition {
)
}
-}
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/okhttp/ScmManagerAPI.groovy b/src/main/groovy/com/cloudogu/gitops/okhttp/ScmManagerAPI.groovy
new file mode 100644
index 000000000..7d677ac7f
--- /dev/null
+++ b/src/main/groovy/com/cloudogu/gitops/okhttp/ScmManagerAPI.groovy
@@ -0,0 +1,4 @@
+package com.cloudogu.gitops.okhttp
+
+class ScmManagerAPI {
+}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/scmm/ScmUrlResolver.groovy b/src/main/groovy/com/cloudogu/gitops/scmm/ScmUrlResolver.groovy
deleted file mode 100644
index ede122dd0..000000000
--- a/src/main/groovy/com/cloudogu/gitops/scmm/ScmUrlResolver.groovy
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.cloudogu.gitops.scmm
-
-import com.cloudogu.gitops.config.Config
-
-class ScmUrlResolver {
- /**
- * Returns the tenant/namespace base URL **without** a trailing slash.
- *
- * Why no trailing slash?
- * - Callers often append further segments (e.g., "/repo//" or ".git").
- * Returning a slash here easily leads to double slashes ("//") or brittle
- * template logic.
- */
- static String tenantBaseUrl(Config config) {
- switch (config.scmm.provider) {
- case "scm-manager":
- // scmmBaseUri ends with /scm/
- return scmmBaseUri(config).resolve("${config.scmm.rootPath}/${config.application.namePrefix}").toString()
- case "gitlab":
- // for GitLab, do not append /scm/
- return externalHost(config).resolve("${config.application.namePrefix}${config.scmm.rootPath}").toString()
- default:
- throw new IllegalArgumentException("Unknown SCM provider: ${config.scmm.provider}")
- }
- }
-
- /**
- * External host base URL, ALWAYS ending with "/".
- *
- * Source:
- * - Uses config.scmm.url if present; otherwise builds from config.scmm.protocol + "://" + config.scmm.host.
- *
- * Notes:
- * - This method does NOT strip paths (e.g., "/scm"). If config.scmm.url includes a path, it will be preserved;
- * only the trailing "/" is enforced.
- * - Intended as a base for later URI.resolve() calls.
- *
- */
- static URI externalHost(Config config) {
- def urlString = (config.scmm?.url ?: "${config.scmm.protocol}://${config.scmm.host}" as String).strip()
- def uri = URI.create(urlString)
- if (uri.toString().endsWith("/")) {
- return uri
- } else {
- return URI.create(uri.toString() + "/")
- }
- }
-
- /**
- * Service base URL (SCM-Manager incl. "/scm/", ALWAYS ending with "/").
- *
- * Source:
- * - Internal: "http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local/scm/"
- * - External: config.scmm.url (must already include the context path, e.g., "/scm")
- *
- * Notes:
- * - Ensures a trailing "/" so java.net.URI.resolve() preserves the "/scm" segment; without it, a base may be treated as a file.
- * - Does NOT strip paths from config.scmm.url; any provided path is preserved—only the trailing "/" is enforced.
- * - Intended as a base for subsequent URI.resolve(...) calls.
- * - Guarantee: always ends with "/".
- *
- * Throws:
- * - IllegalArgumentException if external mode is selected (config.scmm.internal = false) but config.scmm.url is empty.
- */
- static URI scmmBaseUri(Config config) {
- if (config.scmm.internal) {
- return new URI("http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local/scm/")
- }
-
- def urlString = config.scmm?.url?.strip() ?: ""
- if (!urlString) {
- throw new IllegalArgumentException("config.scmm.url must be set when config.scmm.internal = false")
- }
- def uri = URI.create(urlString)
- // ensure a trailing slash
- if (uri.toString().endsWith("/")) {
- return uri
- } else {
- return URI.create(uri.toString() + "/")
- }
- }
-
- static String scmmRepoUrl(Config config, String repoNamespaceAndName) {
- return scmmBaseUri(config).resolve("${config.scmm.rootPath}/${repoNamespaceAndName}").toString()
- }
-}
diff --git a/src/main/groovy/com/cloudogu/gitops/scmm/ScmmRepo.groovy b/src/main/groovy/com/cloudogu/gitops/scmm/ScmmRepo.groovy
deleted file mode 100644
index 1736992c0..000000000
--- a/src/main/groovy/com/cloudogu/gitops/scmm/ScmmRepo.groovy
+++ /dev/null
@@ -1,234 +0,0 @@
-package com.cloudogu.gitops.scmm
-
-import com.cloudogu.gitops.config.Config
-import com.cloudogu.gitops.scmm.api.Permission
-import com.cloudogu.gitops.scmm.api.Repository
-import com.cloudogu.gitops.scmm.api.ScmmApiClient
-import com.cloudogu.gitops.scmm.jgit.InsecureCredentialProvider
-import com.cloudogu.gitops.utils.FileSystemUtils
-import com.cloudogu.gitops.utils.TemplatingEngine
-import groovy.util.logging.Slf4j
-import org.eclipse.jgit.api.Git
-import org.eclipse.jgit.api.PushCommand
-import org.eclipse.jgit.transport.ChainingCredentialsProvider
-import org.eclipse.jgit.transport.CredentialsProvider
-import org.eclipse.jgit.transport.RefSpec
-import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider
-import retrofit2.Response
-
-@Slf4j
-class ScmmRepo {
-
- static final String NAMESPACE_3RD_PARTY_DEPENDENCIES = '3rd-party-dependencies'
-
- private String scmmRepoTarget
- private String username
- private String password
- private String scmmUrl
- private String absoluteLocalRepoTmpDir
- protected FileSystemUtils fileSystemUtils
- private boolean insecure
- private Git gitMemoization = null
- private String gitName
- private String gitEmail
- private String rootPath
- private String scmProvider
- private Config config
-
- Boolean isCentralRepo
-
- ScmmRepo(Config config, String scmmRepoTarget, FileSystemUtils fileSystemUtils, Boolean isCentralRepo = false) {
- def tmpDir = File.createTempDir()
- tmpDir.deleteOnExit()
- this.isCentralRepo = isCentralRepo
- this.username = !this.isCentralRepo ? config.scmm.username : config.multiTenant.username
- this.password = !this.isCentralRepo ? config.scmm.password : config.multiTenant.password
-
- //switching from normal scm path to the central path
- this.scmmUrl = "${config.scmm.protocol}://${config.scmm.host}"
- if(this.isCentralRepo) {
- boolean useInternal = config.multiTenant.internal
- String internalUrl = "http://scmm.${config.multiTenant.centralSCMamespace}.svc.cluster.local/scm"
- String externalUrl = config.multiTenant.centralScmUrl.toString()
-
- this.scmmUrl = useInternal ? internalUrl : externalUrl
- }
-
-
- this.scmmRepoTarget = scmmRepoTarget.startsWith(NAMESPACE_3RD_PARTY_DEPENDENCIES) ? scmmRepoTarget :
- "${config.application.namePrefix}${scmmRepoTarget}"
-
- this.absoluteLocalRepoTmpDir = tmpDir.absolutePath
- this.fileSystemUtils = fileSystemUtils
- this.insecure = config.application.insecure
- this.gitName = config.application.gitName
- this.gitEmail = config.application.gitEmail
- this.scmProvider = config.scmm.provider
- this.rootPath = config.scmm.rootPath
- this.config = config
- }
-
- String getAbsoluteLocalRepoTmpDir() {
- return absoluteLocalRepoTmpDir
- }
-
- String getScmmRepoTarget() {
- return scmmRepoTarget
- }
-
- void cloneRepo() {
- log.debug("Cloning $scmmRepoTarget repo")
- Git.cloneRepository()
- .setURI(getGitRepositoryUrl())
- .setDirectory(new File(absoluteLocalRepoTmpDir))
- .setCredentialsProvider(getCredentialProvider())
- .call()
- }
-
- /**
- * @return true if created, false if already exists. Throw exception on all other errors
- */
- boolean create(String description, ScmmApiClient scmmApiClient, boolean initialize = true) {
- def namespace = scmmRepoTarget.split('/', 2)[0]
- def repoName = scmmRepoTarget.split('/', 2)[1]
-
- def repositoryApi = scmmApiClient.repositoryApi()
- def repo = new Repository(namespace, repoName, description)
- log.debug("Creating repo: ${namespace}/${repoName}")
- def createResponse = repositoryApi.create(repo, initialize).execute()
- handleResponse(createResponse, repo)
-
- def permission = new Permission(config.scmm.gitOpsUsername as String, Permission.Role.WRITE)
- def permissionResponse = repositoryApi.createPermission(namespace, repoName, permission).execute()
- return handleResponse(permissionResponse, permission, "for repo $namespace/$repoName")
- }
-
- private static boolean handleResponse(Response response, Object body, String additionalMessage = '') {
- if (response.code() == 409) {
- // Here, we could consider sending another request for changing the existing object to become proper idempotent
- log.debug("${body.class.simpleName} already exists ${additionalMessage}, ignoring: ${body}")
- return false // because repo exists
- } else if (response.code() != 201) {
- throw new RuntimeException("Could not create ${body.class.simpleName} ${additionalMessage}.\n${body}\n" +
- "HTTP Details: ${response.code()} ${response.message()}: ${response.errorBody().string()}")
- }
- return true// because its created
- }
-
- void writeFile(String path, String content) {
- def file = new File("$absoluteLocalRepoTmpDir/$path")
- fileSystemUtils.createDirectory(file.parent)
- file.createNewFile()
- file.text = content
- }
-
- void copyDirectoryContents(String srcDir, FileFilter fileFilter = null) {
- if (!srcDir) {
- println "Source directory is not defined. Nothing to copy?"
- return
- }
-
- log.debug("Initializing repo $scmmRepoTarget with content of folder $srcDir")
- String absoluteSrcDirLocation = srcDir
- if (!new File(absoluteSrcDirLocation).isAbsolute()) {
- absoluteSrcDirLocation = fileSystemUtils.getRootDir() + "/" + srcDir
- }
- fileSystemUtils.copyDirectory(absoluteSrcDirLocation, absoluteLocalRepoTmpDir, fileFilter)
- }
-
- void replaceTemplates(Map parameters) {
- new TemplatingEngine().replaceTemplates(new File(absoluteLocalRepoTmpDir), parameters)
- }
-
- def commitAndPush(String commitMessage, String tag = null, String refSpec = 'HEAD:refs/heads/main') {
- log.debug("Adding files to repo: ${scmmRepoTarget}")
- getGit()
- .add()
- .addFilepattern(".")
- .call()
-
- if (getGit().status().call().hasUncommittedChanges()) {
- log.debug("Committing repo: ${scmmRepoTarget}")
- getGit()
- .commit()
- .setSign(false)
- .setMessage(commitMessage)
- .setAuthor(gitName, gitEmail)
- .setCommitter(gitName, gitEmail)
- .call()
-
- def pushCommand = createPushCommand(refSpec)
-
- if (tag) {
- log.debug("Setting tag '${tag}' on repo: ${scmmRepoTarget}")
- // Delete existing tags first to get idempotence
- getGit().tagDelete().setTags(tag).call()
- getGit()
- .tag()
- .setName(tag)
- .call()
-
- pushCommand.setPushTags()
- }
-
- log.debug("Pushing repo: ${scmmRepoTarget}, refSpec: ${refSpec}")
- pushCommand.call()
- } else {
- log.debug("No changes after add, nothing to commit or push on repo: ${scmmRepoTarget}")
- }
- }
-
- /**
- * Push all refs, i.e. all tags and branches
- */
- def pushAll(boolean force = false) {
- createPushCommand('refs/*:refs/*').setForce(force).call()
- }
-
- def pushRef(String ref, String targetRef, boolean force = false) {
- createPushCommand("${ref}:${targetRef}").setForce(force).call()
- }
-
- def pushRef(String ref, boolean force = false) {
- pushRef(ref, ref, force)
- }
-
- private PushCommand createPushCommand(String refSpec) {
- getGit()
- .push()
- .setRemote(getGitRepositoryUrl())
- .setRefSpecs(new RefSpec(refSpec))
- .setCredentialsProvider(getCredentialProvider())
- }
-
- private CredentialsProvider getCredentialProvider() {
- if (scmProvider == "gitlab") {
- username = "oauth2"
- }
- def passwordAuthentication = new UsernamePasswordCredentialsProvider(username, password)
-
- if (!insecure) {
- return passwordAuthentication
- }
-
- return new ChainingCredentialsProvider(new InsecureCredentialProvider(), passwordAuthentication)
- }
-
- private Git getGit() {
- if (gitMemoization != null) {
- return gitMemoization
- }
-
- return gitMemoization = Git.open(new File(absoluteLocalRepoTmpDir))
- }
-
- String getGitRepositoryUrl() {
- return "${scmmUrl}/${rootPath}/${scmmRepoTarget}"
- }
- /**
- * Delete all files in this repository
- */
- void clearRepo() {
- fileSystemUtils.deleteFilesExcept(new File(absoluteLocalRepoTmpDir), ".git")
- }
-}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/scmm/ScmmRepoProvider.groovy b/src/main/groovy/com/cloudogu/gitops/scmm/ScmmRepoProvider.groovy
deleted file mode 100644
index a5d3e68ae..000000000
--- a/src/main/groovy/com/cloudogu/gitops/scmm/ScmmRepoProvider.groovy
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.cloudogu.gitops.scmm
-
-import com.cloudogu.gitops.config.Config
-import com.cloudogu.gitops.utils.FileSystemUtils
-import jakarta.inject.Singleton
-
-@Singleton
-class ScmmRepoProvider {
- protected final Config config
- protected final FileSystemUtils fileSystemUtils
-
- ScmmRepoProvider(Config config, FileSystemUtils fileSystemUtils) {
- this.fileSystemUtils = fileSystemUtils
- this.config = config
- }
-
- ScmmRepo getRepo(String repoTarget) {
- return new ScmmRepo(config ,repoTarget, fileSystemUtils)
- }
-
- ScmmRepo getRepo(String repoTarget, Boolean isCentralRepo) {
- return new ScmmRepo(config ,repoTarget, fileSystemUtils, isCentralRepo)
- }
-}
\ No newline at end of file
diff --git a/src/main/groovy/com/cloudogu/gitops/scmm/api/ScmmApiClient.groovy b/src/main/groovy/com/cloudogu/gitops/scmm/api/ScmmApiClient.groovy
deleted file mode 100644
index 811b5c5ec..000000000
--- a/src/main/groovy/com/cloudogu/gitops/scmm/api/ScmmApiClient.groovy
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.cloudogu.gitops.scmm.api
-
-import com.cloudogu.gitops.config.Config
-import jakarta.inject.Named
-import jakarta.inject.Singleton
-import okhttp3.OkHttpClient
-import retrofit2.Retrofit
-import retrofit2.converter.jackson.JacksonConverterFactory
-
-/**
- * Parent class for all SCMM Apis that lazily creates the APIs, so the latest SCMM-URL is used
- */
-@Singleton
-class ScmmApiClient {
- Config config
- OkHttpClient okHttpClient
-
- ScmmApiClient(Config config, @Named("scmm") OkHttpClient okHttpClient) {
- this.config = config
- this.okHttpClient = okHttpClient
- }
-
- UsersApi usersApi() {
- return retrofit().create(UsersApi)
- }
-
- RepositoryApi repositoryApi() {
- return retrofit().create(RepositoryApi)
- }
-
- protected Retrofit retrofit() {
- return new Retrofit.Builder()
- .baseUrl(config.scmm.url + '/api/')
- .client(okHttpClient)
- // Converts HTTP body objects from groovy to JSON
- .addConverterFactory(JacksonConverterFactory.create())
- .build()
- }
-}
diff --git a/src/main/groovy/com/cloudogu/gitops/scmm/api/UsersApi.groovy b/src/main/groovy/com/cloudogu/gitops/scmm/api/UsersApi.groovy
deleted file mode 100644
index 2120be3a9..000000000
--- a/src/main/groovy/com/cloudogu/gitops/scmm/api/UsersApi.groovy
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.cloudogu.gitops.scmm.api
-
-import okhttp3.ResponseBody
-import retrofit2.Call
-import retrofit2.http.DELETE
-import retrofit2.http.Path
-
-interface UsersApi {
- @DELETE("v2/users/{id}")
- Call delete(@Path("id") String id)
-}
diff --git a/src/main/groovy/com/cloudogu/gitops/utils/AirGappedUtils.groovy b/src/main/groovy/com/cloudogu/gitops/utils/AirGappedUtils.groovy
index befa33700..e21a56d22 100644
--- a/src/main/groovy/com/cloudogu/gitops/utils/AirGappedUtils.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/utils/AirGappedUtils.groovy
@@ -1,13 +1,13 @@
package com.cloudogu.gitops.utils
import com.cloudogu.gitops.config.Config
-import com.cloudogu.gitops.scmm.ScmmRepo
-import com.cloudogu.gitops.scmm.ScmmRepoProvider
-import com.cloudogu.gitops.scmm.api.ScmmApiClient
+import com.cloudogu.gitops.config.Config.HelmConfig
+import com.cloudogu.gitops.features.git.GitHandler
+import com.cloudogu.gitops.git.GitRepo
+import com.cloudogu.gitops.git.GitRepoFactory
import groovy.util.logging.Slf4j
import groovy.yaml.YamlSlurper
import jakarta.inject.Singleton
-
import java.nio.file.Path
@Slf4j
@@ -15,37 +15,39 @@ import java.nio.file.Path
class AirGappedUtils {
private Config config
- private ScmmRepoProvider repoProvider
- private ScmmApiClient scmmApiClient
+ private GitRepoFactory repoProvider
private FileSystemUtils fileSystemUtils
private HelmClient helmClient
+ private GitHandler gitHandler
- AirGappedUtils(Config config, ScmmRepoProvider repoProvider, ScmmApiClient scmmApiClient,
- FileSystemUtils fileSystemUtils, HelmClient helmClient) {
+ AirGappedUtils(Config config, GitRepoFactory repoProvider,
+ FileSystemUtils fileSystemUtils, HelmClient helmClient, GitHandler gitHandler) {
this.config = config
this.repoProvider = repoProvider
- this.scmmApiClient = scmmApiClient
this.fileSystemUtils = fileSystemUtils
this.helmClient = helmClient
+ this.gitHandler = gitHandler
}
/**
* In air-gapped mode, the chart's dependencies can't be resolved.
* As helm does not provide an option for changing them interactively, we push the charts into a separate repo.
* We alter these repos to resolve dependencies locally from SCM.
- *
+ *
* @return the repo namespace and name
*/
- String mirrorHelmRepoToGit(Config.HelmConfig helmConfig) {
+ String mirrorHelmRepoToGit(HelmConfig helmConfig) {
String repoName = helmConfig.chart
- String namespace = ScmmRepo.NAMESPACE_3RD_PARTY_DEPENDENCIES
- def repoNamespaceAndName = "${namespace}/${repoName}"
- def localHelmChartFolder = "${config.application.localHelmChartFolder}/${repoName}"
+ String namespace = GitRepo.NAMESPACE_3RD_PARTY_DEPENDENCIES
+ String repoNamespaceAndName = "${namespace}/${repoName}"
+ String localHelmChartFolder = "${config.application.localHelmChartFolder}/${repoName}"
validateChart(repoNamespaceAndName, localHelmChartFolder, repoName)
- ScmmRepo repo = repoProvider.getRepo(repoNamespaceAndName)
- repo.create("Mirror of Helm chart $repoName from ${helmConfig.repoURL}", scmmApiClient)
+ GitRepo repo = repoProvider.getRepo(repoNamespaceAndName, gitHandler.tenant)
+
+ repo.createRepositoryAndSetPermission(repoNamespaceAndName, "Mirror of Helm chart $repoName from ${helmConfig.repoURL}", false)
+
repo.cloneRepo()
repo.copyDirectoryContents(localHelmChartFolder)
@@ -72,17 +74,17 @@ class AirGappedUtils {
}
}
- private Map localizeChartYaml(ScmmRepo scmmRepo) {
- log.debug("Preparing repo ${scmmRepo.scmmRepoTarget} for air-gapped use: Changing Chart.yaml to resolve depencies locally")
+ private Map localizeChartYaml(GitRepo gitRepo) {
+ log.debug("Preparing repo ${gitRepo.repoTarget} for air-gapped use: Changing Chart.yaml to resolve depencies locally")
- def chartYamlPath = Path.of(scmmRepo.absoluteLocalRepoTmpDir, 'Chart.yaml')
+ def chartYamlPath = Path.of(gitRepo.absoluteLocalRepoTmpDir, 'Chart.yaml')
Map chartYaml = new YamlSlurper().parse(chartYamlPath) as Map
- Map chartLock = parseChartLockIfExists(scmmRepo)
+ Map chartLock = parseChartLockIfExists(gitRepo)
List