Skip to content

Commit 5a8cbe4

Browse files
authored
Merge pull request #294 from cloudogu/feature/content-repo-mirror
Content Repos: MIRROR + createJenkinsJob
2 parents 255c1b5 + 1f31b1a commit 5a8cbe4

File tree

53 files changed

+1260
-1070
lines changed

Some content is hidden

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

53 files changed

+1260
-1070
lines changed

docs/configuration.schema.json

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,18 +164,18 @@
164164
"items" : {
165165
"type" : "object",
166166
"properties" : {
167-
"folderBased" : {
167+
"createJenkinsJob" : {
168168
"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"
169+
"description" : "If true, creates a Jenkins job, if jenkinsfile exists in one of the content repo's branches."
170170
},
171-
"overrideMode" : {
171+
"overwriteMode" : {
172172
"anyOf" : [ {
173173
"type" : "null"
174174
}, {
175175
"type" : "string",
176176
"enum" : [ "INIT", "RESET", "UPGRADE" ]
177177
} ],
178-
"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"
178+
"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."
179179
},
180180
"password" : {
181181
"type" : [ "string", "null" ],
@@ -187,19 +187,32 @@
187187
},
188188
"ref" : {
189189
"type" : [ "string", "null" ],
190-
"description" : "Reference for a specific branch, tag, or commit. Emtpy defaults to default branch of the repo"
190+
"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!"
191191
},
192192
"target" : {
193193
"type" : [ "string", "null" ],
194-
"description" : "Target path for the repository"
194+
"description" : "Target repo for the repository in the for of namespace/name. Must contain one slash to separate namespace from name."
195+
},
196+
"targetRef" : {
197+
"type" : [ "string", "null" ],
198+
"description" : "Reference for a specific branch or tag in the target repo of a MIRROR or COPY repo. If ref is a tag, targetRef is treated as tag as well. Except: targetRef is full ref like refs/heads/my-branch or refs/tags/my-tag. Empty defaults to the source ref."
195199
},
196200
"templating" : {
197201
"type" : [ "boolean", "null" ],
198202
"description" : "When true, template all files ending in .ftl within the repo"
199203
},
204+
"type" : {
205+
"anyOf" : [ {
206+
"type" : "null"
207+
}, {
208+
"type" : "string",
209+
"enum" : [ "FOLDER_BASED", "COPY", "MIRROR" ]
210+
} ],
211+
"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.)"
212+
},
200213
"url" : {
201214
"type" : [ "string", "null" ],
202-
"description" : "URL of the content repo"
215+
"description" : "URL of the content repo. Mandatory for each type."
203216
},
204217
"username" : {
205218
"type" : [ "string", "null" ],

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class Application {
4747
statics : new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_32).build().getStaticModels()
4848
]))
4949
}
50+
config.content.namespaces = namespaces.toList()
5051

5152
//iterates over all FeatureWithImages and gets their namespaces
5253
namespaces.addAll(this.features

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,21 +85,23 @@ class GitopsPlaygroundCliMainScripted {
8585

8686
def airGappedUtils = new AirGappedUtils(config, scmmRepoProvider, scmmApiClient, fileSystemUtils, helmClient)
8787
def networkingUtils = new NetworkingUtils()
88-
88+
89+
def jenkins = new Jenkins(config, executor, fileSystemUtils, new GlobalPropertyManager(jenkinsApiClient),
90+
new JobManager(jenkinsApiClient), new UserManager(jenkinsApiClient),
91+
new PrometheusConfigurator(jenkinsApiClient), helmStrategy, k8sClient, networkingUtils)
92+
8993
context.registerSingleton(new Application(config, [
9094
new Registry(config, fileSystemUtils, k8sClient, helmStrategy),
9195
new ScmManager(config, executor, fileSystemUtils, helmStrategy, k8sClient, networkingUtils),
92-
new Jenkins(config, executor, fileSystemUtils, new GlobalPropertyManager(jenkinsApiClient),
93-
new JobManager(jenkinsApiClient), new UserManager(jenkinsApiClient),
94-
new PrometheusConfigurator(jenkinsApiClient), helmStrategy, k8sClient, networkingUtils),
96+
jenkins,
9597
new ArgoCD(config, k8sClient, helmClient, fileSystemUtils, scmmRepoProvider),
9698
new IngressNginx(config, fileSystemUtils, deployer, k8sClient, airGappedUtils),
9799
new CertManager(config, fileSystemUtils, deployer, k8sClient, airGappedUtils),
98100
new Mailhog(config, fileSystemUtils, deployer, k8sClient, airGappedUtils),
99101
new PrometheusStack(config, fileSystemUtils, deployer, k8sClient, airGappedUtils, scmmRepoProvider),
100102
new ExternalSecretsOperator(config, fileSystemUtils, deployer, k8sClient, airGappedUtils),
101103
new Vault(config, fileSystemUtils, k8sClient, deployer, airGappedUtils),
102-
new Content(config, k8sClient, scmmRepoProvider, scmmApiClient),
104+
new Content(config, k8sClient, scmmRepoProvider, scmmApiClient, jenkins),
103105
]))
104106
}
105107
}

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

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package com.cloudogu.gitops.config
33
import com.cloudogu.gitops.utils.FileSystemUtils
44
import groovy.util.logging.Slf4j
55

6+
import static com.cloudogu.gitops.config.Config.ContentRepoType
7+
import static com.cloudogu.gitops.config.Config.ContentSchema.ContentRepositorySchema
8+
69
@Slf4j
710
class ApplicationConfigurator {
811

@@ -268,18 +271,41 @@ class ApplicationConfigurator {
268271

269272
static void validateContent(Config config) {
270273
config.content.repos.each { repo ->
274+
271275
if (!repo.url) {
272276
throw new RuntimeException("content.repos requires a url parameter.")
273277
}
274-
if (!repo.folderBased && !repo.target) {
275-
throw new RuntimeException("content.repos.folderBased: false requires folder content.repos.target to be set. ${repo.url}")
276-
}
277278
if (repo.target) {
278279
if (repo.target.count('/') == 0) {
279-
throw new RuntimeException("content.target needs / to separate namespace/group from repo name. ${repo.url}")
280+
throw new RuntimeException("content.target needs / to separate namespace/group from repo name. Repo: ${repo.url}")
280281
}
282+
}
281283

282-
284+
switch (repo.type) {
285+
case ContentRepoType.COPY:
286+
if (!repo.target) {
287+
throw new RuntimeException("content.repos.type ${ContentRepoType.COPY} requires content.repos.target to be set. Repo: ${repo.url}")
288+
}
289+
break
290+
case ContentRepoType.FOLDER_BASED:
291+
if (repo.target) {
292+
throw new RuntimeException("content.repos.type ${ContentRepoType.FOLDER_BASED} does not support target parameter. Repo: ${repo.url}")
293+
}
294+
if (repo.targetRef) {
295+
throw new RuntimeException("content.repos.type ${ContentRepoType.FOLDER_BASED} does not support targetRef parameter. Repo: ${repo.url}")
296+
}
297+
break
298+
case ContentRepoType.MIRROR:
299+
if (!repo.target) {
300+
throw new RuntimeException("content.repos.type ${ContentRepoType.MIRROR} requires content.repos.target to be set. Repo: ${repo.url}")
301+
}
302+
if (repo.path != ContentRepositorySchema.DEFAULT_PATH) {
303+
throw new RuntimeException("content.repos.type ${ContentRepoType.MIRROR} does not support path. Current path: ${repo.path}. Repo: ${repo.url}")
304+
}
305+
if (repo.templating) {
306+
throw new RuntimeException("content.repos.type ${ContentRepoType.MIRROR} does not support templating. Repo: ${repo.url}")
307+
}
308+
break
283309
}
284310
}
285311
}

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,23 @@ class Config {
102102
Map<String, Object> variables = [:]
103103

104104
static class ContentRepositorySchema {
105+
static final String DEFAULT_PATH = '.'
106+
// This is controversial. Forcing users to explicitly choose a type requires them to understand the concept
107+
// of types. What would be a good default? The simplest use case ist MIRROR from url to target.
108+
// COPY and FOLDER_BASED are more advanced use cases. So we choose MIRROR as the default.
109+
static final ContentRepoType DEFAULT_TYPE = ContentRepoType.MIRROR
110+
105111
@JsonPropertyDescription(CONTENT_REPO_URL_DESCRIPTION)
106112
String url = ''
107113

108114
@JsonPropertyDescription(CONTENT_REPO_PATH_DESCRIPTION)
109-
String path = '.'
115+
String path = DEFAULT_PATH
110116

111117
@JsonPropertyDescription(CONTENT_REPO_REF_DESCRIPTION)
112118
String ref = ''
119+
120+
@JsonPropertyDescription(CONTENT_REPO_TARGET_REF_DESCRIPTION)
121+
String targetRef = ''
113122

114123
@JsonPropertyDescription(CONTENT_REPO_USERNAME_DESCRIPTION)
115124
String username = ''
@@ -120,14 +129,17 @@ class Config {
120129
@JsonPropertyDescription(CONTENT_REPO_TEMPLATING_DESCRIPTION)
121130
Boolean templating = false
122131

123-
@JsonPropertyDescription(CONTENT_REPO_FOLDER_BASED_REPOS_DESCRIPTION)
124-
Boolean folderBased = false
132+
@JsonPropertyDescription(CONTENT_REPO_TYPE_DESCRIPTION)
133+
ContentRepoType type = DEFAULT_TYPE
125134

126135
@JsonPropertyDescription(CONTENT_REPO_TARGET_DESCRIPTION)
127136
String target = ''
128137

129-
@JsonPropertyDescription(CONTENT_REPO_TARGET_OVERRIDE_MODE)
130-
OverrideMode overrideMode = OverrideMode.INIT // Defensively use init to not override existing files by default
138+
@JsonPropertyDescription(CONTENT_REPO_TARGET_OVERWRITE_MODE_DESCRIPTION)
139+
OverwriteMode overwriteMode = OverwriteMode.INIT // Defensively use init to not override existing files by default
140+
141+
@JsonPropertyDescription(CONTENT_REPO_CREATE_JENKINS_JOB_DESCRIPTION)
142+
Boolean createJenkinsJob = false
131143
}
132144
}
133145

@@ -784,16 +796,19 @@ class Config {
784796
}
785797
}
786798

799+
static enum ContentRepoType {
800+
FOLDER_BASED, COPY, MIRROR
801+
}
787802

788803
static enum VaultMode {
789804
dev, prod
790805
}
791806

792807
/**
793808
* This defines, how customer repos will be updated.
794-
* See {@link ConfigConstants#CONTENT_REPO_TARGET_OVERRIDE_MODE}
809+
* See {@link ConfigConstants#CONTENT_REPO_TARGET_OVERWRITE_MODE_DESCRIPTION}
795810
*/
796-
static enum OverrideMode {
811+
static enum OverwriteMode {
797812
INIT, RESET, UPGRADE
798813
}
799814

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ interface ConfigConstants {
55
public static final String BINARY_NAME = 'apply-ng'
66
public static final String APP_NAME = 'gitops-playground (GOP)'
77
public static final String APP_DESCRIPTION = 'CLI-tool to deploy gitops-playground.'
8-
8+
99
// group registry
1010
String REGISTRY_ENABLE_DESCRIPTION = 'Installs a simple cluster-local registry for demonstration purposes. Warning: Registry does not provide authentication!'
1111
String REGISTRY_DESCRIPTION = 'Config parameters for Registry'
@@ -18,7 +18,7 @@ interface ConfigConstants {
1818
String REGISTRY_PROXY_URL_DESCRIPTION = 'The url of your proxy-registry. Used in pipelines to authorize pull base images. Use in conjunction with petclinic base image. Used in helm charts when create-image-pull-secrets is set. Use in conjunction with helm.*image fields.'
1919
String REGISTRY_PROXY_USERNAME_DESCRIPTION = 'Use with registry-proxy-url, added to Jenkins as credentials and created as pull secrets, when create-image-pull-secrets is set.'
2020
String REGISTRY_PROXY_PASSWORD_DESCRIPTION = 'Use with registry-proxy-url, added to Jenkins as credentials and created as pull secrets, when create-image-pull-secrets is set.'
21-
21+
2222
String REGISTRY_USERNAME_RO_DESCRIPTION = 'Optional alternative username for registry-url with read-only permissions that is used when create-image-pull-secrets is set.'
2323
String REGISTRY_PASSWORD_RO_DESCRIPTION = 'Optional alternative password for registry-url with read-only permissions that is used when create-image-pull-secrets is set.'
2424
String REGISTRY_CREATE_IMAGE_PULL_SECRETS_DESCRIPTION = 'Create image pull secrets for registry and proxy-registry for all GOP namespaces and helm charts. Uses proxy-username, read-only-username or registry-username (in this order). Use this if your cluster is not auto-provisioned with credentials for your private registries or if you configure individual helm images to be pulled from the proxy-registry that requires authentication.'
@@ -31,15 +31,17 @@ interface ConfigConstants {
3131
String CONTENT_EXAMPLES_DESCRIPTION = 'Deploy example content: source repos, GitOps repos, Jenkins Job, Argo CD apps/project'
3232
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'
3333
String CONTENT_REPO_DESCRIPTION = "Content repos to push into target environment"
34-
String CONTENT_REPO_URL_DESCRIPTION = "URL of the content repo"
34+
String CONTENT_REPO_URL_DESCRIPTION = "URL of the content repo. Mandatory for each type."
3535
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. Emtpy defaults to default branch of the repo"
36+
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!"
37+
String CONTENT_REPO_TARGET_REF_DESCRIPTION = "Reference for a specific branch or tag in the target repo of a MIRROR or COPY repo. If ref is a tag, targetRef is treated as tag as well. Except: targetRef is full ref like refs/heads/my-branch or refs/tags/my-tag. Empty defaults to the source ref."
3738
String CONTENT_REPO_USERNAME_DESCRIPTION = "Username to authenticate against content repo"
3839
String CONTENT_REPO_PASSWORD_DESCRIPTION = "Password to authenticate against content repo"
3940
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.\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"
41+
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.)"
42+
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."
43+
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."
44+
String CONTENT_REPO_CREATE_JENKINS_JOB_DESCRIPTION = "If true, creates a Jenkins job, if jenkinsfile exists in one of the content repo's branches."
4345
String CONTENT_VARIABLES_DESCRIPTION = "Additional variables to use in custom templates."
4446

4547
// group jenkins

0 commit comments

Comments
 (0)