Skip to content

Commit cec82a7

Browse files
Merge pull request #281 from cloudogu/feature/contentRepos
Implement MVP for content repos
2 parents 852b55c + 17ead83 commit cec82a7

File tree

45 files changed

+1242
-263
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1242
-263
lines changed

Dockerfile

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ WORKDIR /app
1313
COPY .mvn/ /app/.mvn/
1414
COPY mvnw /app/
1515
COPY pom.xml /app/
16-
RUN ./mvnw dependency:resolve-plugins dependency:go-offline -B
16+
RUN ./mvnw dependency:resolve-plugins dependency:go-offline -B
1717

1818
FROM graal AS maven-build
1919
ENV MAVEN_OPTS='-Dmaven.repo.local=/mvn'
@@ -25,7 +25,7 @@ COPY compiler.groovy /app
2525
COPY .git /app/.git
2626

2727
WORKDIR /app
28-
# Exclude code not needed in productive image
28+
# Exclude code not needed in productive image
2929
RUN cd /app/src/main/groovy/com/cloudogu/gitops/cli/ \
3030
&& rm GenerateJsonSchema.groovy \
3131
&& rm GitopsPlaygroundCliMainScripted.groovy
@@ -37,7 +37,7 @@ RUN mv $(ls -S target/*.jar | head -n 1) /app/gitops-playground.jar
3737

3838
FROM alpine AS downloader
3939
RUN apk add curl grep
40-
# When updating,
40+
# When updating,
4141
# * also update the checksum found at https://dl.k8s.io/release/v${K8S_VERSION}/bin/linux/amd64/kubectl.sha256
4242
# * also update in init-cluster.sh. vars.tf, Config.groovy and apply.sh
4343
# When upgrading to 1.26 we can verify the kubectl signature with cosign!
@@ -61,11 +61,11 @@ ENV HOME=/tmp
6161
WORKDIR /tmp
6262

6363
# Helm
64-
RUN curl --location --fail --retry 20 --retry-connrefused --retry-all-errors --output helm.tar.gz https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz
64+
RUN curl --location --fail --retry 20 --retry-connrefused --retry-all-errors --output helm.tar.gz https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz
6565
RUN curl --location --fail --retry 20 --retry-connrefused --retry-all-errors --output helm.tar.gz.asc https://github.com/helm/helm/releases/download/v${HELM_VERSION}/helm-v${HELM_VERSION}-linux-amd64.tar.gz.asc
6666
RUN tar -xf helm.tar.gz
6767
RUN set -o pipefail && curl --location --fail --retry 20 --retry-connrefused --retry-all-errors \
68-
https://raw.githubusercontent.com/helm/helm/main/KEYS | gpg --import --batch --no-default-keyring --keyring /tmp/keyring.gpg
68+
https://raw.githubusercontent.com/helm/helm/main/KEYS | gpg --import --batch --no-default-keyring --keyring /tmp/keyring.gpg
6969
RUN gpgv --keyring /tmp/keyring.gpg helm.tar.gz.asc helm.tar.gz
7070
RUN mv linux-amd64/helm /dist/usr/local/bin
7171
ENV PATH=$PATH:/dist/usr/local/bin
@@ -79,7 +79,7 @@ RUN mv /tmp/kubectl /dist/usr/local/bin/kubectl
7979

8080
# External Repos used in GOP
8181
WORKDIR /dist/gitops/repos
82-
RUN git clone --bare https://github.com/cloudogu/spring-petclinic.git
82+
RUN git clone --bare https://github.com/cloudogu/spring-petclinic.git
8383
RUN git clone --bare https://github.com/cloudogu/spring-boot-helm-chart.git
8484
RUN git clone --bare https://github.com/cloudogu/gitops-build-lib.git
8585
RUN git clone --bare https://github.com/cloudogu/ces-build-lib.git
@@ -118,7 +118,7 @@ RUN mv /dist/app/src /dist-dev/src && \
118118
chmod a=rwx -R /dist-dev/src && \
119119
rm -r /dist-dev/src/main/groovy/com/cloudogu/gitops/graal
120120
COPY --from=maven-build /app/gitops-playground.jar /dist-dev/gitops-playground.jar
121-
# Remove compiled GOP code from jar to avoid duplicate in dev image, allowing for scripting.
121+
# Remove compiled GOP code from jar to avoid duplicate in dev image, allowing for scripting.
122122
# Keep generated class Version, to avoid ClassNotFoundException.
123123
RUN zip -d /dist-dev/gitops-playground.jar 'com/cloudogu/gitops/*' -x com/cloudogu/gitops/cli/Version.class
124124

@@ -132,7 +132,7 @@ FROM graal AS native-image
132132
ENV MAVEN_OPTS='-Dmaven.repo.local=/mvn'
133133
RUN microdnf install gnupg
134134

135-
# Provide binaries used by apply-ng, so our runs with native-image-agent dont fail
135+
# Provide binaries used by apply-ng, so our runs with native-image-agent dont fail
136136
# with "java.io.IOException: Cannot run program "kubectl"..." etc.
137137
RUN microdnf install iproute
138138

docs/configuration.schema.json

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,64 @@
150150
"examples" : {
151151
"type" : [ "boolean", "null" ],
152152
"description" : "Deploy example content: source repos, GitOps repos, Jenkins Job, Argo CD apps/project"
153+
},
154+
"namespaces" : {
155+
"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",
156+
"type" : [ "array", "null" ],
157+
"items" : {
158+
"type" : "string"
159+
}
160+
},
161+
"repos" : {
162+
"description" : "Content repos to push into target environment",
163+
"type" : [ "array", "null" ],
164+
"items" : {
165+
"type" : "object",
166+
"properties" : {
167+
"folderBased" : {
168+
"type" : [ "boolean", "null" ],
169+
"description" : "When true, interpret the folder structure of each repo as repos. That is, root folder becomes namespace in SCM, sub folders become repository names in SCM"
170+
},
171+
"overrideMode" : {
172+
"anyOf" : [ {
173+
"type" : "null"
174+
}, {
175+
"type" : "string",
176+
"enum" : [ "INIT", "RESET", "UPGRADE" ]
177+
} ],
178+
"description" : "This defines, how customer repos will be updated.\nINIT - push only if repo does not exist (SCMM Return value 409).\nRESET - delete alle files after cloning source - new files are deleted\nUPGRADE - clone and copy - existing files will be overwritten, new files are kept"
179+
},
180+
"password" : {
181+
"type" : [ "string", "null" ],
182+
"description" : "Password to authenticate against content repo"
183+
},
184+
"path" : {
185+
"type" : [ "string", "null" ],
186+
"description" : "Path within the content repo to process"
187+
},
188+
"ref" : {
189+
"type" : [ "string", "null" ],
190+
"description" : "Reference for a specific branch, tag, or commit"
191+
},
192+
"target" : {
193+
"type" : [ "string", "null" ],
194+
"description" : "Target path for the repository"
195+
},
196+
"templating" : {
197+
"type" : [ "boolean", "null" ],
198+
"description" : "When true, template all files ending in .ftl within the repo"
199+
},
200+
"url" : {
201+
"type" : [ "string", "null" ],
202+
"description" : "URL of the content repo"
203+
},
204+
"username" : {
205+
"type" : [ "string", "null" ],
206+
"description" : "Username to authenticate against content repo"
207+
}
208+
},
209+
"additionalProperties" : false
210+
}
153211
}
154212
},
155213
"additionalProperties" : false,

docs/developers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ The versions are also specified in the `Config.groovy` file, so it is recommende
1313

1414
## Table of contents
1515

16-
<!-- Update with ` doctoc --notitle docs/developers.md --maxlevel 4 `. See https://github.com/thlorenz/doctoc -->
16+
<!-- Update with `doctoc --notitle docs/developers.md --maxlevel 4`. See https://github.com/thlorenz/doctoc -->
1717
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
1818
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
1919

src/main/groovy/com/cloudogu/gitops/Application.groovy

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.cloudogu.gitops
22

33
import com.cloudogu.gitops.config.Config
4+
import com.cloudogu.gitops.utils.TemplatingEngine
5+
import freemarker.template.Configuration
6+
import freemarker.template.DefaultObjectWrapperBuilder
47
import groovy.util.logging.Slf4j
58
import jakarta.inject.Singleton
69

@@ -35,13 +38,14 @@ class Application {
3538

3639
void setNamespaceListToConfig(Config config) {
3740
Set<String> namespaces = new HashSet<>()
38-
String namePrefix = config.application.namePrefix
39-
40-
if (config.content.examples) {
41-
namespaces.addAll(Arrays.asList(
42-
namePrefix + "example-apps-staging",
43-
namePrefix + "example-apps-production"
44-
))
41+
def engine = new TemplatingEngine()
42+
43+
config.content.namespaces.each { String ns ->
44+
namespaces.add(engine.template(ns, [
45+
config : config,
46+
// Allow for using static classes inside the templates
47+
statics : new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_32).build().getStaticModels()
48+
]))
4549
}
4650

4751
//iterates over all FeatureWithImages and gets their namespaces

src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScripted.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class GitopsPlaygroundCliMainScripted {
9292
new Jenkins(config, executor, fileSystemUtils, new GlobalPropertyManager(jenkinsApiClient),
9393
new JobManager(jenkinsApiClient), new UserManager(jenkinsApiClient),
9494
new PrometheusConfigurator(jenkinsApiClient), helmStrategy, k8sClient, networkingUtils),
95-
new Content(config, k8sClient),
95+
new Content(config, k8sClient, scmmRepoProvider, scmmApiClient),
9696
new ArgoCD(config, k8sClient, helmClient, fileSystemUtils, scmmRepoProvider),
9797
new IngressNginx(config, fileSystemUtils, deployer, k8sClient, airGappedUtils),
9898
new CertManager(config, fileSystemUtils, deployer, k8sClient, airGappedUtils),

src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,12 @@ class ApplicationConfigurator {
5252
if (newConfig.features.ingressNginx.active && !newConfig.application.baseUrl) {
5353
log.warn("Ingress-controller is activated without baseUrl parameter. Services will not be accessible by hostnames. To avoid this use baseUrl with ingress. ")
5454
}
55-
56-
if (newConfig.content.examples && !newConfig.registry.active) {
57-
throw new RuntimeException("content.examples requires either registry.active or registry.url")
55+
if (newConfig.content.examples) {
56+
if (!newConfig.registry.active) {
57+
throw new RuntimeException("content.examples requires either registry.active or registry.url")
58+
}
59+
String prefix = newConfig.application.namePrefix
60+
newConfig.content.namespaces += [ prefix+"example-apps-staging", prefix+"example-apps-production"]
5861
}
5962
}
6063

@@ -251,6 +254,7 @@ class ApplicationConfigurator {
251254
void validateConfig(Config configToSet) {
252255
validateScmmAndJenkinsAreBothSet(configToSet)
253256
validateMirrorReposHelmChartFolderSet(configToSet)
257+
validateContent(configToSet)
254258
}
255259

256260
private void validateMirrorReposHelmChartFolderSet(Config configToSet) {
@@ -261,6 +265,17 @@ class ApplicationConfigurator {
261265
"LOCAL_HELM_CHART_FOLDER='charts' after running 'scripts/downloadHelmCharts.sh' from the repo")
262266
}
263267
}
268+
269+
static void validateContent(Config config) {
270+
config.content.repos.each { repo ->
271+
if (!repo.url) {
272+
throw new RuntimeException('content.repos requires a url parameter')
273+
}
274+
if (!repo.folderBased && !repo.target) {
275+
throw new RuntimeException('content.repos.folderBased: false requires folder content.repos.target to be set')
276+
}
277+
}
278+
}
264279

265280
private void validateScmmAndJenkinsAreBothSet(Config configToSet) {
266281
if (configToSet.jenkins.active &&

src/main/groovy/com/cloudogu/gitops/config/Config.groovy

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,50 @@ class Config {
8585
@JsonPropertyDescription(CONTENT_DESCRIPTION)
8686
@Mixin
8787
ContentSchema content = new ContentSchema()
88-
88+
8989
class ContentSchema {
90+
9091
@Option(names = ['--content-examples'], description = CONTENT_EXAMPLES_DESCRIPTION)
9192
@JsonPropertyDescription(CONTENT_EXAMPLES_DESCRIPTION)
9293
Boolean examples = false
94+
95+
@JsonPropertyDescription(CONTENT_NAMESPACES_DESCRIPTION)
96+
List<String> namespaces = []
97+
98+
@JsonPropertyDescription(CONTENT_REPO_DESCRIPTION)
99+
List<ContentRepositorySchema> repos = []
100+
101+
static class ContentRepositorySchema {
102+
@JsonPropertyDescription(CONTENT_REPO_URL_DESCRIPTION)
103+
String url = ''
104+
105+
@JsonPropertyDescription(CONTENT_REPO_PATH_DESCRIPTION)
106+
String path = '.'
107+
108+
@JsonPropertyDescription(CONTENT_REPO_REF_DESCRIPTION)
109+
String ref = 'main'
110+
111+
@JsonPropertyDescription(CONTENT_REPO_USERNAME_DESCRIPTION)
112+
String username = ''
113+
114+
@JsonPropertyDescription(CONTENT_REPO_PASSWORD_DESCRIPTION)
115+
String password = ''
116+
117+
@JsonPropertyDescription(CONTENT_REPO_TEMPLATING_DESCRIPTION)
118+
Boolean templating = false
119+
120+
@JsonPropertyDescription(CONTENT_REPO_FOLDER_BASED_REPOS_DESCRIPTION)
121+
Boolean folderBased = false
122+
123+
@JsonPropertyDescription(CONTENT_REPO_TARGET_DESCRIPTION)
124+
String target = ''
125+
126+
@JsonPropertyDescription(CONTENT_REPO_TARGET_OVERRIDE_MODE)
127+
OverrideMode overrideMode = OverrideMode.INIT // default is init a new repository
128+
}
93129
}
94130

131+
95132
static class HelmConfig {
96133
@JsonPropertyDescription(HELM_CONFIG_CHART_DESCRIPTION)
97134
String chart = ''
@@ -749,6 +786,14 @@ class Config {
749786
dev, prod
750787
}
751788

789+
/**
790+
* This defines, how customer repos will be updated.
791+
*/
792+
static enum OverrideMode {
793+
INIT, RESET, UPGRADE
794+
}
795+
796+
752797
private static final ObjectMapper objectMapper = new ObjectMapper()
753798
.registerModule(new SimpleModule().addSerializer(GString, new JsonSerializer<GString>() {
754799
@Override

src/main/groovy/com/cloudogu/gitops/config/ConfigConstants.groovy

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ interface ConfigConstants {
2929

3030
// Content
3131
String CONTENT_EXAMPLES_DESCRIPTION = 'Deploy example content: source repos, GitOps repos, Jenkins Job, Argo CD apps/project'
32+
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'
33+
String CONTENT_REPO_DESCRIPTION = "Content repos to push into target environment"
34+
String CONTENT_REPO_URL_DESCRIPTION = "URL of the content repo"
35+
String CONTENT_REPO_PATH_DESCRIPTION = "Path within the content repo to process"
36+
String CONTENT_REPO_REF_DESCRIPTION = "Reference for a specific branch, tag, or commit"
37+
String CONTENT_REPO_USERNAME_DESCRIPTION = "Username to authenticate against content repo"
38+
String CONTENT_REPO_PASSWORD_DESCRIPTION = "Password to authenticate against content repo"
39+
String CONTENT_REPO_TEMPLATING_DESCRIPTION = "When true, template all files ending in .ftl within the repo"
40+
String CONTENT_REPO_FOLDER_BASED_REPOS_DESCRIPTION = "When true, interpret the folder structure of each repo as repos. That is, root folder becomes namespace in SCM, sub folders become repository names in SCM"
41+
String CONTENT_REPO_TARGET_DESCRIPTION = "Target path for the repository"
42+
String CONTENT_REPO_TARGET_OVERRIDE_MODE = "This defines, how customer repos will be updated.\nINIT - push only if repo does not exist (SCMM Return value 409).\nRESET - delete alle files after cloning source - new files are deleted\nUPGRADE - clone and copy - existing files will be overwritten, new files are kept"
3243

3344
// group jenkins
3445
String JENKINS_ENABLE_DESCRIPTION = 'Installs Jenkins as CI server'

0 commit comments

Comments
 (0)