Skip to content

Commit

Permalink
Create declarative default configuration
Browse files Browse the repository at this point in the history
Mainly to store e.g. helm chart version in single place.
But also this allows for reading configuration from a file in the future.

Introduce more generic terms monitoring and secrets in internal config.
Can't ue application.yml, because it will not be bundled into static image.
  • Loading branch information
schnatterer committed Oct 28, 2022
1 parent ef07e1c commit 49fe9bd
Show file tree
Hide file tree
Showing 18 changed files with 433 additions and 204 deletions.
2 changes: 1 addition & 1 deletion Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ node('docker') {
docker.image(imageNames[0])
.inside("-e KUBECONFIG=${env.WORKSPACE}/.kube/config " +
" --network=host --entrypoint=''" ) {
sh "/app/scripts/apply.sh --yes --trace --internal-registry-port=${registryPort} --argocd --metrics --vault=dev"
sh "/app/scripts/apply.sh --yes --trace --internal-registry-port=${registryPort} --argocd --monitoring --vault=dev"
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,9 @@ To override each image in all the applications you can use following parameters:
If you are using a remote cluster you can set the `--argocd-url` parameter so that argocd-notification messages have a
link to the corresponding application.
##### Metrics
##### Monitoring
Set the parameter `--metrics` to enable deployment of monitoring and alerting tools like prometheus, grafana and mailhog.
Set the parameter `--monitoring` to enable deployment of monitoring and alerting tools like prometheus, grafana and mailhog.
See [Monitoring tools](#monitoring-tools) for details.
Expand Down Expand Up @@ -391,7 +391,7 @@ The user on the scm has to have privileges to:
### Monitoring tools
Set the parameter `--metrics` so the [kube-prometheus-stack](https://github.com/prometheus-operator/kube-prometheus)
Set the parameter `--monitoring` so the [kube-prometheus-stack](https://github.com/prometheus-operator/kube-prometheus)
via its [helm-chart](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack)
is being deployed including Argo CD dashboards.
Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-lambda</artifactId>
Expand Down
6 changes: 3 additions & 3 deletions scripts/apply.sh
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ function printParameters() {
echo " | --argocd-config-only >> Skips installing argo-cd. Applies ConfigMap and Application manifests to bootstrap existing argo-cd"
echo
echo "Configure additional modules"
echo " | --metrics >> Installs the Kube-Prometheus-Stack for ArgoCD. This includes Prometheus, the Prometheus operator, Grafana and some extra resources"
echo " | --monitoring, --metrics >> Installs the Kube-Prometheus-Stack for ArgoCD. This includes Prometheus, the Prometheus operator, Grafana and some extra resources"
echo
echo " -d | --debug >> Debug output"
echo " -x | --trace >> Debug + Show each command executed (set -x)"
Expand All @@ -848,7 +848,7 @@ function printParameters() {
readParameters() {
COMMANDS=$(getopt \
-o hdxyc \
--long help,fluxv1,fluxv2,argocd,argocd-url:,debug,remote,username:,password:,jenkins-url:,jenkins-username:,jenkins-password:,registry-url:,registry-path:,registry-username:,registry-password:,internal-registry-port:,scmm-url:,scmm-username:,scmm-password:,kubectl-image:,helm-image:,kubeval-image:,helmkubeval-image:,yamllint-image:,trace,insecure,yes,skip-helm-update,argocd-config-only,metrics,vault: \
--long help,fluxv1,fluxv2,argocd,argocd-url:,debug,remote,username:,password:,jenkins-url:,jenkins-username:,jenkins-password:,registry-url:,registry-path:,registry-username:,registry-password:,internal-registry-port:,scmm-url:,scmm-username:,scmm-password:,kubectl-image:,helm-image:,kubeval-image:,helmkubeval-image:,yamllint-image:,trace,insecure,yes,skip-helm-update,argocd-config-only,metrics,monitoring,vault: \
-- "$@")

if [ $? != 0 ]; then
Expand Down Expand Up @@ -922,7 +922,7 @@ readParameters() {
-y | --yes ) ASSUME_YES=true; shift ;;
--skip-helm-update ) SKIP_HELM_UPDATE=true; shift ;;
--argocd-config-only ) ARGOCD_CONFIG_ONLY=true; shift ;;
--metrics ) DEPLOY_METRICS=true; shift;;
--metrics | --monitoring ) DEPLOY_METRICS=true; shift;;
--vault ) shift 2;; # Ignore, used in groovy only
-- ) shift; break ;;
*) break ;;
Expand Down
257 changes: 172 additions & 85 deletions src/main/groovy/com/cloudogu/gitops/ApplicationConfigurator.groovy
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.cloudogu.gitops

import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import com.cloudogu.gitops.utils.FileSystemUtils
import com.cloudogu.gitops.utils.NetworkingUtils
import groovy.json.JsonSlurper
import groovy.util.logging.Slf4j
import ch.qos.logback.classic.Logger
import org.slf4j.LoggerFactory

import static groovy.json.JsonOutput.prettyPrint
Expand All @@ -13,130 +14,216 @@ import static groovy.json.JsonOutput.toJson
@Slf4j
class ApplicationConfigurator {

private Map config
public static final String HELM_IMAGE = "ghcr.io/cloudogu/helm:3.5.4-1"
public static final String DEFAULT_ADMIN_USER = 'admin'
public static final String DEFAULT_ADMIN_PW = 'admin'
private static final Map DEFAULT_VALUES = makeDeeplyImmutable([
registry : [
internal: true, // Set dynamically
url : '',
path : '',
username : '',
password : '',
internalPort: ''
],
jenkins : [
internal: true, // Set dynamically
url : '',
username: DEFAULT_ADMIN_USER,
password: DEFAULT_ADMIN_PW,
urlForScmm: "http://jenkins"
],
scmm : [
internal: true, // Set dynamically
url : '',
username: DEFAULT_ADMIN_USER,
password: DEFAULT_ADMIN_PW,
urlForJenkins : 'http://scmm-scm-manager/scm',
host : '', // Set dynamically
protocol : '' // Set dynamically
],
application: [
remote : false,
insecure : false,
skipHelmUpdate: false,
debug : false,
trace : false,
username : DEFAULT_ADMIN_USER,
password : DEFAULT_ADMIN_PW,
yes : false,
runningInsideK8s : false, // Set dynamically
clusterBindAddress : '' // Set dynamically
],
images : [
kubectl : "lachlanevenson/k8s-kubectl:v1.21.2",
helm : HELM_IMAGE,
kubeval : HELM_IMAGE,
helmKubeval: HELM_IMAGE,
yamllint : "cytopia/yamllint:1.25-0.7"
],
repositories : [
springBootHelmChart: "https://github.com/cloudogu/spring-boot-helm-chart.git",
springPetclinic : "https://github.com/cloudogu/spring-petclinic.git",
gitopsBuildLib : "https://github.com/cloudogu/gitops-build-lib.git",
cesBuildLib : "https://github.com/cloudogu/ces-build-lib.git"
],
features : [
fluxv1 : true,
fluxv2 : true,
argocd : [
active : true,
configOnly: false,
url : ''
],
mail : [
active: true,
helm : [
chart : 'mailhog',
repoURL: 'https://codecentric.github.io/helm-charts',
version: '5.0.1'
]
],
monitoring: [
active: false,
helm : [
chart : 'kube-prometheus-stack',
repoURL: 'https://prometheus-community.github.io/helm-charts',
version: '19.2.2'
]
],
secrets : [
active : false, // Set dynamically
externalSecrets: [
helm: [
chart : 'external-secrets',
repoURL: 'https://charts.external-secrets.io',
version: '0.6.0'
]
],
vault : [
mode: ''
]
],
]
])
Map config
private NetworkingUtils networkingUtils
private FileSystemUtils fileSystemUtils

ApplicationConfigurator(Map config, NetworkingUtils networkingUtils = new NetworkingUtils(), FileSystemUtils fileSystemUtils = new FileSystemUtils()) {
this.config = config
ApplicationConfigurator(NetworkingUtils networkingUtils = new NetworkingUtils(), FileSystemUtils fileSystemUtils = new FileSystemUtils()) {
this.config = DEFAULT_VALUES
this.networkingUtils = networkingUtils
this.fileSystemUtils = fileSystemUtils
}

Map populateConfig() {
// TODO currently only variables were set which are at the beginning of the apply.sh.
// There are still more to check and implement in the main function and elsewhere

log.info("Populating application config with derived options from existing configuration")
setLogLevel()
addInternalStatus()
addAdditionalApplicationConfig()
setScmmConfig()
addServiceUrls()
setDefaultImagesIfNotConfigured()
addRepos()

/**
* Sets config internally and als returns it, fluent interface
*/
Map setConfig(Map configToSet) {
Map newConfig = deepCopy(config)
deepMerge(configToSet, newConfig)

setLogLevel(newConfig)
addAdditionalApplicationConfig(newConfig)
setScmmConfig(newConfig)
addJenkinsConfig(newConfig)

if (newConfig.registry['url'])
newConfig.registry["internal"] = false
if (newConfig['features']['secrets']['vault']['mode'])
newConfig['features']['secrets']['active'] = true

log.debug(prettyPrint(toJson(config)))
return new LinkedHashMap(config)
}

private void addInternalStatus() {
config.jenkins["internal"] = config.jenkins["url"] ? false : true
config.registry["internal"] = config.registry["url"] ? false : true
config = makeDeeplyImmutable(newConfig)
return config
}

private void addAdditionalApplicationConfig() {
log.debug("Setting additional application config")
String appUsername = config.application["username"]
String appPassword = config.application["password"]

if (appUsername == null) {
log.debug("No application username was set. Setting default username: admin")
config.application["username"] = "admin"
}
if (appPassword == null) {
log.debug("No application password was set. Setting default password: admin")
config.application["password"] = "admin"
}
private void addAdditionalApplicationConfig(Map newConfig) {
if (System.getenv("KUBERNETES_SERVICE_HOST")) {
log.debug("installation is running in kubernetes.")
config.application["runningInsideK8s"] = true
} else {
log.debug("installation is not running in kubernetes")
config.application["runningInsideK8s"] = false
newConfig.application["runningInsideK8s"] = true
}
String clusterBindAddress = networkingUtils.findClusterBindAddress()
log.debug("Setting cluster bind Address: " + clusterBindAddress)
config.application["clusterBindAddress"] = clusterBindAddress
newConfig.application["clusterBindAddress"] = clusterBindAddress
}

private void setScmmConfig() {
private void setScmmConfig(Map newConfig) {
log.debug("Adding additional config for SCM-Manager")
config.scmm["internal"] = true
config.scmm["urlForJenkins"] = "http://scmm-scm-manager/scm"

if (config.scmm["url"]) {
if (newConfig.scmm["url"]) {
log.debug("Setting external scmm config")
config.scmm["internal"] = false
config.scmm["urlForJenkins"] = config.scmm["url"]
} else if (config.application["runningInsideK8s"]) {
newConfig.scmm["internal"] = false
newConfig.scmm["urlForJenkins"] = newConfig.scmm["url"]
} else if (newConfig.application["runningInsideK8s"]) {
log.debug("Setting scmm url to k8s service, since installation is running inside k8s")
config.scmm["url"] = networkingUtils.createUrl("scmm-scm-manager.default.svc.cluster.local", "80", "/scm")
newConfig.scmm["url"] = networkingUtils.createUrl("scmm-scm-manager.default.svc.cluster.local", "80", "/scm")
} else {
log.debug("Setting internal scmm configs")
def port = fileSystemUtils.getLineFromFile(fileSystemUtils.getRootDir() + "/scm-manager/values.yaml", "nodePort:").findAll(/\d+/)*.toString().get(0)
String cba = config.application["clusterBindAddress"]
config.scmm["url"] = networkingUtils.createUrl(cba, port, "/scm")
String cba = newConfig.application["clusterBindAddress"]
newConfig.scmm["url"] = networkingUtils.createUrl(cba, port, "/scm")
}

if (config.scmm["internal"]) {
log.debug("Setting the scmm credentials")
if (config.scmm["username"] == null) config.scmm["username"] = config.application["username"]
if (config.scmm["password"] == null) config.scmm["password"] = config.application["password"]
}
String scmmUrl = config.scmm["url"]
String scmmUrl = newConfig.scmm["url"]
log.debug("Getting host and protocol from scmmUrl: " + scmmUrl)
config.scmm["host"] = networkingUtils.getHost(scmmUrl)
config.scmm["protocol"] = networkingUtils.getProtocol(scmmUrl)
newConfig.scmm["host"] = networkingUtils.getHost(scmmUrl)
newConfig.scmm["protocol"] = networkingUtils.getProtocol(scmmUrl)
}

private void addServiceUrls() {
private void addJenkinsConfig(Map newConfig) {
log.debug("Adding additional config for Jenkins")
config.jenkins["urlForScmm"] = "http://jenkins"
}

private void setDefaultImagesIfNotConfigured() {
log.debug("Adding additional config for images")
if (config.images["kubectl"] == null) config.images["kubectl"] = "lachlanevenson/k8s-kubectl:v1.21.2"
if (config.images["helm"] == null) config.images["helm"] = "ghcr.io/cloudogu/helm:3.5.4-1"
if (config.images["kubeval"] == null) config.images["kubeval"] = config.images["helm"]
if (config.images["helmKubeval"] == null) config.images["helmKubeval"] = config.images["helm"]
if (config.images["yamllint"] == null) config.images["yamllint"] = "cytopia/yamllint:1.25-0.7"
}

private void addRepos() {
log.debug("Adding additional config for repos")

config.repositories = [
springBootHelmChart: "https://github.com/cloudogu/spring-boot-helm-chart.git",
springPetclinic : "https://github.com/cloudogu/spring-petclinic.git",
gitopsBuildLib : "https://github.com/cloudogu/gitops-build-lib.git",
cesBuildLib : "https://github.com/cloudogu/ces-build-lib.git"
]
if (newConfig.jenkins["url"]) {
log.debug("Setting external jenkins config")
newConfig.jenkins["internal"] = false
newConfig.jenkins["urlForScmm"] = newConfig.jenkins["url"]
}
// TODO missing external config (see setScmmConfig())
}

private void setLogLevel() {
boolean trace = config.application["trace"]
boolean debug = config.application["debug"]
private void setLogLevel(Map newConfig) {
boolean trace = newConfig.application["trace"]
boolean debug = newConfig.application["debug"]
Logger logger = (Logger) LoggerFactory.getLogger("com.cloudogu.gitops");
if (trace) {
log.info("Setting loglevel to trace")
logger.setLevel(Level.TRACE)
} else if(debug) {
} else if (debug) {
log.info("Setting loglevel to debug")
logger.setLevel(Level.DEBUG);
} else {
logger.setLevel(Level.INFO)
}
}

Map deepCopy(Map input) {
// Lazy mans deep map copy 😬
String json = toJson(input)
return (Map) new JsonSlurper().parseText(json)
}

Map deepMerge(Map src, Map target) {
src.forEach(
(key, value) -> { if (value != null) target.merge(key, value, (oldVal, newVal) -> {
if (oldVal instanceof Map) {
if (!newVal instanceof Map) {
throw new RuntimeException("Can't merge config, different types, map vs other: Map ${oldVal}; Other ${newVal}")
}
return deepMerge(newVal as Map, oldVal)
} else {
return newVal
}
})
})
return target
}

static Map makeDeeplyImmutable(Map map) {
map.forEach((key, value) -> {
if (value instanceof Map) {
map[key] = Collections.unmodifiableMap(value)
}
})
return Collections.unmodifiableMap(map)
}
}
Loading

0 comments on commit 49fe9bd

Please sign in to comment.