diff --git a/README.md b/README.md index 8c2d28a02..5807c16aa 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Kubernetes clusters: * Pipelines: Example applications using [Jenkins](#jenkins) with the [gitops-build-lib](https://github.com/cloudogu/gitops-build-lib) and [SCM-Manager](#scm-manager) * Ingress Controller: [ingress-nginx](https://github.com/kubernetes/ingress-nginx/) * Certificate Management: [cert-manager](#certificate-management) +* [Content Loader](docs/content-loader/content-loader.md): Completely customize what is pushed to Git during installation. + This allows for adding your own end-user or IDP apps, creating repos, adding Argo CD tenants, etc. * Runs on: * local cluster (try it [with only one command](#tldr)), * in the public cloud, diff --git a/docs/content-loader/argocd/argocd/applications/example-tenant.ftl.yaml b/docs/content-loader/argocd/argocd/applications/example-tenant.ftl.yaml new file mode 100644 index 000000000..7f00d338f --- /dev/null +++ b/docs/content-loader/argocd/argocd/applications/example-tenant.ftl.yaml @@ -0,0 +1,23 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: example-tenant + 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: "http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local/scm/repo/${config.application.namePrefix}example-tenant/gitops" + 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/docs/content-loader/argocd/argocd/projects/example-tenant.ftl.yaml b/docs/content-loader/argocd/argocd/projects/example-tenant.ftl.yaml new file mode 100644 index 000000000..2c641da7e --- /dev/null +++ b/docs/content-loader/argocd/argocd/projects/example-tenant.ftl.yaml @@ -0,0 +1,33 @@ +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: example-tenant + namespace: ${config.application.namePrefix}argocd + annotations: +<#if config.features.mail.active> + notifications.argoproj.io/subscribe.email: ${config.features.argocd.emailToUser} + +spec: + description: Contains examples of end-user applications + destinations: + - namespace: ${config.application.namePrefix}example-tenant-production + server: https://kubernetes.default.svc + - namespace: ${config.application.namePrefix}example-tenant-staging + server: https://kubernetes.default.svc + sourceRepos: + - http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local/scm/repo/${config.application.namePrefix}example-tenant/gitops + - http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local/scm/repo/${config.application.namePrefix}example-tenant/nginx-helm-umbrella + - https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami + + # allow to only see application resources from the specified namespace + sourceNamespaces: + - '${config.application.namePrefix}example-tenant-staging' + - '${config.application.namePrefix}example-tenant-production' + + # Allow all namespaced-scoped resources to be created + namespaceResourceWhitelist: + - group: '*' + kind: '*' + + # Deny all cluster-scoped resources from being created. Least privilege. + clusterResourceWhitelist: diff --git a/docs/content-loader/content-hooks-folder-based.png b/docs/content-loader/content-hooks-folder-based.png new file mode 100644 index 000000000..04c06904a Binary files /dev/null and b/docs/content-loader/content-hooks-folder-based.png differ diff --git a/docs/content-loader/content-loader-config.yaml b/docs/content-loader/content-loader-config.yaml new file mode 100644 index 000000000..e1c60fb3f --- /dev/null +++ b/docs/content-loader/content-loader-config.yaml @@ -0,0 +1,55 @@ +# $schema: https://raw.githubusercontent.com/cloudogu/gitops-playground/main/docs/configuration.schema.json +content: + repos: + - url: https://github.com/cloudogu/gitops-build-lib + target: 3rd-party-dependencies/gitops-build-lib + overwriteMode: RESET + - url: https://github.com/cloudogu/ces-build-lib + target: 3rd-party-dependencies/ces-build-lib + overwriteMode: RESET + - url: https://github.com/cloudogu/spring-boot-helm-chart + target: 3rd-party-dependencies/spring-boot-helm-chart + overwriteMode: RESET + - url: https://github.com/cloudogu/spring-petclinic + target: example-tenant/petclinic-plain + ref: feature/gitops_ready + targetRef: main + # ref: b0738b2 + overwriteMode: UPGRADE + createJenkinsJob: true + - url: https://github.com/cloudogu/spring-petclinic + target: example-tenant/petclinic-helm + ref: feature/gitops_ready + targetRef: main + overwriteMode: UPGRADE + createJenkinsJob: true + - url: 'https://github.com/cloudogu/gitops-playground.git' + path: 'docs/content-loader' + ref: main + templating: true + type: FOLDER_BASED + overwriteMode: UPGRADE + + namespaces: +# - ${config.application.namePrefix}example-tenant-production +# - ${config.application.namePrefix}example-tenant-staging + - example-tenant-production + - example-tenant-staging + variables: + umbrella: + nginxAnnotation: 'my value' + some: other + +application: + yes: true + baseUrl: http://localhost +# namePrefix: customer-1 +jenkins: + active: true +registry: + active: true +features: + argocd: + active: true + ingressNginx: + active: true diff --git a/docs/content-loader/content-loader.md b/docs/content-loader/content-loader.md new file mode 100644 index 000000000..c49e192fe --- /dev/null +++ b/docs/content-loader/content-loader.md @@ -0,0 +1,327 @@ +# Content loader Documentation + +This documentation shows the content loader feature and its usage. + +Content loader offers the ability of hooking into the GitOps Playground (GOP) installation process, allowing for +customization of what is pushed to Git. +This can be used to deploy your own content, e.g. your own applications, or adding tenants to Argo CD, etc. + +Example for a GOP content repository: + +- Sample [configuration file](content-loader-config.yaml). +- [Directory structure](.) as an example of a folder-based content repository. + +This document will use the short for "repo" from here on, for brevity. + +# Table of contents + + + + +- [Purpose of content loader](#purpose-of-content-loader) +- [Meaning of the term "content"](#meaning-of-the-term-content) +- [Content loader concepts](#content-loader-concepts) + - [Content repos](#content-repos) + - [Additional options](#additional-options) + - [Different types of content repos](#different-types-of-content-repos) + - [The overrideMode](#the-overridemode) + - [Templating](#templating) +- [TL;DR](#tldr) +- [Example use cases](#example-use-cases) + - [Mirror the entire repo on every call](#mirror-the-entire-repo-on-every-call) + - [Create additional tenant in Argo CD](#create-additional-tenant-in-argo-cd) + - [Mirror/copy repo and add specific files](#mirrorcopy-repo-and-add-specific-files) + + + + +# Purpose of content loader + +You like the idea of automatically rolling out IDPs using GOP but + +* want to initialize it with your own real-world end-user application that is automatically deployed as a turnkey solution, +* want to add multiple tenants with Argo CD, +* want to have different or additional IDP tools, e.g. logging/backup/tracing, etc. + +Then content loader is for you! +It allows you to define content using a folder structure inside a Git repo (so-called content repo) that is picked up +during GOP apply optionally run through a templating engine and then pushed to the target Git. + +# Meaning of the term "content" + +- Currently, GOP (version > 0.11.0) consists of example applications and exercises and their dependencies, + in addition to the actual IDP (ArgoCD, Prometheus, etc.). + ➡️ Turnkey-solution deployed via GitOps pipelines +- We refer to these applications as "content". +- When rolling out GOP, the `--content-examples` parameter leads to example applications being pushed to Git. +- These applications include: + - Code (e.g., repo `argocd/petclinic-helm`) + - Configuration (Argo CD `Application` and YAML resources in the GitOps repo `example-tenant/gitops`) + - Basic configuration of the tenant (Argo CD `AppProject` and `Application` of Applications in the repo `argocd/argocd`) + - Potentially dependencies, e.g., `3rd-party-dependencies/gitops-build-lib` and `3rd-party-dependencies/spring-boot-helm-chart` + - Potentially `Jenkinsfile`s that describe how to build and push images and start the GitOps process by writing resources to Git that are then picked up by ArgoCD. + In addition, a Jenkins job that pulls and executes the `Jenkinsfile`. +- After installing GOP, the example applications are built by Jenkins and deployed by ArgoCD via GitOps. + Finally, these end-user applications can be reached via HTTP(S) via their ingress (turnkey-solution). +- The content loader feature provides the possibility to deliver your own custom content, i.e. real-world applications instead of examples. + +# Content loader concepts + +The content deployed by GOP can be completely defined via configuration. + +This allows for +* changing all Git repos created by GOP. +* adding new Git repos, e.g. for end-user applications (including their dependencies such as Helm charts or build libraries) as well as IDP applications, such as monitoring tools. + +This is done by means of a `content` section within GOP's config file (the one being specified by `--config-file`). +This config holds references to Git repos that contain the actual content to be pushed to Git. + +Here is a schematic example of the `content` section that will be described in the following: + +```yaml +content: + repos: + - url: 'https://...' + path: 'a/b' + ref: branch + templating: true + type: FOLDER_BASED # MIRROR, COPY + overwriteMode: UPGRADE # INIT, RESET + createJenkinsJob: true + - ... + + namespaces: + - prod # templating allowed, e.g. ${config.application.namePrefix}prod + - ... + + variables: # can be used within templates + my: value + another: value + + examples: true # deploy example content described in https://github.com/cloudogu/gitops-playground +``` + +See [here for a full example](content-loader-config.yaml). +The [TL;DR](#tldr) sections shows how to see this example in action. + +## Content repos +- The content is defined in Git repos, known as content repos (`content.repos`) +- There are different `type`s of content repos: `MIRROR`, `COPY`, and `FOLDER_BASED` (see [here](#different-types-of-content-repos) for details). +- For these types of content repos, the `overrideMode` determines how to handle previously existing files in the repo: `INIT`, `UPGRADE`, `RESET` ([see overrideMode](#the-overridemode)). +- Templating with [Freemarker](https://freemarker.apache.org/) can be enabled via `templating: true` for each content repo. + - The templates can access the config and custom variables defined in `content.variables`. + - See [templating](#templating) for more details. +- Multiple content repos can be specified in the `content.repos` field +- GOP merges these repos in the defined order into a directory structure. +- This allows you to overwrite files from all repos created by GOP. + - One use case for this is, for example, a base repo that specifies the basic structure of all GOP instances (e.g. repo structure for tenants) in a cloud environment and more specialized repos that contain specific applications (e.g. per tenant). + - Another use case is to keep the configuration (YAML) in one repo and the code in another to deploy different config with the same code. + Current examples is `petclinic-plain` (see [Mirror/copy example](#mirrorcopy-repo-and-add-specific-files)). +- Existing repos, e.g., `argocd/argocd`, can be extended by `COPY`, and `FOLDER_BASED` content repos. + - ArgoCD `AppProjects` and `Applications` can be defined in the content. + - This also allows you to hook into the configuration of Argo CD and, for example, define tenants. + +## Additional options + +- **Kubernetes namespaces** needed for the content (e.g. `example-tenant-staging`) can be specified via the `content.namespaces` field. + - GOP deploys the namespaces listed therein via GitOps (repo `argocd/cluster-resources`, application `misc`) + - In each namespace, the following is set up: + - the configured ImagePullSecrets + - RBAC resources + - `NetworkPolicies` (e.g. to enable Prometheus to access the metrics). + - The namespaces allow templating, e.g., `‘${config.application.namePrefix}example-tenant-staging’, ‘${config.application.namePrefix}example-tenant-production’` + - Note that you can add arbitrary namespaces. They don't necessarily need to be related to your content. + - With `--openshift` GOP creates `ProjectRequests` instead of `Namespaces`. +- **Jenkins**: Automatic generation of Jenkins jobs based on the content is possible. + When enable for a content repo (via `content.repos.createJenkinsJob` set to `true`) a job is created + - for each SCM Manager namespace found in the content + - that contains a `Jenkinsfile`. +- The **example content** (see [README/Example Applications](../../README.md#example-applications)) can be activated via the `content.examples` field. + This content is defined in the [repo](https://github.com/cloudogu/gitops-playground/) and shipped with the image (does not require internet access at runtime) + + +## Different types of content repos + +There are different types of content repos: `MIRROR`, `COPY`, and `FOLDER_BASED`. +- `MIRROR` (default): The entire content repo is mirrored to the target repo if it does not yet exist ([see overrideMode](#the-overridemode)). +- `COPY`: Only the files (no Git history) are copied to the target repo, then committed and pushed. +- `FOLDER_BASED`: Using the folder structure in the content repo, multiple repos can be created and initialized or changed in the target. + +**Global Properties** + +- `url` (required) — url of the content repo +- `ref` — Git reference that is cloned from content repo (branch, tag, commit). + Defaults: + - `COPY` / `FOLDER_BASED`: Default branch of repo. + - `MIRROR`: All branches and tags of repo +- `overrideMode` (`INIT`, `UPGRADE`, `RESET`) defines how to handle existing files in the target repo ([see overrideMode](#the-overridemode)). +- `username`, `password` — credentials +- `createJenkinsJob` — If `true` a Jenkins job is created for the associated SCM Manager namespace, when the following is also true: + - Jenkins is active in GOP (`--jenkins`), and + - there is a `Jenkinsfile` in one of the content repos or the specified `refs`. + + +### `MIRROR` + +A content repo is mirrored completely (or only a `ref`) to the target repo (including Git history). + +**Caution** +Force push is used here! +Note that by default only new repos are mirrored. +To overwrite `overrideMode: RESET` must be set ([see overrideMode](#the-overridemode)). + +**Note** +The default branch of the source repo is not explicitly set. +If the source repo has a default branch != `main`, it is not set at the moment. +This might be implemented at a later point. + +**Properties** + +- `target` (required) — target repo, e.g. `namespace/name` +- `targetRef` — Git reference in `target` to which it is pushed (branch or tag). + - If `ref `is a tag,` targetRef` is also treated as a tag. + - Exception:` targetRef` is a full ref such as` refs/heads/my-branch` or `refs/tags/my-tag`. + - If `targetRef` is empty, the source `ref` is used by default. + + +### `COPY` + +Only the files (no Git history) are copied and committed to the target repo. + +**Properties** + +- `target` (required) — target repo, e.g. `namespace/name` +- `targetRef` — Git reference in `target` to which is pushed (branch or tag). + - If `ref `is a tag, `targetRef` is also treated as a tag. + - Exception:` targetRef` is a complete `ref `such as `refs/heads/my-branch` or `refs/tags/my-tag`. + - If` targetRef` is empty, the source `ref `is used by default. +- `path `- Folder within the content repo from which to copy +- `templating`- If `true`, all `.ftl` files are rendered using [Freemarker](https://freemarker.apache.org/) before being pushed to `target` ([see templating](#templating)). + + +### `FOLDER_BASED` +- Using the folder structure in the content repo, multiple repos can be created in the target and initialized or extended using `COPY`. +- That is, The top two directory levels of the repo determine the target repos in GOP. +- Example: The contents of the `example-tenant/gitops` folder are pushed to the `gitops` repo in the `example-tenant` namespace. + +![content-hooks-folder-based.png](content-hooks-folder-based.png) + +This allows, for example, adding additional Argo CD applications and even your own tenants to be deployed. + +**Properties** + +- `target` (required) — target repo, e.g. `namespace/name` +- `path` — source folder in the content repo used for copying +- `templating` — If `true`, all `.ftl` files are rendered using [Freemarker](https://freemarker.apache.org/) before being pushed to `target` ([see templating](#templating)). + +## The overrideMode + +For all types of content repos, the `overrideMode` determines how to handle previously existing files in the repo: `INIT`, `UPGRADE`, `RESET`. +- `INIT` (default): Only push if the repo does not exist +- `UPGRADE`: Clone and copy – existing files are overwritten, files that are not in the content are retained. +- `RESET`: Delete all files after cloning the source, then copy new files. + This results in files that are not in the content being deleted. + +**Note** +With `MIRROR`, `RESET` does not reset the entire repo. Specific effect: Branches that exist in the target but not in the source are retained. + +**Important** +If existing repos of GOP are to be extended, e.g., `cluster-resources`, the `overrideMode` must be set to `UPGRADE`. + +## Templating +When `templating` is enabled, all files ending in `.ftl` are rendered using [Freemarker](https://freemarker.apache.org/) during GOP installation and the result is created under the same name without the `.ftl` extension. +Example`values.yam;.ftl` is written to `values.yaml`. + +The entire configuration of GOP is available as `config` variable in the templates. + +In addition, you can define your own variables (`content.variables`). +This makes it possible to write parameterizable content that can be used for different instances. +Example: `config.content.variables.my` from the schematic example in [concepts](#content-loader-concepts). + +In Freemarker you can use static methods from GOP and JDK. +An [example from GOP code](https://github.com/cloudogu/gitops-playground/blob/0.11.0/applications/cluster-resources/monitoring/prometheus-stack-helm-values.ftl.yaml#L111): + +```yaml +<#assign DockerImageParser=statics['com.cloudogu.gitops.utils.DockerImageParser']> +# ... + <#if config.features.monitoring.helm.prometheusOperatorImage?has_content> + <#assign operatorImageObject = DockerImageParser.parse(config.features.monitoring.helm.prometheusOperatorImage)> +image: + registry : ${operatorImageObject.registry} + repo: ${operatorImageObject.repo} + tag : ${operatorImageObject.tag} + +``` + +# TL;DR + +How to get started with content loader? + +You can deploy GOP with customized content on a local Kubernetes cluster like this: + +```shell +bash <(curl -s \ + https://raw.githubusercontent.com/cloudogu/gitops-playground/main/scripts/init-cluster.sh) \ + && curl -s "https://raw.githubusercontent.com/cloudogu/gitops-playground/main/docs/content-loader/content-loader-config.yaml" > contentConfig.yaml \ + && docker run --rm -t --pull=always -u $(id -u) \ + -v ~/.config/k3d/kubeconfig-gitops-playground.yaml:/home/.kube/config \ + -v "$(pwd)/contentConfig.yaml:/app/contentConfig.yaml" \ + --net=host \ + ghcr.io/cloudogu/gitops-playground --config-file=contentConfig.yaml --debug +``` + +Once GOP is started, you can try the following example use cases. +Most applications mentioned in [README/Example Applications](../../README.md#example-applications) are deployed now. + +# Example use cases + +This chapter describes real-world use cases that can be done via content loader. + +You can try them out using the [`content-loader-config.yaml`](content-loader-config.yaml) used in [TL;DR](#tldr). + +## Mirror the entire repo on every call +```yaml + - url: 'https://github.com/cloudogu/spring-boot-helm-chart' + target: '3rd-party/spring-boot-helm' + overrideMode: RESET +``` + + +## Create additional tenant in Argo CD +```yaml + - url: 'https://github.com/cloudogu/gitops-playground.git' + path: 'docs/content-loader' + ref: main + # username: 'abc' # necessary if git repo requires credentials + # password: 'ey...' # e.g. API Token from SCM-Manager + templating: true + type: FOLDER_BASED + overrideMode: UPGRADE +``` + +In this repo, the folder structure defines the target repos, e.g. [argocd/argocd](argocd/argocd). + +## Mirror/copy repo and add specific files + +This example first `MIRROR`s a repo, then adds a `Dockerfile` and `Jenkinsfile` and creates a Jenkins job. + +As an alternative, you can add the type `COPY` in the first repo (petclinic), resulting in only the files being pushed to `target` (without the git history). +Reminder: no type means `MIRROR` (default). + +```yaml + - url: https://github.com/cloudogu/spring-petclinic + target: example-tenant/petclinic-plain + ref: feature/gitops_ready + targetRef: main + overrideMode: UPGRADE + createJenkinsJob: true + - url: https://github.com/cloudogu/gitops-playground.git + path: 'docs/content-loader' + ref: main + # username: 'abc' # necessary if git repo requires credentials + # password: 'ey...' # e.g. API Token from SCM-Manager + templating: true + type: FOLDER_BASED + overrideMode: UPGRADE +``` + diff --git a/docs/content-loader/example-tenant/gitops/README.md b/docs/content-loader/example-tenant/gitops/README.md new file mode 100644 index 000000000..143c98de3 --- /dev/null +++ b/docs/content-loader/example-tenant/gitops/README.md @@ -0,0 +1 @@ +Contains examples of end-user applications \ No newline at end of file diff --git a/docs/content-loader/example-tenant/gitops/apps/nginx-helm-umbrella/Chart.yaml b/docs/content-loader/example-tenant/gitops/apps/nginx-helm-umbrella/Chart.yaml new file mode 100644 index 000000000..62b99b38c --- /dev/null +++ b/docs/content-loader/example-tenant/gitops/apps/nginx-helm-umbrella/Chart.yaml @@ -0,0 +1,7 @@ +apiVersion: v2 +version: 13.2.21 +name: nginx +dependencies: + - name: nginx + version: 13.2.21 + repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami diff --git a/docs/content-loader/example-tenant/gitops/apps/nginx-helm-umbrella/README.md b/docs/content-loader/example-tenant/gitops/apps/nginx-helm-umbrella/README.md new file mode 100644 index 000000000..b5726758d --- /dev/null +++ b/docs/content-loader/example-tenant/gitops/apps/nginx-helm-umbrella/README.md @@ -0,0 +1,2 @@ +3rd Party app (NGINX) with helm, templated by argo from a separate git repo, using helm's dependency mechanism. +This is an alternative approach to keeping the values.yaml inside the `application`. \ No newline at end of file diff --git a/docs/content-loader/example-tenant/gitops/apps/nginx-helm-umbrella/values.yaml b/docs/content-loader/example-tenant/gitops/apps/nginx-helm-umbrella/values.yaml new file mode 100644 index 000000000..347396191 --- /dev/null +++ b/docs/content-loader/example-tenant/gitops/apps/nginx-helm-umbrella/values.yaml @@ -0,0 +1,10 @@ +nginx: + service: + ports: + http: 80 + type: ClusterIP + + ingress: + enabled: true + pathType: Prefix + hostname: production.nginx-helm-umbrella.nginx.localhost diff --git a/docs/content-loader/example-tenant/gitops/apps/spring-petclinic-plain/.gitkeep b/docs/content-loader/example-tenant/gitops/apps/spring-petclinic-plain/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/content-loader/example-tenant/gitops/argocd/nginx-helm-umbrella.ftl.yaml b/docs/content-loader/example-tenant/gitops/argocd/nginx-helm-umbrella.ftl.yaml new file mode 100644 index 000000000..0426e32a2 --- /dev/null +++ b/docs/content-loader/example-tenant/gitops/argocd/nginx-helm-umbrella.ftl.yaml @@ -0,0 +1,25 @@ +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-tenant-production + +spec: + destination: + annotations: + myvar: ${config.content.variables.umbrella.nginxAnnotation} + namespace: ${config.application.namePrefix}example-tenant-production + server: https://kubernetes.default.svc + project: example-tenant + source: + path: apps/nginx-helm-umbrella + repoURL: http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local/scm/repo/${config.application.namePrefix}example-tenant/gitops + targetRevision: main + + syncPolicy: + automated: + prune: true + selfHeal: true diff --git a/docs/content-loader/example-tenant/gitops/argocd/nginx-helm.ftl.yaml b/docs/content-loader/example-tenant/gitops/argocd/nginx-helm.ftl.yaml new file mode 100644 index 000000000..884c459db --- /dev/null +++ b/docs/content-loader/example-tenant/gitops/argocd/nginx-helm.ftl.yaml @@ -0,0 +1,37 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: nginx-helm + <#if config.features.argocd.operator> +namespace: ${config.application.namePrefix}argocd + <#else> +namespace: ${config.application.namePrefix}example-tenant-production + +spec: + destination: + namespace: ${config.application.namePrefix}example-tenant-production + server: https://kubernetes.default.svc + project: example-tenant + source: + path: '' + repoURL: >- + https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami + targetRevision: 13.2.21 + chart: nginx + helm: + values: |- + service: + ports: + http: 80 + type: ClusterIP + + ingress: + enabled: true + pathType: Prefix + hostname: production.nginx-helm.nginx.localhost + image: + repository: bitnamilegacy/nginx + syncPolicy: + automated: + prune: true + selfHeal: true \ No newline at end of file diff --git a/docs/content-loader/example-tenant/gitops/argocd/petclinic-helm.ftl.yaml b/docs/content-loader/example-tenant/gitops/argocd/petclinic-helm.ftl.yaml new file mode 100644 index 000000000..c2677f1e7 --- /dev/null +++ b/docs/content-loader/example-tenant/gitops/argocd/petclinic-helm.ftl.yaml @@ -0,0 +1,52 @@ +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-tenant-staging + +spec: + destination: + namespace: ${config.application.namePrefix}example-tenant-staging + server: https://kubernetes.default.svc + project: example-tenant + source: + path: apps/spring-petclinic-helm/staging + repoURL: http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local/scm/repo/${config.application.namePrefix}example-tenant/gitops + 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-tenant-production + +spec: + destination: + namespace: ${config.application.namePrefix}example-tenant-production + server: https://kubernetes.default.svc + project: example-tenant + source: + path: apps/spring-petclinic-helm/production + repoURL: http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local/scm/repo/${config.application.namePrefix}example-tenant/gitops + targetRevision: main + directory: + recurse: true + syncPolicy: + automated: + prune: true + selfHeal: true \ No newline at end of file diff --git a/docs/content-loader/example-tenant/gitops/argocd/petclinic-plain.ftl.yaml b/docs/content-loader/example-tenant/gitops/argocd/petclinic-plain.ftl.yaml new file mode 100644 index 000000000..4f631b432 --- /dev/null +++ b/docs/content-loader/example-tenant/gitops/argocd/petclinic-plain.ftl.yaml @@ -0,0 +1,53 @@ +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-tenant-staging + +spec: + destination: + namespace: ${config.application.namePrefix}example-tenant-staging + server: https://kubernetes.default.svc + project: example-tenant + source: + path: apps/spring-petclinic-plain/staging + repoURL: http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local/scm/repo/${config.application.namePrefix}example-tenant/gitops + 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-tenant-production + +spec: + destination: + namespace: ${config.application.namePrefix}example-tenant-production + server: https://kubernetes.default.svc + project: example-tenant + source: + path: apps/spring-petclinic-plain/production + repoURL: http://scmm.${config.application.namePrefix}scm-manager.svc.cluster.local/scm/repo/${config.application.namePrefix}example-tenant/gitops + targetRevision: main + directory: + recurse: true + syncPolicy: + automated: + prune: true + selfHeal: true diff --git a/docs/content-loader/example-tenant/gitops/misc/.gitkeep b/docs/content-loader/example-tenant/gitops/misc/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/content-loader/example-tenant/petclinic-helm/Dockerfile.ftl b/docs/content-loader/example-tenant/petclinic-helm/Dockerfile.ftl new file mode 100644 index 000000000..c739682dc --- /dev/null +++ b/docs/content-loader/example-tenant/petclinic-helm/Dockerfile.ftl @@ -0,0 +1,15 @@ +# TODO +# FROM {config.contentVariables.images.petclinic} +FROM ${config.images.petclinic} + +# Set permissions to group 0 for openshift compatibility +# See https://docs.openshift.com/container-platform/4.15/openshift_images/create-images.html#use-uid_create-images +RUN mkdir /app && chown 1000:0 /app \ + && chmod -R g=u /app + +COPY target/*.jar /app/petclinic.jar + +EXPOSE 8080 +USER 1000 + +ENTRYPOINT [ "java", "-server","-jar", "/app/petclinic.jar" ] diff --git a/docs/content-loader/example-tenant/petclinic-helm/Jenkinsfile.ftl b/docs/content-loader/example-tenant/petclinic-helm/Jenkinsfile.ftl new file mode 100644 index 000000000..2dcbcf60a --- /dev/null +++ b/docs/content-loader/example-tenant/petclinic-helm/Jenkinsfile.ftl @@ -0,0 +1,200 @@ +#!groovy + +String getApplication() { "spring-petclinic-helm" } +String getConfigRepositoryPRRepo() { '${(config.application.namePrefix?has_content)?then(config.application.namePrefix, "") + "example-tenant/gitops"}' } +String getScmManagerCredentials() { 'scmm-user' } +String getConfigRepositoryPRBaseUrl() { env.${config.application.namePrefixForEnvVars}SCMM_URL } + +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 getDockerRegistryProxyBaseUrl() { env.${config.application.namePrefixForEnvVars}REGISTRY_PROXY_URL } +String getDockerRegistryProxyCredentials() { 'registry-proxy-user' } + + +<#noparse> + +String getCesBuildLibRepo() { configRepositoryPRBaseUrl+"/repo/3rd-party-dependencies/ces-build-lib/" } +String getCesBuildLibVersion() { '2.5.0' } +String getGitOpsBuildLibRepo() { configRepositoryPRBaseUrl+"/repo/3rd-party-dependencies/gitops-build-lib" } +String getGitOpsBuildLibVersion() { '0.7.0'} +String getHelmChartRepository() { configRepositoryPRBaseUrl+"/repo/3rd-party-dependencies/spring-boot-helm-chart" } +String getHelmChartVersion() { "0.4.0" } +String getMainBranch() { 'main' } + +cesBuildLib = library(identifier: "ces-build-lib@${cesBuildLibVersion}", + retriever: modernSCM([$class: 'GitSCMSource', remote: cesBuildLibRepo, credentialsId: scmManagerCredentials]) +).com.cloudogu.ces.cesbuildlib + +gitOpsBuildLib = library(identifier: "gitops-build-lib@${gitOpsBuildLibVersion}", + retriever: modernSCM([$class: 'GitSCMSource', remote: gitOpsBuildLibRepo, credentialsId: scmManagerCredentials]) +).com.cloudogu.gitops.gitopsbuildlib + +properties([ + // Don't run concurrent builds, because the ITs use the same port causing random failures on concurrent builds. + disableConcurrentBuilds() +]) + +node { + + +<#if config.images.maven?has_content> + <#if config.registry.twoRegistries> + mvn = cesBuildLib.MavenInDocker.new(this, '${config.images.maven}', dockerRegistryProxyCredentials) + <#else> + mvn = cesBuildLib.MavenInDocker.new(this, '${config.images.maven}') + +<#else> + mvn = cesBuildLib.MavenWrapper.new(this) + + +<#if config.jenkins.mavenCentralMirror?has_content> + mvn.useMirrors([name: 'maven-central-mirror', mirrorOf: 'central', url: env.${config.application.namePrefixForEnvVars}MAVEN_CENTRAL_MIRROR]) + +<#noparse> + + catchError { + + stage('Checkout') { + checkout scm + } + + stage('Build') { + mvn 'clean package -DskipTests -Dcheckstyle.skip' + archiveArtifacts artifacts: '**/target/*.jar' + } + + stage('Test') { + // Disable database integration tests because they start docker images (which won't work in air-gapped envs and take a lot of time in demos) + mvn "test -Dmaven.test.failure.ignore=true -Dcheckstyle.skip " + + '-Dtest=!org.springframework.samples.petclinic.MySqlIntegrationTests,!org.springframework.samples.petclinic.PostgresIntegrationTests' + } + + String imageName = "" + stage('Docker') { + String imageTag = createImageTag() + +<#noparse> + String pathPrefix = !dockerRegistryPath?.trim() ? "" : "${dockerRegistryPath}/" + imageName = "${dockerRegistryBaseUrl}/${pathPrefix}${application}:${imageTag}" + +<#if config.registry.twoRegistries> +<#noparse> + docker.withRegistry("https://${dockerRegistryProxyBaseUrl}", dockerRegistryProxyCredentials) { + image = docker.build(imageName, '.') + } + +<#else> +<#noparse> + image = docker.build(imageName, '.') + + +<#noparse> + if (isBuildSuccessful()) { + docker.withRegistry("https://${dockerRegistryBaseUrl}", dockerRegistryCredentials) { + image.push() + } + } else { + echo 'Skipping docker push, because build not successful' + } + } + + stage('Deploy') { + if (isBuildSuccessful() && env.BRANCH_NAME in [mainBranch]) { + + def gitopsConfig = [ + scm : [ + provider : 'SCMManager', + credentialsId: scmManagerCredentials, + baseUrl : configRepositoryPRBaseUrl, + repositoryUrl : configRepositoryPRRepo, + ], + cesBuildLibRepo: cesBuildLibRepo, + cesBuildLibVersion: cesBuildLibVersion, + cesBuildLibCredentialsId: scmManagerCredentials, + application: application, + mainBranch: mainBranch, + gitopsTool: 'ARGO', + folderStructureStrategy: 'ENV_PER_APP', + + k8sVersion : env.${config.application.namePrefixForEnvVars}K8S_VERSION, + buildImages : [ +<#if config.registry.twoRegistries> + helm: [ + image: '${config.images.helm}', + credentialsId: dockerRegistryProxyCredentials + ], + kubectl: [ + image: '${config.images.kubectl}', + credentialsId: dockerRegistryProxyCredentials + ], + kubeval: [ + image: '${config.images.kubeval}', + credentialsId: dockerRegistryProxyCredentials + ], + helmKubeval: [ + image: '${config.images.helmKubeval}', + credentialsId: dockerRegistryProxyCredentials + ], + yamllint: [ + image: '${config.images.yamllint}', + credentialsId: dockerRegistryProxyCredentials + ] +<#else> + helm: '${config.images.helm}', + kubectl: '${config.images.kubectl}', + kubeval: '${config.images.kubeval}', + helmKubeval: '${config.images.helmKubeval}', + yamllint: '${config.images.yamllint}' + + ], + deployments: [ + sourcePath: 'k8s', + destinationRootPath: 'apps', + helm : [ + repoType : 'GIT', + credentialsId : scmManagerCredentials, + repoUrl : helmChartRepository, + version: helmChartVersion, + updateValues : [[fieldPath: "image.name", newValue: imageName]] + ] + ], + stages: [ + staging: [ + namespace: '${config.application.namePrefix}example-tenant-staging', + deployDirectly: true ], + production: [ + namespace: '${config.application.namePrefix}example-tenant-production', + deployDirectly: false ] + ] + ] +<#noparse> + deployViaGitops(gitopsConfig) + } else { + echo 'Skipping deploy, because build not successful or not on main branch' + } + } + } + + // 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 +def gitOpsBuildLib + \ No newline at end of file diff --git a/docs/content-loader/example-tenant/petclinic-helm/k8s/production/.gitkeep b/docs/content-loader/example-tenant/petclinic-helm/k8s/production/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/content-loader/example-tenant/petclinic-helm/k8s/staging/.gitkeep b/docs/content-loader/example-tenant/petclinic-helm/k8s/staging/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/content-loader/example-tenant/petclinic-helm/k8s/values-production.ftl.yaml b/docs/content-loader/example-tenant/petclinic-helm/k8s/values-production.ftl.yaml new file mode 100644 index 000000000..b7528242d --- /dev/null +++ b/docs/content-loader/example-tenant/petclinic-helm/k8s/values-production.ftl.yaml @@ -0,0 +1,13 @@ +service: + port: 80 + +<#if config.features.exampleApps.petclinic.baseDomain?has_content> +ingress: + hosts: + <#if config.application.urlSeparatorHyphen> + - host: production-petclinic-helm-${config.features.exampleApps.petclinic.baseDomain} + <#else> + - host: production.petclinic-helm.${config.features.exampleApps.petclinic.baseDomain} + + paths: ['/'] + diff --git a/docs/content-loader/example-tenant/petclinic-helm/k8s/values-shared.ftl.yaml b/docs/content-loader/example-tenant/petclinic-helm/k8s/values-shared.ftl.yaml new file mode 100644 index 000000000..2aa824334 --- /dev/null +++ b/docs/content-loader/example-tenant/petclinic-helm/k8s/values-shared.ftl.yaml @@ -0,0 +1,30 @@ +extraEnv: | + - name: TZ + value: Europe/Berlin + +service: + type: <#if config.application.remote>LoadBalancer<#else>ClusterIP + +ingress: + enabled: <#if config.features.exampleApps.petclinic.baseDomain?has_content>true<#else>false + + +<#if config.application.openshift == true> +securityContext: + runAsUser: null + runAsGroup: null + + +<#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 + diff --git a/docs/content-loader/example-tenant/petclinic-helm/k8s/values-staging.ftl.yaml b/docs/content-loader/example-tenant/petclinic-helm/k8s/values-staging.ftl.yaml new file mode 100644 index 000000000..b0eab58c7 --- /dev/null +++ b/docs/content-loader/example-tenant/petclinic-helm/k8s/values-staging.ftl.yaml @@ -0,0 +1,13 @@ +service: + port: 80 + +<#if config.features.exampleApps.petclinic.baseDomain?has_content> +ingress: + hosts: + <#if config.application.urlSeparatorHyphen> + - host: staging-petclinic-helm-${config.features.exampleApps.petclinic.baseDomain} + <#else> + - host: staging.petclinic-helm.${config.features.exampleApps.petclinic.baseDomain} + + paths: ['/'] + diff --git a/docs/content-loader/example-tenant/petclinic-plain/Dockerfile.ftl b/docs/content-loader/example-tenant/petclinic-plain/Dockerfile.ftl new file mode 100644 index 000000000..c739682dc --- /dev/null +++ b/docs/content-loader/example-tenant/petclinic-plain/Dockerfile.ftl @@ -0,0 +1,15 @@ +# TODO +# FROM {config.contentVariables.images.petclinic} +FROM ${config.images.petclinic} + +# Set permissions to group 0 for openshift compatibility +# See https://docs.openshift.com/container-platform/4.15/openshift_images/create-images.html#use-uid_create-images +RUN mkdir /app && chown 1000:0 /app \ + && chmod -R g=u /app + +COPY target/*.jar /app/petclinic.jar + +EXPOSE 8080 +USER 1000 + +ENTRYPOINT [ "java", "-server","-jar", "/app/petclinic.jar" ] diff --git a/docs/content-loader/example-tenant/petclinic-plain/Jenkinsfile.ftl b/docs/content-loader/example-tenant/petclinic-plain/Jenkinsfile.ftl new file mode 100644 index 000000000..1c6b5b268 --- /dev/null +++ b/docs/content-loader/example-tenant/petclinic-plain/Jenkinsfile.ftl @@ -0,0 +1,206 @@ +#!groovy + +String getApplication() { 'spring-petclinic-plain' } +String getConfigRepositoryPRRepo() { '${(config.application.namePrefix?has_content)?then(config.application.namePrefix, "") + "example-tenant/gitops"}' } +String getScmManagerCredentials() { 'scmm-user' } +String getConfigRepositoryPRBaseUrl() { env.${config.application.namePrefixForEnvVars}SCMM_URL } + +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 getDockerRegistryProxyBaseUrl() { env.${config.application.namePrefixForEnvVars}REGISTRY_PROXY_URL } +String getDockerRegistryProxyCredentials() { 'registry-proxy-user' } + +<#noparse> +String getCesBuildLibRepo() { configRepositoryPRBaseUrl+"/repo/3rd-party-dependencies/ces-build-lib/" } +String getCesBuildLibVersion() { '2.5.0' } +String getGitOpsBuildLibRepo() { configRepositoryPRBaseUrl+"/repo/3rd-party-dependencies/gitops-build-lib" } +String getGitOpsBuildLibVersion() { '0.7.0'} + +loadLibraries() + +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) + +<#if config.jenkins.mavenCentralMirror?has_content> + mvn.useMirrors([name: 'maven-central-mirror', mirrorOf: 'central', url: env.${config.application.namePrefixForEnvVars}MAVEN_CENTRAL_MIRROR]) + +<#noparse> + + catchError { + + stage('Checkout') { + checkout scm + } + + stage('Build') { + mvn 'clean package -DskipTests -Dcheckstyle.skip' + archiveArtifacts artifacts: '**/target/*.jar' + } + + stage('Test') { + // Tests skipped for faster demo and exercise purposes + //mvn 'test -Dmaven.test.failure.ignore=true -Dcheckstyle.skip' + } + + String imageName = "" + stage('Docker') { + String imageTag = createImageTag() + +<#if config.registry.twoRegistries> +<#noparse> + String pathPrefix = !dockerRegistryPushPath?.trim() ? "" : "${dockerRegistryPushPath}/" + imageName = "${dockerRegistryPushBaseUrl}/${pathPrefix}${application}:${imageTag}" + docker.withRegistry("http://${dockerRegistryPullBaseUrl}", dockerRegistryPullCredentials) { + image = docker.build(imageName, '.') + } + +<#else> +<#noparse> + String pathPrefix = !dockerRegistryPath?.trim() ? "" : "${dockerRegistryPath}/" + imageName = "${dockerRegistryBaseUrl}/${pathPrefix}${application}:${imageTag}" + image = docker.build(imageName, '.') + + + + if (isBuildSuccessful()) { +<#if config.registry.twoRegistries> +<#noparse> + docker.withRegistry("https://${dockerRegistryPushBaseUrl}", dockerRegistryPushCredentials) { + +<#else> +<#noparse> + docker.withRegistry("https://${dockerRegistryBaseUrl}", dockerRegistryCredentials) { + + +<#noparse> + image.push() + } + } else { + echo 'Skipping docker push, because build not successful' + } + } + + stage('Deploy') { + if (isBuildSuccessful() && env.BRANCH_NAME in ['main']) { + + def gitopsConfig = [ + scm: [ + provider : 'SCMManager', + credentialsId: scmManagerCredentials, + baseUrl : configRepositoryPRBaseUrl, + repositoryUrl : configRepositoryPRRepo, + ], + application: application, + gitopsTool: 'ARGO', + folderStructureStrategy: 'ENV_PER_APP', + + k8sVersion : env.${config.application.namePrefixForEnvVars}K8S_VERSION, + deployments: [ + sourcePath: 'k8s', + destinationRootPath: 'apps', + plain: [ + updateImages: [ + [ filename: 'deployment.yaml', + containerName: application, + imageName: imageName ] + ] + ] + ], + fileConfigmaps: [ + // Showcase for gitops-build-lib: Convert file into a config map + [ + name : 'messages', + sourceFilePath : '../src/main/resources/messages/messages.properties', + stage: ['staging', 'production'] + ] + ], + stages: [ + staging: [ + namespace: '${config.application.namePrefix}example-tenant-staging', + deployDirectly: true ], + production: [ + namespace: '${config.application.namePrefix}example-tenant-production', + deployDirectly: false ], + ] + ] +<#noparse> + addSpecificGitOpsConfig(gitopsConfig) + + deployViaGitops(gitopsConfig) + } else { + echo 'Skipping deploy, because build not successful or not on main branch' + } + } + } + + // Archive Unit and integration test results, if any + junit allowEmptyResults: true, testResults: '**/target/failsafe-reports/TEST-*.xml,**/target/surefire-reports/TEST-*.xml' +} + +/** Initializations might not be needed in a real-world setup, but are necessary for GitOps playground */ +void addSpecificGitOpsConfig(gitopsConfig) { + gitopsConfig += [ + // In the GitOps playground, we're loading the build libs from our local SCM so it also works in an offline context + // As the gitops-build-lib also uses the ces-build-lib we need to pass those parameters on. + // If you can access the internet, you can rely on the defaults, which load the lib from GitHub. + cesBuildLibRepo: cesBuildLibRepo, + cesBuildLibVersion: cesBuildLibVersion, + cesBuildLibCredentialsId: scmManagerCredentials, + + + // The GitOps playground provides parameters for overwriting the build images used by gitops-build-lib, so + // it also works in an offline context. + // Those parameters overwrite the following parameters. + // If you can access the internet, you can rely on the defaults, which load the images from public registries. + buildImages : [ + + helm: '${config.images.helm}', + kubectl: '${config.images.kubectl}', + kubeval: '${config.images.kubeval}', + helmKubeval: '${config.images.helmKubeval}', + yamllint: '${config.images.yamllint}' +<#noparse> + ] + ] +} + +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 loadLibraries() { + // In the GitOps playground, we're loading the build libs from our local SCM so it also works in an offline context + // If you can access the internet, you could also load the libraries directly from github like so + // @Library(["github.com/cloudogu/ces-build-lib@${cesBuildLibVersion}", "github.com/cloudogu/gitops-build-lib@${gitOpsBuildLibRepo}"]) _ + //import com.cloudogu.ces.cesbuildlib.* + //import com.cloudogu.ces.gitopsbuildlib.* + + cesBuildLib = library(identifier: "ces-build-lib@${cesBuildLibVersion}", + retriever: modernSCM([$class: 'GitSCMSource', remote: cesBuildLibRepo, credentialsId: scmManagerCredentials]) + ).com.cloudogu.ces.cesbuildlib + + library(identifier: "gitops-build-lib@${gitOpsBuildLibVersion}", + retriever: modernSCM([$class: 'GitSCMSource', remote: gitOpsBuildLibRepo, credentialsId: scmManagerCredentials]) + ).com.cloudogu.gitops.gitopsbuildlib +} + +def cesBuildLib +def gitOpsBuildLib + diff --git a/docs/content-loader/example-tenant/petclinic-plain/k8s/production/deployment.ftl.yaml b/docs/content-loader/example-tenant/petclinic-plain/k8s/production/deployment.ftl.yaml new file mode 100644 index 000000000..61be07b4c --- /dev/null +++ b/docs/content-loader/example-tenant/petclinic-plain/k8s/production/deployment.ftl.yaml @@ -0,0 +1,49 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-petclinic-plain +spec: + replicas: 1 + selector: + matchLabels: + app: spring-petclinic-plain + template: + metadata: + labels: + app: spring-petclinic-plain + spec: + containers: + - name: spring-petclinic-plain + image: localhost:30000/petclinic-plain:1 + ports: + - containerPort: 8080 + name: http + env: + - name: MANAGEMENT_SERVER_PORT + value: "9080" + - name: SPRING_MESSAGES_BASENAME + value: "file:/tmp/messages/messages" + - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK + value: INFO + readinessProbe: + failureThreshold: 3 + httpGet: + path: /actuator/health/readiness + port: 9080 + scheme: HTTP + volumeMounts: + - name: messages + mountPath: /tmp/messages +<#if config.application.podResources == true> + resources: + limits: + cpu: '1' + memory: 1Gi + requests: + cpu: 300m + memory: 650Mi + + volumes: + - name: messages + configMap: + name: messages diff --git a/docs/content-loader/example-tenant/petclinic-plain/k8s/production/ingress.ftl.yaml b/docs/content-loader/example-tenant/petclinic-plain/k8s/production/ingress.ftl.yaml new file mode 100644 index 000000000..510da81fe --- /dev/null +++ b/docs/content-loader/example-tenant/petclinic-plain/k8s/production/ingress.ftl.yaml @@ -0,0 +1,25 @@ +<#if config.features.exampleApps.petclinic.baseDomain?has_content> +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: spring-petclinic-plain + labels: + app: spring-petclinic-plain +spec: + rules: + <#if config.application.urlSeparatorHyphen> + - host: production-petclinic-plain-${config.features.exampleApps.petclinic.baseDomain} + <#else> + - host: production.petclinic-plain.${config.features.exampleApps.petclinic.baseDomain} + + http: + paths: + - backend: + service: + name: spring-petclinic-plain + port: + name: http + path: / + pathType: Prefix + + diff --git a/docs/content-loader/example-tenant/petclinic-plain/k8s/production/service.ftl.yaml b/docs/content-loader/example-tenant/petclinic-plain/k8s/production/service.ftl.yaml new file mode 100644 index 000000000..e88130722 --- /dev/null +++ b/docs/content-loader/example-tenant/petclinic-plain/k8s/production/service.ftl.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: spring-petclinic-plain + labels: + app: spring-petclinic-plain +spec: + type: <#if config.application.remote>LoadBalancer<#else>NodePort + ports: + - name: http + port: 80 + nodePort: 30021 + targetPort: http + selector: + app: spring-petclinic-plain diff --git a/docs/content-loader/example-tenant/petclinic-plain/k8s/staging/deployment.ftl.yaml b/docs/content-loader/example-tenant/petclinic-plain/k8s/staging/deployment.ftl.yaml new file mode 100644 index 000000000..9a47ea7b9 --- /dev/null +++ b/docs/content-loader/example-tenant/petclinic-plain/k8s/staging/deployment.ftl.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-petclinic-plain +spec: + replicas: 1 + selector: + matchLabels: + app: spring-petclinic-plain + template: + metadata: + labels: + app: spring-petclinic-plain + spec: + containers: + - name: spring-petclinic-plain + image: localhost:30000/petclinic-plain:1 + ports: + - containerPort: 8080 + name: http + env: + - name: MANAGEMENT_SERVER_PORT + value: "9080" + - name: SPRING_MESSAGES_BASENAME + value: "file:/tmp/messages/messages" + - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK + value: INFO + readinessProbe: + failureThreshold: 3 + httpGet: + path: /actuator/health/readiness + port: 9080 + scheme: HTTP + volumeMounts: + - name: messages + mountPath: /tmp/messages + - name: tmp + # Make /tmp writable with readOnlyRootFilesystem + mountPath: /tmp +<#if config.application.podResources == true> + resources: + limits: + cpu: '1' + memory: 1Gi + requests: + cpu: 300m + memory: 650Mi + + securityContext: # Implement some least privilege good practices + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + # These two seem to cause problems with OpenShift, so we disable them for now + #runAsUser: 65312 + #seccompProfile: + # type: RuntimeDefault + # With k8s 1.30 apparmor is added + # Won't work on k3d, though, because apparmor is not enabled + # apparmorProfile: "runtime/default" + enableServiceLinks: false + automountServiceAccountToken: false # When not communicating with API Server + volumes: + - name: messages + configMap: + name: messages + - name: tmp + emptyDir: {} \ No newline at end of file diff --git a/docs/content-loader/example-tenant/petclinic-plain/k8s/staging/ingress.ftl.yaml b/docs/content-loader/example-tenant/petclinic-plain/k8s/staging/ingress.ftl.yaml new file mode 100644 index 000000000..05e50faf5 --- /dev/null +++ b/docs/content-loader/example-tenant/petclinic-plain/k8s/staging/ingress.ftl.yaml @@ -0,0 +1,25 @@ +<#if config.features.exampleApps.petclinic.baseDomain?has_content> +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: spring-petclinic-plain + labels: + app: spring-petclinic-plain +spec: + rules: + <#if config.application.urlSeparatorHyphen> + - host: staging-petclinic-plain-${config.features.exampleApps.petclinic.baseDomain} + <#else> + - host: staging.petclinic-plain.${config.features.exampleApps.petclinic.baseDomain} + + http: + paths: + - backend: + service: + name: spring-petclinic-plain + port: + name: http + path: / + pathType: Prefix + + diff --git a/docs/content-loader/example-tenant/petclinic-plain/k8s/staging/service.ftl.yaml b/docs/content-loader/example-tenant/petclinic-plain/k8s/staging/service.ftl.yaml new file mode 100644 index 000000000..77f87b98d --- /dev/null +++ b/docs/content-loader/example-tenant/petclinic-plain/k8s/staging/service.ftl.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: spring-petclinic-plain + labels: + app: spring-petclinic-plain +spec: + type: <#if config.application.remote>LoadBalancer<#else>NodePort + ports: + - name: http + port: 80 + nodePort: 30020 + targetPort: http + selector: + app: spring-petclinic-plain