Commit d19da616 authored by Pierre Smeyers's avatar Pierre Smeyers
Browse files

Merge branch 'initial' into 'main'

New template: GitOps trigger

See merge request to-be-continuous/gitops!1
parents e0ea65d9 7e328c1a
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -21,3 +21,4 @@
/nbdist/
/.nb-gradle/
.DS_Store
triggers-ex-1.json
+2 −2
Original line number Diff line number Diff line
@@ -8,8 +8,8 @@ Closes #999
## Checklist

* General:
    * [ ] use [rules](https://docs.gitlab.com/ee/ci/yaml/#rules) instead of [only/except](https://docs.gitlab.com/ee/ci/yaml/#onlyexcept-advanced)
    * [ ] optimized [cache](https://docs.gitlab.com/ee/ci/caching/) configuration (wherever applicable)
    * [ ] use [rules](https://docs.gitlab.com/ci/yaml/#rules) instead of [only/except](https://docs.gitlab.com/ci/yaml/#onlyexcept-advanced)
    * [ ] optimized [cache](https://docs.gitlab.com/ci/caching/) configuration (wherever applicable)
* Publicly usable:
    * [ ] untagged runners
    * [ ] no proxy configuration but support `http_proxy`/`https_proxy`/`no_proxy`
+1 −1
Original line number Diff line number Diff line
@@ -61,7 +61,7 @@ To contribute:

1. Create an issue describing the bug or enhancement you want to propose (select the right issue template).
2. Make sure the issue has been reviewed and agreed.
3. Create a Merge Request, from your **own** fork (see [forking workflow](https://docs.gitlab.com/ee/user/project/repository/forking_workflow.html) documentation).
3. Create a Merge Request, from your **own** fork (see [forking workflow](https://docs.gitlab.com/user/project/repository/forking_workflow/) documentation).
   Don't hesitate to mark your MR as `Draft` as long as you think it's not ready to be reviewed.

### Git Commit Conventions
+1 −2
Original line number Diff line number Diff line
@@ -3,10 +3,9 @@ files:
    documentation: ./README.md
    changelog: ./CHANGELOG.md
data:
    description: "Run your automated tests with GitOps"
    description: "Trigger a GitOps deployment from your GitLab CI/CD pipelines"
    labels:
    - to be continuous
    - Test
    - GitOps
    public: true
    license: LGPL v3
+166 −38
Original line number Diff line number Diff line
# GitLab CI template for GitOps

This project implements a GitLab CI/CD template to run your automated tests with [GitOps](https://doc.acme-tech.com).
This project implements a template to integrate GitOps deployment strategy with your GitLab CI/CD pipelines.

More specifically, this template allows you to trigger a change to a JSON or YAML file in a remote Git repository from your project pipeline.

For example, it will allow you to automatically update an image tag in a [Helm Values file](https://helm.sh/docs/chart_template_guide/values_files/) as soon as a new version of the container image is produced.

> [!important]
> This technique is certainly not the only nor the best way to implement a GitOps deployment strategy but in some cases it might help.
>
> Whenever possible, we recommend using dedicated tools, such as:
>
> - [Auto image update](https://fluxcd.io/flux/guides/image-update/) with FluxCD,
> - [Argo CD Image Updater](https://argocd-image-updater.readthedocs.io/) with ArgoCD,
> - even [Renovate](https://docs.renovatebot.com/) can help you implement the same very smartly.

## Usage
**component: $CI_SERVER_FQDN**
This template can be used both as a [CI/CD component](https://docs.gitlab.com/ee/ci/components/#use-a-component) 
or using the legacy [`include:project`](https://docs.gitlab.com/ee/ci/yaml/index.html#includeproject) syntax.

This template can be used both as a [CI/CD component](https://docs.gitlab.com/ci/components/#use-a-component)
or using the legacy [`include:project`](https://docs.gitlab.com/ci/yaml/#includeproject) syntax.

### Use as a CI/CD component

@@ -15,9 +28,8 @@ Add the following to your `.gitlab-ci.yml`:
include:
  # 1: include the component
  - component: $CI_SERVER_FQDN/to-be-continuous/gitops/gitlab-ci-gitops@1.0.0
    # 2: set/override component inputs
    inputs:
      review-enabled: true # ⚠ this is only an example

# 2: GITOPS_TRIGGER_XXX variables are defined as project CI/CD variables
```

### Use as a CI/CD template (legacy)
@@ -27,50 +39,166 @@ Add the following to your `.gitlab-ci.yml`:
```yaml
include:
  # 1: include the template
  - project: 'to-be-continuous/gitops'
    ref: '1.0.0'
    file: '/templates/gitlab-ci-gitops.yml'
  - project: "to-be-continuous/gitops"
    ref: "1.0.0"
    file: "/templates/gitlab-ci-gitops.yml"

variables:
  # 2: set/override template variables
  REVIEW_ENABLED: "true" # ⚠ this is only an example
# 2: GITOPS_TRIGGER_XXX variables are defined as project CI/CD variables
```

## `gitops` job
## `gitops-trigger` job

This job starts [GitOps](https://doc.acme-tech.com) (functional) tests.
This job triggers a change to a JSON or YAML file in a remote Git repository.

It uses the following variable:

| Input / Variable                  | Description                                                                                               | Default value |
| --------------------- | ---------------------------------------- | ----------------- |
| `image` / `GITOPS_IMAGE`       | The Docker image used to run GitOps. | `docker.io/gitops:latest`<br/>[![Trivy Badge](https://to-be-continuous.gitlab.io/doc/secu/trivy-badge-GITOPS_IMAGE.svg)](https://to-be-continuous.gitlab.io/doc/secu/trivy-GITOPS_IMAGE) |
| `project-dir` / `GITOPS_PROJECT_DIR` | The GitOps project directory (containing test scripts) | `.` |
| `extra-args` / `GITOPS_EXTRA_ARGS`  | GitOps extra [run options](link-to-cli-options-ref) | _none_ |
| `review-enabled` / `REVIEW_ENABLED`      | Set to `true` to enable GitOps tests on review environments (dynamic environments instantiated on development branches) | _none_ (disabled) |
| --------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------- |
| `image` / `GITOPS_IMAGE`          | The Docker image used to run GitOps (`git` + `yq`)                                                        | `docker.io/alpine/git:latest`<br/>[![Trivy Badge](https://to-be-continuous.gitlab.io/doc/secu/trivy-badge-GITOPS_IMAGE.svg)](https://to-be-continuous.gitlab.io/doc/secu/trivy-GITOPS_IMAGE) |
| :lock: `GITOPS_TRIGGER_ONPROD`    | The GitOps changes to trigger on the production branch (JSON) - [see below](#triggers-definition-json)    | _none_ (disabled) |
| :lock: `GITOPS_TRIGGER_ONINTEG`   | The GitOps changes to trigger on the integration branch (JSON) - [see below](#triggers-definition-json)   | _none_ (disabled) |
| :lock: `GITOPS_TRIGGER_ONDEV`     | The GitOps changes to trigger on the development branches (JSON) - [see below](#triggers-definition-json) | _none_ (disabled) |
| :lock: `GITOPS_TRIGGER_ONRELEASE` | The GitOps changes to trigger on release tags (JSON) - [see below](#triggers-definition-json)             | _none_ (disabled) |

### Triggers definition (JSON)

The GitOps changes to trigger must be defined as a JSON structure, defining:

- whether to trigger automatically or manually,
- the target Git repositories and branch,
- the files to modify,
- the [`yq eval` command](https://mikefarah.gitbook.io/yq/operators/eval) to apply (supports late variable expansion).

:warning: the triggers definition MUST be managed as a project CI/CD variable (outside of the project `.gitlab-ci.yaml` then) for two reasons:

- target repository URL bears their own authentication credentials,
- allows a loose coupling between the source project and the target repositories to trigger (you don't necessarilly want to update the source project `.gitlab-ci.yaml` every time you change the triggers).

The triggers definition is formatted as follows:

```json
{
  "when": "<whether to trigger changes manually or automatically (one of 'manual' or 'on_success')>",
  "commit_message": "<optional /The commit message template to use for GitOps trigger (supports late variable expansion)>",
  "triggers": [
    {
      "url": "<The target repository URL to push changes, including credentials>",
      "branch": "<The target repository branch to push changes to>",
      "commit_message": "<optional / The repo specific commit message template to use (supports late variable expansion)>",
      "dry_run": "<optional / true or false>",
      "changes": [
        {
          "file": "<The target file to apply the 'yq eval' command>",
          "yq_eval": "<The 'yq eval' command to apply (supports late variable expansion) - see https://mikefarah.gitbook.io/yq/operators/eval>"
        },
        ...
      ]
    },
    ...
  ]
}
```

:information_source: The triggers definition must respect [this JSON schema](./triggers-schema-1.json).

### Example

```json
{
  "when": "manual",
  "commit_message": "chore(gitops): update deployment values triggered from commit #$CI_COMMIT_SHA (see $CI_PIPELINE_URL)",
  "triggers": [
    {
      "url": "https://token:$MY_RW_ACCESS_TOKEN@$CI_SERVER_FQDN/path/to/values/project.git",
      "branch": "main",
      "changes": [
        {
          "file": "values_preprod.yml",
          "yq_eval": ".myapp.image.tag = \"$${docker_tag}@$${docker_digest}\""
        }
      ]
    }
  ]
}
```


> [!important] Late variable expansion mechanism
>
> The template supports a **late variable expansion mechanism** for the `commit_message` and `yq_eval` JSON fields.
>
> It allows you to inject dynamically evaluated variables (coming from the pipeline execution context), using the `%{somevar}` (or `$${somevar}`) syntax.
> 
> The above example makes use of it to inject `$docker_tag` and `$docker_digest` variables, propagated by the Docker template.
> 
> :bulb: on the other hand, the here-above `commit_message` field doesn't need to use this technique here as `$CI_COMMIT_SHA` and `$CI_PIPELINE_URL` are not _late evaluated_.

> [!important] Target repository URL bears authentication
> 
> This template doesn't support any fancy mechanism to provide authentication, but instead assumes the target Git repository URL bears its own authentication credentials.
> 
> Example: `https://user:suP3rpA55w0rd@gitlab.acme.corp/path/to/values/project`
> 
> :warning: make sure to manage those credentials safely, as any other secret:
> 
> - never store them directly in your source code,
> - manage them as [project or group CI/CD variables](https://docs.gitlab.com/ci/variables/#for-a-project):
>    * [**masked**](https://docs.gitlab.com/ci/variables/#mask-a-cicd-variable) to prevent them from being inadvertently displayed in your job logs,
>    * [**protected**](https://docs.gitlab.com/ci/variables/#protected-cicd-variables) if you want to secure some secrets you don't want everyone in the project to have access to (for instance production secrets).

In addition to a textual report in the console, this job produces the following reports, kept for one day:
## Variants

| Report         | Format                                                                       | Usage             |
| -------------- | ---------------------------------------------------------------------------- | ----------------- |
| `$GITOPS_PROJECT_DIR/reports/gitops.xunit.xml` | [xUnit](https://github.com/jest-community/jest-junit#readme) test report(s) | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportsjunit) |
### Vault variant

### base url auto evaluation
This variant allows delegating your secrets management to a [Vault](https://www.vaultproject.io/) server.

By default, the GitOps template tries to auto-evaluate its base url
(i.e. the variable pointing at server under test) by looking either for a `$environment_url` variable or for an 
`environment_url.txt` file.
#### Configuration

Therefore if an upstream job in the pipeline deployed your code to a server and propagated the deployed server url,
either through a [dotenv](https://docs.gitlab.com/ee/ci/pipelines/job_artifacts.html#artifactsreportsdotenv) variable `$environment_url` 
or through a basic `environment_url.txt` file, then the GitOps test will automatically be run on this server.
In order to be able to communicate with the Vault server, the variant requires the additional configuration parameters:

:warning: all our deployment templates implement this design. Therefore even purely dynamic environments (such as review
environments) will automatically be propagated to your GitOps tests.
| INPUT / Variable                    | Description                                                                                                                     | Default value                                                              |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `TBC_VAULT_IMAGE`                   | The [Vault Secrets Provider](https://gitlab.com/to-be-continuous/tools/vault-secrets-provider) image to use (can be overridden) | `registry.gitlab.com/to-be-continuous/tools/vault-secrets-provider:master` |
| `vault-base-url` / `VAULT_BASE_URL` | The Vault server base API url                                                                                                   | **must be defined**                                                        |
| `vault-oidc-aud` / `VAULT_OIDC_AUD` | The `aud` claim for the JWT                                                                                                     | `$CI_SERVER_URL`                                                           |
| :lock: `VAULT_ROLE_ID`              | The [AppRole](https://www.vaultproject.io/docs/auth/approle) RoleID                                                             | _none_                                                                     |
| :lock: `VAULT_SECRET_ID`            | The [AppRole](https://www.vaultproject.io/docs/auth/approle) SecretID                                                           | _none_                                                                     |

### Hook scripts
By default, the variant will authentifacte using a [JWT ID token](https://docs.gitlab.com/ci/secrets/id_token_authentication/). To use [AppRole](https://www.vaultproject.io/docs/auth/approle) instead the `VAULT_ROLE_ID` and `VAULT_SECRET_ID` should be defined as secret project variables.

The GitOps template supports _optional_ **hook scripts** from your project, located in the `$GITOPS_PROJECT_DIR` directory to perform additional project-specific logic:
#### Usage

* `pre-gitops.sh` is executed **before** running GitOps,
* `post-gitops.sh` is executed **after** running GitOps (whichever the tests status).
Then you may retrieve any of your secret(s) from Vault using the following syntax:

```
@url@http://vault-secrets-provider/api/secrets/{secret_path}?field={field}
```

With:

| Name                             | Description                                                           |
| -------------------------------- | --------------------------------------------------------------------- |
| `secret_path` (_path parameter_) | this is your secret location in the Vault server                      |
| `field` (_query parameter_)      | parameter to access a single basic field from the secret JSON payload |

#### Example

```yaml
include:
  # main template
  - project: "to-be-continuous/gitops"
    ref: "1.0.0"
    file: "/templates/gitlab-ci-gitops.yml"
  # Vault variant
  - project: "to-be-continuous/gitops"
    ref: "1.0.0"
    file: "/templates/gitlab-ci-gitops-vault.yml"

variables:
  # audience claim for JWT
  VAULT_OIDC_AUD: "https://vault.acme.host"
  # Vault server Url
  VAULT_BASE_URL: "https://vault.acme.host/v1"
  # Secrets managed by Vault
  GITOPS_TRIGGER_ONPROD: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/my-srv/gitops/prod?field=trigger"
```
Loading