From 60f2c3f60178a74660a4b057a054b3914cf56922 Mon Sep 17 00:00:00 2001 From: Pierre Smeyers <pierre.smeyers@gmail.com> Date: Sat, 27 Jan 2024 16:17:06 +0000 Subject: [PATCH] feat: migrate to GitLab CI/CD component --- .gitlab-ci.yml | 2 +- README.md | 293 ++++++++++++++------------- bumpversion.sh | 4 +- kicker.json | 22 +- logo.png | Bin 22570 -> 16871 bytes templates/gitlab-ci-docker-ecr.yml | 41 +++- templates/gitlab-ci-docker-gcp.yml | 42 +++- templates/gitlab-ci-docker-vault.yml | 14 +- templates/gitlab-ci-docker.yml | 261 ++++++++++++++++++++---- 9 files changed, 478 insertions(+), 201 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 47047ce..8075643 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,7 +10,7 @@ include: file: '/templates/validation.yml' - project: 'to-be-continuous/bash' ref: '3.3' - file: 'templates/gitlab-ci-bash.yml' + file: '/templates/gitlab-ci-bash.yml' - project: 'to-be-continuous/semantic-release' ref: '3.7' file: '/templates/gitlab-ci-semrel.yml' diff --git a/README.md b/README.md index e883a18..0c12f5a 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,36 @@ This project implements a GitLab CI/CD template to build, check and inspect your ## Usage -In order to include this template in your project, add the following to your `.gitlab-ci.yml` : +This template can be used both as a [CI/CD component](https://docs.gitlab.com/ee/ci/components/#use-a-component-in-a-cicd-configuration) +or using the legacy [`include:project`](https://docs.gitlab.com/ee/ci/yaml/index.html#includeproject) syntax. + +### Use as a CI/CD component + +Add the following to your `gitlab-ci.yml`: ```yaml include: + # 1: include the component + - component: gitlab.com/to-be-continuous/docker/gitlab-ci-docker@5.7.0 + # 2: set/override component inputs + inputs: + build-tool: buildah # ⚠ this is only an example +``` + +### Use as a CI/CD template (legacy) + +Add the following to your `gitlab-ci.yml`: + +```yaml +include: + # 1: include the template - project: 'to-be-continuous/docker' ref: '5.7.1' file: '/templates/gitlab-ci-docker.yml' + +variables: + # 2: set/override template variables + DOCKER_BUILD_TOOL: buildah # ⚠ this is only an example ``` ## Understanding the Docker template @@ -33,19 +56,19 @@ select an alternate build tool by using the `DOCKER_BUILD_TOOL` variable (see be The Docker template uses some global configuration used throughout all jobs. -| Name | Description | Default value | +| Input / Variable | Description | Default value | | --------------------- | -------------------------------------- | ----------------- | -| `DOCKER_BUILD_TOOL` | The build tool to use for building container image, possible values are `kaniko`, `buildah` or `dind` | `kaniko` | -| `DOCKER_KANIKO_IMAGE` | The image used to run `kaniko` - _for kaniko build only_ | `gcr.io/kaniko-project/executor:debug` (use `debug` images for GitLab) | -| `DOCKER_BUILDAH_IMAGE` | The image used to run `buildah` - _for buildah build only_ | `quay.io/buildah/stable` | -| `DOCKER_IMAGE` | The Docker image used to run the docker client (see [full list](https://hub.docker.com/r/library/docker/)) - _for Docker-in-Docker build only_ | `registry.hub.docker.com/library/docker:latest` | -| `DOCKER_DIND_IMAGE` | The Docker image used to run the Docker daemon (see [full list](https://hub.docker.com/r/library/docker/)) - _for Docker-in-Docker build only_ | `registry.hub.docker.com/library/docker:dind` | -| `DOCKER_FILE` | The path to your `Dockerfile` | `./Dockerfile` | -| `DOCKER_CONTEXT_PATH` | The Docker [context path](https://docs.docker.com/engine/reference/commandline/build/#build-with-path) (working directory) | _none_ _only set if you want a context path different from the Dockerfile location_ | +| `build-tool` / `DOCKER_BUILD_TOOL` | The build tool to use for building container image, possible values are `kaniko`, `buildah` or `dind` | `kaniko` | +| `kaniko-image` / `DOCKER_KANIKO_IMAGE` | The image used to run `kaniko` - _for kaniko build only_ | `gcr.io/kaniko-project/executor:debug` (use `debug` images for GitLab) | +| `buildah-image` / `DOCKER_BUILDAH_IMAGE` | The image used to run `buildah` - _for buildah build only_ | `quay.io/buildah/stable` | +| `image` / `DOCKER_IMAGE` | The Docker image used to run the docker client (see [full list](https://hub.docker.com/r/library/docker/)) - _for Docker-in-Docker build only_ | `registry.hub.docker.com/library/docker:latest` | +| `dind-image` / `DOCKER_DIND_IMAGE` | The Docker image used to run the Docker daemon (see [full list](https://hub.docker.com/r/library/docker/)) - _for Docker-in-Docker build only_ | `registry.hub.docker.com/library/docker:dind` | +| `file` / `DOCKER_FILE` | The path to your `Dockerfile` | `Dockerfile` | +| `context-path` / `DOCKER_CONTEXT_PATH` | The Docker [context path](https://docs.docker.com/engine/reference/commandline/build/#build-with-path) (working directory) | _none_ _only set if you want a context path different from the Dockerfile location_ | In addition to this, the template supports _standard_ Linux proxy variables: -| Name | Description | Default value | +| Input / Variable | Description | Default value | | --------------------- | ------------------------------------------- | ------------- | | `http_proxy` | Proxy used for http requests | _none_ | | `https_proxy` | Proxy used for https requests | _none_ | @@ -72,10 +95,10 @@ In practice: The **snapshot** and **release** images are defined by the following variables: -| Name | Description | Default value | +| Input / Variable | Description | Default value | | ------------------------- | --------------------- | ------------------------------------------------- | -| `DOCKER_SNAPSHOT_IMAGE` | Docker snapshot image | `$CI_REGISTRY_IMAGE/snapshot:$CI_COMMIT_REF_SLUG` | -| `DOCKER_RELEASE_IMAGE` | Docker release image | `$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME` | +| `snapshot-image` / `DOCKER_SNAPSHOT_IMAGE` | Docker snapshot image | `$CI_REGISTRY_IMAGE/snapshot:$CI_COMMIT_REF_SLUG` | +| `release-image` / `DOCKER_RELEASE_IMAGE` | Docker release image | `$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME` | As you can see, the Docker template is configured by default to use the GitLab container registry. You may perfectly override this and use another Docker registry, but be aware of a few things: @@ -98,7 +121,7 @@ If you use the **same registry** for both snapshot and release images, you shall variables: -| Name | Description | +| Input / Variable | Description | | -------------------------------- | -------------------------------------- | | :lock: `DOCKER_REGISTRY_USER` | Docker registry username for image registry | | :lock: `DOCKER_REGISTRY_PASSWORD`| Docker registry password for image registry | @@ -107,7 +130,7 @@ variables: If you use **different registries** for snapshot and release images, you shall use separate configuration variables: -| Name | Description | +| Input / Variable | Description | | ---------------------------------------- | -------------------------------------- | | :lock: `DOCKER_REGISTRY_SNAPSHOT_USER` | Docker registry username for snapshot image registry | | :lock: `DOCKER_REGISTRY_SNAPSHOT_PASSWORD`| Docker registry password for snapshot image registry | @@ -128,9 +151,9 @@ If you are in one of those cases, you will need to use the `DOCKER_CONFIG_FILE` * leave the default value (`.docker/config.json`) or override it to some alternate location in your project repository and create the file **without any secret in it** using our dynamic variables replacement (see below), * or override it as a GitLab project variable of type [File](https://docs.gitlab.com/ee/ci/variables/#cicd-variable-types), possibly inlining your secret credentials in it. -| Name | Description | Default value | +| Input / Variable | Description | Default value | | ------------------------- | --------------------- | ------------------------------------------------- | -| `DOCKER_CONFIG_FILE` | Path to the Docker configuration file (JSON) | `.docker/config.json` | +| `config-file` / `DOCKER_CONFIG_FILE` | Path to the Docker configuration file (JSON) | `.docker/config.json` | Moreover, this file supports **dynamic environment variables replacement**. That means it may contain references to other environment variables (in the format `${variable_name}`) that will be dynamically replaced @@ -245,11 +268,11 @@ This job performs a [Lint](https://github.com/hadolint/hadolint) on your `Docker It is bound to the `build` stage, and uses the following variables: -| Name | Description | Default value | +| Input / Variable | Description | Default value | | -------------------------- | -------------------------------------- | --------------------------------------- | -| `DOCKER_HADOLINT_DISABLED` | Set to `true` to disable Hadolint | _(none: enabled by default)_ | -| `DOCKER_HADOLINT_IMAGE` | The Hadolint image | `registry.hub.docker.com/hadolint/hadolint:latest-alpine` | -| `DOCKER_HADOLINT_ARGS` | Additional `hadolint` arguments | _(none)_ | +| `hadolint-disabled` / `DOCKER_HADOLINT_DISABLED` | Set to `true` to disable Hadolint | _(none: enabled by default)_ | +| `hadolint-image` / `DOCKER_HADOLINT_IMAGE` | The Hadolint image | `registry.hub.docker.com/hadolint/hadolint:latest-alpine` | +| `hadolint-args` / `DOCKER_HADOLINT_ARGS` | Additional `hadolint` arguments | _(none)_ | In case you have to disable some rules, either add `--ignore XXXX` to the `DOCKER_HADOLINT_ARGS` variable or create a [Hadolint configuration file](https://github.com/hadolint/hadolint#configure) named `hadolint.yaml` at the root of your repository. @@ -276,18 +299,18 @@ This job builds the image and publishes it to the _snapshot_ repository. It is bound to the `package-build` stage, and uses the following variables: -| Name | Description | Default value | +| Input / Variable | Description | Default value | | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | -| `DOCKER_BUILD_ARGS` | Additional `docker/kaniko/buildah` `build` arguments | _(none)_ | -| `DOCKER_REGISTRY_MIRROR` | URL of a Docker registry mirror to use during the image build (instead of default `https://index.docker.io`) <br>:warning: Used by the `kaniko` and `dind` options only | _(none)_ | -| `CONTAINER_REGISTRIES_CONFIG_FILE` | The [`registries.conf`](https://www.redhat.com/sysadmin/manage-container-registries) configuration to be used<br>:warning: Used by the `buildah` build only | _(none)_ | -| `DOCKER_METADATA` | Additional `docker build`/`kaniko` arguments to set label | OCI Image Format Specification | -| `KANIKO_SNAPSHOT_IMAGE_CACHE` | Snapshot image repository that will be used to store cached layers<br>:warning: Used by the `kaniko` build only | `${DOCKER_SNAPSHOT_IMAGE%:*}/cache` | -| `DOCKER_BUILD_CACHE_DISABLED` | Set to `true` to disable the build cache.<br/>Cache can typically be disabled when there is a network latency between the container registry and the runner. | _none_ (i.e cache enabled) | +| `build-args` / `DOCKER_BUILD_ARGS` | Additional `docker/kaniko/buildah` `build` arguments | _(none)_ | +| `registry-mirror` / `DOCKER_REGISTRY_MIRROR` | URL of a Docker registry mirror to use during the image build (instead of default `https://index.docker.io`) <br>:warning: Used by the `kaniko` and `dind` options only | _(none)_ | +| `container-registries-config-file` / `CONTAINER_REGISTRIES_CONFIG_FILE` | The [`registries.conf`](https://www.redhat.com/sysadmin/manage-container-registries) configuration to be used<br>:warning: Used by the `buildah` build only | _(none)_ | +| `metadata` / `DOCKER_METADATA` | Additional `docker build`/`kaniko` arguments to set label | OCI Image Format Specification | +| `kaniko-snapshot-image-cache` / `KANIKO_SNAPSHOT_IMAGE_CACHE` | Snapshot image repository that will be used to store cached layers<br>:warning: Used by the `kaniko` build only | `${DOCKER_SNAPSHOT_IMAGE%:*}/cache` | +| `build-cache-disabled` / `DOCKER_BUILD_CACHE_DISABLED` | Set to `true` to disable the build cache.<br/>Cache can typically be disabled when there is a network latency between the container registry and the runner. | _none_ (i.e cache enabled) | This job produces _output variables_ that are propagated to downstream jobs (using [dotenv artifacts](https://docs.gitlab.com/ee/ci/pipelines/job_artifacts.html#artifactsreportsdotenv)): -| Name | Description | Example | +| Input / Variable | Description | Example | | --------------------- | ------------------------------------------------------ | --------------------------------------- | | `docker_image` | snapshot image name **with tag** | `registry.gitlab.com/acme/website/snapshot:main` | | `docker_image_digest` | snapshot image name **with digest** (no tag) | `registry.gitlab.com/acme/website/snapshot@sha256:b7914a91...` | @@ -346,12 +369,12 @@ This job performs a [Health Check](https://docs.docker.com/engine/reference/buil It is bound to the `package-test` stage, and uses the following variables: -| Name | Description | Default value | +| Input / Variable | Description | Default value | | -------------------------------------- | -------------------------------------------------------------------- | ----------------- | -| `DOCKER_HEALTHCHECK_DISABLED` | Set to `true` to disable health check | _(none: enabled by default)_ | -| `DOCKER_HEALTHCHECK_TIMEOUT` | When testing a Docker Health (test stage), how long (in seconds) wait for the [HealthCheck status](https://docs.docker.com/engine/reference/builder/#healthcheck) | `60` | -| `DOCKER_HEALTHCHECK_OPTIONS` | Docker options for health check such as port mapping, environment... | _(none)_ | -| `DOCKER_HEALTHCHECK_CONTAINER_ARGS` | Set arguments sent to the running container for health check | _(none)_ | +| `healthcheck-disabled` / `DOCKER_HEALTHCHECK_DISABLED` | Set to `true` to disable health check | _(none: enabled by default)_ | +| `healthcheck-timeout` / `DOCKER_HEALTHCHECK_TIMEOUT` | When testing a Docker Health (test stage), how long (in seconds) wait for the [HealthCheck status](https://docs.docker.com/engine/reference/builder/#healthcheck) | `60` | +| `healthcheck-options` / `DOCKER_HEALTHCHECK_OPTIONS` | Docker options for health check such as port mapping, environment... | _(none)_ | +| `healthcheck-container-args` / `DOCKER_HEALTHCHECK_CONTAINER_ARGS` | Set arguments sent to the running container for health check | _(none)_ | In case your Docker image is not intended to run as a service and only contains a *client tool* (like curl, Ansible, ...) you can test it by overriding the Health Check Job. See [this example](#overriding-docker-healthcheck). @@ -373,13 +396,13 @@ variables: It is bound to the `package-test` stage, and uses the following variables: -| Name | Description | Default value | +| Input / Variable | Description | Default value | | ---------------------- | -------------------------------------- | ----------------- | -| `DOCKER_TRIVY_IMAGE` | The docker image used to scan images with Trivy | `registry.hub.docker.com/aquasec/trivy:latest` | -| `DOCKER_TRIVY_ADDR` | The Trivy server address (for client/server mode) | _(none: standalone mode)_ | -| `DOCKER_TRIVY_SECURITY_LEVEL_THRESHOLD`| Severities of vulnerabilities to be displayed (comma separated values: `UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`) | `UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL` | -| `DOCKER_TRIVY_DISABLED`| Set to `true` to disable Trivy analysis | _(none)_ | -| `DOCKER_TRIVY_ARGS` | Additional [`trivy client` arguments](https://aquasecurity.github.io/trivy/v0.27.1/docs/references/cli/client/) | `--ignore-unfixed --vuln-type os` | +| `trivy-image` / `DOCKER_TRIVY_IMAGE` | The docker image used to scan images with Trivy | `registry.hub.docker.com/aquasec/trivy:latest` | +| `trivy-addr` / `DOCKER_TRIVY_ADDR` | The Trivy server address (for client/server mode) | _(none: standalone mode)_ | +| `trivy-security-level-threshold` / `DOCKER_TRIVY_SECURITY_LEVEL_THRESHOLD` | Severities of vulnerabilities to be displayed (comma separated values: `UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`) | `UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL` | +| `trivy-disabled` / `DOCKER_TRIVY_DISABLED` | Set to `true` to disable Trivy analysis | _(none)_ | +| `trivy-args` / `DOCKER_TRIVY_ARGS` | Additional [`trivy client` arguments](https://aquasecurity.github.io/trivy/v0.27.1/docs/references/cli/client/) | `--ignore-unfixed --vuln-type os` | In addition to a textual report in the console, this job produces the following reports, kept for one day: @@ -394,28 +417,28 @@ This job generates a [SBOM](https://cyclonedx.org/) file listing installed packa It is bound to the `package-test` stage, and uses the following variables: -| Name | description | default value | +| Input / Variable | Description | Default value | | --------------------- | -------------------------------------- | ----------------- | -| `DOCKER_SBOM_DISABLED` | Set to `true` to disable this job | _none_ | -| `DOCKER_SBOM_IMAGE` | The docker image used to emit SBOM | `registry.hub.docker.com/anchore/syft:debug` | -| `DOCKER_SBOM_OPTS` | Options for syft used for SBOM analysis | `--override-default-catalogers rpm-db-cataloger,alpm-db-cataloger,apk-db-cataloger,dpkg-db-cataloger,portage-cataloger` | +| `sbom-disabled` / `DOCKER_SBOM_DISABLED` | Set to `true` to disable this job | _none_ | +| `sbom-image` / `DOCKER_SBOM_IMAGE` | The docker image used to emit SBOM | `registry.hub.docker.com/anchore/syft:debug` | +| `sbom-opts` / `DOCKER_SBOM_OPTS` | Options for syft used for SBOM analysis | `--catalogers rpm-db-cataloger,alpm-db-cataloger,apk-db-cataloger,dpkg-db-cataloger,portage-cataloger,alpmdb-cataloger,apkdb-cataloger,dpkgdb-cataloger` | ### `docker-publish` job This job pushes (_promotes_) the built image as the _release_ image [skopeo](https://github.com/containers/skopeo). -| Name | Description | Default value | +| Input / Variable | Description | Default value | | --------------------- | --------------------------------------------------------------------------- | ----------------- | -| `DOCKER_SKOPEO_IMAGE` | The Docker image used to run [skopeo](https://github.com/containers/skopeo) | `quay.io/skopeo/stable:latest` | -| `DOCKER_PUBLISH_ARGS` | Additional [`skopeo copy` arguments](https://github.com/containers/skopeo/blob/master/docs/skopeo-copy.1.md#options) | _(none)_ | -| `DOCKER_PROD_PUBLISH_STRATEGY`| Defines the publish to production strategy. One of `manual` (i.e. _one-click_), `auto` or `none` (disabled). | `manual` | -| `DOCKER_RELEASE_EXTRA_TAGS_PATTERN` | Defines the image tag pattern that `$DOCKER_RELEASE_IMAGE` should match to push extra tags (supports capturing groups - [see below](#using-extra-tags)) | `^v?(?P<major>[0-9]+)\\.(?P<minor>[0-9]+)\\.(?P<patch>[0-9]+)(?P<suffix>(?P<prerelease>-[0-9A-Za-z-\\.]+)?(?P<build>\\+[0-9A-Za-z-\\.]+)?)$` _(SemVer pattern)_ | -| `DOCKER_RELEASE_EXTRA_TAGS` | Defines extra tags to publish the _release_ image (supports capturing group references from `$DOCKER_RELEASE_EXTRA_TAGS_PATTERN` - [see below](#using-extra-tags)) | _(none)_ | -| `DOCKER_SEMREL_RELEASE_DISABLED` | Set to `true` to disable [semantic-release integration](#semantic-release-integration) | _none_ (enabled) | +| `skopeo-image` / `DOCKER_SKOPEO_IMAGE` | The Docker image used to run [skopeo](https://github.com/containers/skopeo) | `quay.io/skopeo/stable:latest` | +| `publish-args` / `DOCKER_PUBLISH_ARGS` | Additional [`skopeo copy` arguments](https://github.com/containers/skopeo/blob/master/docs/skopeo-copy.1.md#options) | _(none)_ | +| `prod-publish-strategy` / `DOCKER_PROD_PUBLISH_STRATEGY` | Defines the publish to production strategy. One of `manual` (i.e. _one-click_), `auto` or `none` (disabled). | `manual` | +| `release-extra-tags-pattern` / `DOCKER_RELEASE_EXTRA_TAGS_PATTERN` | Defines the image tag pattern that `$DOCKER_RELEASE_IMAGE` should match to push extra tags (supports capturing groups - [see below](#using-extra-tags)) | `^v?(?P<major>[0-9]+)\\.(?P<minor>[0-9]+)\\.(?P<patch>[0-9]+)(?P<suffix>(?P<prerelease>-[0-9A-Za-z-\\.]+)?(?P<build>\\+[0-9A-Za-z-\\.]+)?)$` _(SemVer pattern)_ | +| `release-extra-tags` / `DOCKER_RELEASE_EXTRA_TAGS` | Defines extra tags to publish the _release_ image (supports capturing group references from `$DOCKER_RELEASE_EXTRA_TAGS_PATTERN` - [see below](#using-extra-tags)) | _(none)_ | +| `semrel-release-disabled` / `DOCKER_SEMREL_RELEASE_DISABLED` | Set to `true` to disable [semantic-release integration](#semantic-release-integration) | _none_ (enabled) | This job produces _output variables_ that are propagated to downstream jobs (using [dotenv artifacts](https://docs.gitlab.com/ee/ci/pipelines/job_artifacts.html#artifactsreportsdotenv)): -| Name | Description | Example | +| Input / Variable | Description | Example | | --------------------- | ----------------------------------------------------- | --------------------------------------- | | `docker_image` | release image name **with tag** | `registry.gitlab.com/acme/website:main` | | `docker_image_digest` | release image name **with digest** (no tag) | `registry.gitlab.com/acme/website@sha256:b7914a91...` | @@ -504,14 +527,11 @@ Here is a `.gitlab-ci.yaml` using an external Docker registry: ```yaml include: - - project: 'to-be-continuous/docker' - ref: '5.7.1' - file: '/templates/gitlab-ci-docker.yml' - -variables: - DOCKER_SNAPSHOT_IMAGE: "registry.acme.host/$CI_PROJECT_NAME/snapshot:$CI_COMMIT_REF_SLUG" - DOCKER_RELEASE_IMAGE: "registry.acme.host/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME" - # $DOCKER_REGISTRY_USER and $DOCKER_REGISTRY_PASSWORD are defined as secret GitLab variables + - component: gitlab.com/to-be-continuous/docker/gitlab-ci-docker@5.7.0 + inputs: + snapshot-image: "registry.acme.host/$CI_PROJECT_NAME/snapshot:$CI_COMMIT_REF_SLUG" + release-image: "registry.acme.host/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME" + # $DOCKER_REGISTRY_USER and $DOCKER_REGISTRY_PASSWORD are defined as secret GitLab variables ``` Depending on the Docker registry you're using, you may have to use a real password or generate a token as authentication credential. @@ -522,12 +542,7 @@ Here is a `.gitlab-ci.yaml` that builds 2 Docker images from the same project (u ```yaml include: - - project: 'to-be-continuous/docker' - ref: '5.7.1' - file: '/templates/gitlab-ci-docker.yml' - -variables: - DOCKER_DIND_BUILD: "true" + - component: gitlab.com/to-be-continuous/docker/gitlab-ci-docker@5.7.0 .docker-base: parallel: @@ -552,11 +567,11 @@ This variant allows delegating your secrets management to a [Vault](https://www. In order to be able to communicate with the Vault server, the variant requires the additional configuration parameters: -| Name | Description | Default value | +| 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` | The Vault server base API url | _none_ | -| `VAULT_OIDC_AUD` | The `aud` claim for the JWT | `$CI_SERVER_URL` | +| `vault-base-url` / `VAULT_BASE_URL` | The Vault server base API url | _none_ | +| `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 | **must be defined** | | :lock: `VAULT_SECRET_ID` | The [AppRole](https://www.vaultproject.io/docs/auth/approle) SecretID | **must be defined** | @@ -570,7 +585,7 @@ Then you may retrieve any of your secret(s) from Vault using the following synta With: -| Name | Description | +| Parameter | 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 | @@ -580,24 +595,21 @@ With: ```yaml include: # main template - - project: 'to-be-continuous/docker' - ref: '5.7.1' - file: '/templates/gitlab-ci-docker.yml' + - component: gitlab.com/to-be-continuous/docker/gitlab-ci-docker@5.7.0 # Vault variant - - project: 'to-be-continuous/docker' - ref: '5.7.1' - file: '/templates/gitlab-ci-docker-vault.yml' + - component: gitlab.com/to-be-continuous/docker/gitlab-ci-docker-vault@5.7.0 + inputs: + # audience claim for JWT + vault-oidc-aud: "https://vault.acme.host" + vault-base-url: "https://vault.acme.host/v1" + # $VAULT_ROLE_ID and $VAULT_SECRET_ID defined as a secret CI/CD variable variables: - # audience claim for JWT - VAULT_OIDC_AUD: "https://vault.acme.host" - # Secrets managed by Vault - DOCKER_REGISTRY_SNAPSHOT_USER: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/artifactory/snapshot/credentials?field=user" - DOCKER_REGISTRY_SNAPSHOT_PASSWORD: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/artifactory/snapshot/credentials?field=token" - DOCKER_REGISTRY_RELEASE_USER: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/artifactory/release/credentials?field=user" - DOCKER_REGISTRY_RELEASE_PASSWORD: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/artifactory/release/credentials?field=token" - VAULT_BASE_URL: "https://vault.acme.host/v1" - # $VAULT_ROLE_ID and $VAULT_SECRET_ID defined as a secret CI/CD variable + # Secrets managed by Vault + DOCKER_REGISTRY_SNAPSHOT_USER: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/artifactory/snapshot/credentials?field=user" + DOCKER_REGISTRY_SNAPSHOT_PASSWORD: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/artifactory/snapshot/credentials?field=token" + DOCKER_REGISTRY_RELEASE_USER: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/artifactory/release/credentials?field=user" + DOCKER_REGISTRY_RELEASE_PASSWORD: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/artifactory/release/credentials?field=token" ``` ### Google Cloud variant @@ -614,15 +626,16 @@ List of requirements before using this variant for publishing your container ima #### Configuration -| Name | description | default value | +| Input / Variable | Description | Default value | | ------------------------ | -------------------------------------- | ----------------- | | `TBC_GCP_PROVIDER_IMAGE` | The [GCP Auth Provider](https://gitlab.com/to-be-continuous/tools/gcp-auth-provider) image to use (can be overridden) | `registry.gitlab.com/to-be-continuous/tools/gcp-auth-provider:main` | -| `GCP_OIDC_PROVIDER` | Default Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/) | _none_ | -| `GCP_OIDC_ACCOUNT` | Default Service Account to which impersonate with OpenID Connect authentication | _none_ | -| `GCP_SNAPSHOT_OIDC_PROVIDER` | Workload Identity Provider to push the snapshot image _(only define if different from default)_ | _none_ | -| `GCP_SNAPSHOT_OIDC_ACCOUNT` | Service Account to use to push the snapshot image _(only define if different from default)_ | _none_ | -| `GCP_RELEASE_OIDC_PROVIDER` | Workload Identity Provider to push the release image _(only define if different from default)_ | _none_ | -| `GCP_RELEASE_OIDC_ACCOUNT` | Service Account to use to push the release image _(only define if different from default)_ | _none_ | +| `gcp-oidc-aud` / `GCP_OIDC_AUD` | The `aud` claim for the JWT token | `$CI_SERVER_URL` | +| `gcp-oidc-provider` / `GCP_OIDC_PROVIDER` | Default Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/) | _none_ | +| `gcp-oidc-account` / `GCP_OIDC_ACCOUNT` | Default Service Account to which impersonate with OpenID Connect authentication | _none_ | +| `gcp-snapshot-oidc-provider` / `GCP_SNAPSHOT_OIDC_PROVIDER` | Workload Identity Provider to push the snapshot image _(only define to override default)_ | _none_ | +| `gcp-snapshot-oidc-account` / `GCP_SNAPSHOT_OIDC_ACCOUNT` | Service Account to use to push the snapshot image _(only define to override default)_ | _none_ | +| `gcp-release-oidc-provider` / `GCP_RELEASE_OIDC_PROVIDER` | Workload Identity Provider to push the release image _(only define to override default)_ | _none_ | +| `gcp-release-oidc-account` / `GCP_RELEASE_OIDC_ACCOUNT` | Service Account to use to push the release image _(only define to override default)_ | _none_ | :warning: if using Kaniko, don't forget to either create the cache repository (snapshot image repository + `/cache`) or override `$KANIKO_SNAPSHOT_IMAGE_CACHE` to use the snapshot image repository (will host your snapshot image as well as cached layers). @@ -631,28 +644,24 @@ to use the snapshot image repository (will host your snapshot image as well as c ```yaml include: - - project: 'to-be-continuous/docker' - ref: "5.2.0" - file: '/templates/gitlab-ci-docker.yml' - - project: 'to-be-continuous/docker' - ref: "5.2.0" - file: '/templates/gitlab-ci-docker-gcp.yml' - -variables: - # untested & unverified container image - DOCKER_SNAPSHOT_IMAGE: "{GCP_REGION}-docker.pkg.dev/{GCP_PROJECT_ID}/{YOUR_REPOSITORY}/{YOUR_IMAGE_NAME}/snapshot:$CI_COMMIT_REF_SLUG" - # ⚠ don't forget to create the '{GCP_REGION}-docker.pkg.dev/{GCP_PROJECT_ID}/{YOUR_REPOSITORY}/{YOUR_IMAGE_NAME}/snapshot/cache' repo for Kaniko - # validated container image (published) - DOCKER_RELEASE_IMAGE: "{GCP_REGION}-docker.pkg.dev/{GCP_PROJECT_ID}/{YOUR_REPOSITORY}/{YOUR_IMAGE_NAME}:$CI_COMMIT_REF_NAME" - # default WIF provider - GCP_OIDC_PROVIDER: "projects/{GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/{YOUR_WIF_POOL_NAME}/providers/gitlab-diod" - # default GCP Service Account - GCP_OIDC_ACCOUNT: "{YOUR_REGISTRY_SA}@{GCP_PROJECT_ID}.iam.gserviceaccount.com" - # WIF provider for snapshot images - GCP_SNAPSHOT_OIDC_PROVIDER: "projects/{GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/{YOUR_WIF_POOL_NAME}/providers/gitlab-diod" - # GCP Service Account for snapshot images - GCP_SNAPSHOT_OIDC_ACCOUNT: "{YOUR_REGISTRY_SA}@{GCP_PROJECT_ID}.iam.gserviceaccount.com" - DOCKER_BUILD_TOOL: "kaniko" # Only Kaniko has been proved to work for this use case YET + - component: gitlab.com/to-be-continuous/docker/gitlab-ci-docker@5.7.0 + inputs: + build-tool: "kaniko" # Only Kaniko has been proved to work for this use case YET + # untested & unverified container image + snapshot-image: "{GCP_REGION}-docker.pkg.dev/{GCP_PROJECT_ID}/{YOUR_REPOSITORY}/{YOUR_IMAGE_NAME}/snapshot:$CI_COMMIT_REF_SLUG" + # ⚠ don't forget to create the '{GCP_REGION}-docker.pkg.dev/{GCP_PROJECT_ID}/{YOUR_REPOSITORY}/{YOUR_IMAGE_NAME}/snapshot/cache' repo for Kaniko + # validated container image (published) + release-image: "{GCP_REGION}-docker.pkg.dev/{GCP_PROJECT_ID}/{YOUR_REPOSITORY}/{YOUR_IMAGE_NAME}:$CI_COMMIT_REF_NAME" + - component: gitlab.com/to-be-continuous/docker/gitlab-ci-docker-gcp@5.7.0 + inputs: + # default WIF provider + gcp-oidc-provider: "projects/{GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/{YOUR_WIF_POOL_NAME}/providers/gitlab-diod" + # default GCP Service Account + gcp-oidc-account: "{YOUR_REGISTRY_SA}@{GCP_PROJECT_ID}.iam.gserviceaccount.com" + # WIF provider for snapshot images + gcp-snapshot-oidc-provider: "projects/{GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/{YOUR_WIF_POOL_NAME}/providers/gitlab-diod" + # GCP Service Account for snapshot images + gcp-snapshot-oidc-account: "{YOUR_REGISTRY_SA}@{GCP_PROJECT_ID}.iam.gserviceaccount.com" ``` ### Amazon Elastic Container Registry @@ -671,12 +680,12 @@ In order to use the AWS APIs, the variant supports two authentication methods: #### Configuration -| Name | description | default value | +| Input / Variable | Description | Default value | | ------------------------ | -------------------------------------- | ----------------- | | `TBC_AWS_PROVIDER_IMAGE` | The [AWS Auth Provider](https://gitlab.com/to-be-continuous/tools/aws-auth-provider) image to use (can be overridden) | `registry.gitlab.com/to-be-continuous/tools/aws-auth-provider:master` | -| `AWS_REGION` | Default region (where the ECR registry is located) | _none_ | -| `AWS_SNAPSHOT_REGION` | Region of the ECR registry for the snapshot image _(only define if different from default)_ | _none_ | -| `AWS_RELEASE_REGION` | Region of the ECR registry for the release image _(only define if different from default)_ | _none_ | +| `aws-region` / `AWS_REGION` | Default region (where the ECR registry is located) | _none_ | +| `aws-snapshot-region` / `AWS_SNAPSHOT_REGION` | Region of the ECR registry for the snapshot image _(only define to override default)_ | _none_ | +| `aws-release-region` / `AWS_RELEASE_REGION` | Region of the ECR registry for the release image _(only define to override default)_ | _none_ | :warning: if using Kaniko, don't forget to either create the cache repository (snapshot image repository + `/cache`) or override `$KANIKO_SNAPSHOT_IMAGE_CACHE` to use the snapshot image repository (will host your snapshot image as well as cached layers). @@ -686,42 +695,38 @@ to use the snapshot image repository (will host your snapshot image as well as c This is the recommended authentication method. In order to use it, first carefuly follow [GitLab's documentation](https://docs.gitlab.com/ee/ci/cloud_services/aws/), then set the required configuration. -| Name | description | default value | +| Input / Variable | Description | Default value | | ------------------------ | -------------------------------------- | ----------------- | -| `AWS_OIDC_AUD` | The `aud` claim for the JWT token | `$CI_SERVER_URL` | -| `AWS_OIDC_ROLE_ARN` | Default IAM Role ARN associated with GitLab | _none_ | -| `AWS_SNAPSHOT_OIDC_ROLE_ARN`| IAM Role ARN associated with GitLab for the snapshot image _(only define if different from default)_| _none_ | -| `AWS_RELEASE_OIDC_ROLE_ARN`| IAM Role ARN associated with GitLab for the release image _(only define if different from default)_| _none_ | +| `aws-oidc-aud` / `AWS_OIDC_AUD` | The `aud` claim for the JWT token | `$CI_SERVER_URL` | +| `aws-oidc-role-arn` / `AWS_OIDC_ROLE_ARN` | Default IAM Role ARN associated with GitLab | _none_ | +| `aws-snapshot-oidc-role-arn` / `AWS_SNAPSHOT_OIDC_ROLE_ARN` | IAM Role ARN associated with GitLab for the snapshot image _(only define to override default)_| _none_ | +| `aws-release-oidc-role-arn` / `AWS_RELEASE_OIDC_ROLE_ARN` | IAM Role ARN associated with GitLab for the release image _(only define to override default)_| _none_ | ##### Basic authentication config -| Name | description | default value | +| Variable | Description | Default value | | ------------------------ | -------------------------------------- | ----------------- | | `AWS_ACCESS_KEY_ID` | Default access key ID | _none_ (disabled) | | `AWS_SECRET_ACCESS_KEY` | Default secret access key | _none_ (disabled) | -| `AWS_SNAPSHOT_ACCESS_KEY_ID`| Access key ID for the snapshot image _(only define if different from default)_ | _none_ | -| `AWS_SNAPSHOT_SECRET_ACCESS_KEY`| Secret access key for the snapshot image _(only define if different from default)_ | _none_ | -| `AWS_RELEASE_ACCESS_KEY_ID`| Access key ID for the release image _(only define if different from default)_ | _none_ | -| `AWS_RELEASE_SECRET_ACCESS_KEY`| Secret access key for the release image _(only define if different from default)_ | _none_ | +| `AWS_SNAPSHOT_ACCESS_KEY_ID`| Access key ID for the snapshot image _(only define to override default)_ | _none_ | +| `AWS_SNAPSHOT_SECRET_ACCESS_KEY`| Secret access key for the snapshot image _(only define to override default)_ | _none_ | +| `AWS_RELEASE_ACCESS_KEY_ID`| Access key ID for the release image _(only define to override default)_ | _none_ | +| `AWS_RELEASE_SECRET_ACCESS_KEY`| Secret access key for the release image _(only define to override default)_ | _none_ | #### Example ```yaml include: - - project: 'to-be-continuous/docker' - ref: "5.2.0" - file: '/templates/gitlab-ci-docker.yml' - - project: 'to-be-continuous/docker' - ref: "5.2.0" - file: '/templates/gitlab-ci-docker-ecr.yml' - -variables: - AWS_REGION: "us-east-1" - # untested & unverified container image - DOCKER_SNAPSHOT_IMAGE: "123456789012.dkr.ecr.us-east-1.amazonaws.com/$CI_PROJECT_PATH/snapshot:$CI_COMMIT_REF_SLUG" - # ⚠ don't forget to create the '123456789012.dkr.ecr.us-east-1.amazonaws.com/$CI_PROJECT_PATH/snapshot/cache' repo for Kaniko - # validated container image (published) - DOCKER_RELEASE_IMAGE: "123456789012.dkr.ecr.us-east-1.amazonaws.com/$CI_PROJECT_PATH:$CI_COMMIT_REF_NAME" - # default Role ARN (using OIDC authentication method) - AWS_OIDC_ROLE_ARN: "arn:aws:iam::123456789012:role/gitlab-ci" + - component: gitlab.com/to-be-continuous/docker/gitlab-ci-docker@5.7.0 + inputs: + # untested & unverified container image + snapshot-image: "123456789012.dkr.ecr.us-east-1.amazonaws.com/$CI_PROJECT_PATH/snapshot:$CI_COMMIT_REF_SLUG" + # ⚠ don't forget to create the '123456789012.dkr.ecr.us-east-1.amazonaws.com/$CI_PROJECT_PATH/snapshot/cache' repo for Kaniko + # validated container image (published) + release-image: "123456789012.dkr.ecr.us-east-1.amazonaws.com/$CI_PROJECT_PATH:$CI_COMMIT_REF_NAME" + - component: gitlab.com/to-be-continuous/docker/gitlab-ci-docker-ecr@5.7.0 + inputs: + # default Role ARN (using OIDC authentication method) + aws-oidc-role-arn: "arn:aws:iam::123456789012:role/gitlab-ci" + aws-region: "us-east-1" ``` diff --git a/bumpversion.sh b/bumpversion.sh index f06829a..ed44d7b 100755 --- a/bumpversion.sh +++ b/bumpversion.sh @@ -27,13 +27,13 @@ if [[ "$curVer" ]]; then log_info "Bump version from \\e[33;1m${curVer}\\e[0m to \\e[33;1m${nextVer}\\e[0m (release type: $relType)..." # replace in README - sed -e "s/ref: '$curVer'/ref: '$nextVer'/" README.md > README.md.next + sed -e "s/ref: *'$curVer'/ref: '$nextVer'/" -e "s/ref: *\"$curVer\”/ref: \”$nextVer\”/" -e "s/component: *\(.*\)@$curVer/component: \1@$nextVer/" README.md > README.md.next mv -f README.md.next README.md # replace in template and variants for tmpl in templates/*.yml do - sed -e "s/\"$curVer\"/\"$nextVer\"/" "$tmpl" > "$tmpl.next" + sed -e "s/command: *\[\"--service\", \"\(.*\)\", \"$curVer\"\]/command: [\"--service\", \"\1\", \"$nextVer\"]/" "$tmpl" > "$tmpl.next" mv -f "$tmpl.next" "$tmpl" done else diff --git a/kicker.json b/kicker.json index 39dccad..6e62433 100644 --- a/kicker.json +++ b/kicker.json @@ -3,6 +3,8 @@ "description": "Build, check and inspect your containers with [Docker](https://www.docker.com/)", "template_path": "templates/gitlab-ci-docker.yml", "kind": "package", + "prefix": "docker", + "is_component": true, "variables": [ { "name": "DOCKER_BUILD_TOOL", @@ -39,7 +41,7 @@ { "name": "DOCKER_FILE", "description": "The path to your `Dockerfile`", - "default": "$CI_PROJECT_DIR/Dockerfile" + "default": "Dockerfile" }, { "name": "DOCKER_CONTEXT_PATH", @@ -265,6 +267,12 @@ "default": "registry.gitlab.com/to-be-continuous/tools/gcp-auth-provider:main", "advanced": true }, + { + "name": "GCP_OIDC_AUD", + "description": "The `aud` claim for the JWT token _(only required for [OIDC authentication](https://docs.gitlab.com/ee/ci/cloud_services/aws/))_", + "default": "$CI_SERVER_URL", + "advanced": true + }, { "name": "GCP_OIDC_ACCOUNT", "description": "Default Service Account to which impersonate with OpenID Connect authentication" @@ -275,22 +283,22 @@ }, { "name": "GCP_SNAPSHOT_OIDC_ACCOUNT", - "description": "Service Account to use to push the snapshot image _(only define if different from default)_", + "description": "Service Account to use to push the snapshot image _(only define to override default)_", "advanced": true }, { "name": "GCP_SNAPSHOT_OIDC_PROVIDER", - "description": "Workload Identity Provider to push the snapshot image _(only define if different from default)_", + "description": "Workload Identity Provider to push the snapshot image _(only define to override default)_", "advanced": true }, { "name": "GCP_RELEASE_OIDC_ACCOUNT", - "description": "Service Account to use to push the release image _(only define if different from default)_", + "description": "Service Account to use to push the release image _(only define to override default)_", "advanced": true }, { "name": "GCP_RELEASE_OIDC_PROVIDER", - "description": "Workload Identity Provider to push the release image _(only define if different from default)_", + "description": "Workload Identity Provider to push the release image _(only define to override default)_", "advanced": true } ] @@ -313,12 +321,12 @@ }, { "name": "AWS_SNAPSHOT_REGION", - "description": "Region of the ECR registry for the snapshot image _(only define if different from default)_", + "description": "Region of the ECR registry for the snapshot image _(only define to override default)_", "advanced": true }, { "name": "AWS_RELEASE_REGION", - "description": "Region of the ECR registry for the release image _(only define if different from default)_", + "description": "Region of the ECR registry for the release image _(only define to override default)_", "advanced": true }, { diff --git a/logo.png b/logo.png index 4b836a451211a05f853691ef34bd3cf879979970..fd1e2cff1ab094be66f5943aa631bb5e5558d2c6 100644 GIT binary patch literal 16871 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 literal 22570 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 diff --git a/templates/gitlab-ci-docker-ecr.yml b/templates/gitlab-ci-docker-ecr.yml index 25df3b4..7c52540 100644 --- a/templates/gitlab-ci-docker-ecr.yml +++ b/templates/gitlab-ci-docker-ecr.yml @@ -1,9 +1,46 @@ # ===================================================================================================================== # === AWS Auth template variant # ===================================================================================================================== +spec: + inputs: + aws-region: + description: Default region (where the ECR registry is located) + default: '' + aws-snapshot-region: + description: Region of the ECR registry for the snapshot image _(only define if + different from default)_ + default: '' + aws-release-region: + description: Region of the ECR registry for the release image _(only define if + different from default)_ + default: '' + aws-oidc-aud: + description: The `aud` claim for the JWT token _(only required for [OIDC authentication](https://docs.gitlab.com/ee/ci/cloud_services/aws/))_ + default: $CI_SERVER_URL + aws-oidc-role-arn: + description: Default IAM Role ARN associated with GitLab _(only required for [OIDC + authentication](https://docs.gitlab.com/ee/ci/cloud_services/aws/))_ + default: '' + aws-snapshot-oidc-role-arn: + description: IAM Role ARN associated with GitLab for the snapshot image _(only + required for [OIDC authentication](https://docs.gitlab.com/ee/ci/cloud_services/aws/) + and if different from default)_ + default: '' + aws-release-oidc-role-arn: + description: IAM Role ARN associated with GitLab for the release image _(only + required for [OIDC authentication](https://docs.gitlab.com/ee/ci/cloud_services/aws/) + and if different from default)_ + default: '' +--- variables: - TBC_AWS_PROVIDER_IMAGE: "registry.gitlab.com/to-be-continuous/tools/aws-auth-provider:master" - AWS_OIDC_AUD: "$CI_SERVER_URL" + TBC_AWS_PROVIDER_IMAGE: registry.gitlab.com/to-be-continuous/tools/aws-auth-provider:master + AWS_OIDC_AUD: $[[ inputs.aws-oidc-aud ]] + AWS_REGION: $[[ inputs.aws-region ]] + AWS_SNAPSHOT_REGION: $[[ inputs.aws-snapshot-region ]] + AWS_RELEASE_REGION: $[[ inputs.aws-release-region ]] + AWS_OIDC_ROLE_ARN: $[[ inputs.aws-oidc-role-arn ]] + AWS_SNAPSHOT_OIDC_ROLE_ARN: $[[ inputs.aws-snapshot-oidc-role-arn ]] + AWS_RELEASE_OIDC_ROLE_ARN: $[[ inputs.aws-release-oidc-role-arn ]] .docker-base: services: diff --git a/templates/gitlab-ci-docker-gcp.yml b/templates/gitlab-ci-docker-gcp.yml index 4c722f7..8162d6d 100644 --- a/templates/gitlab-ci-docker-gcp.yml +++ b/templates/gitlab-ci-docker-gcp.yml @@ -1,10 +1,46 @@ # ===================================================================================================================== # === GCP Auth template variant # ===================================================================================================================== +spec: + inputs: + gcp-oidc-aud: + description: The `aud` claim for the JWT token _(only required for [OIDC authentication](https://docs.gitlab.com/ee/ci/cloud_services/aws/))_ + default: $CI_SERVER_URL + gcp-oidc-account: + description: Default Service Account to which impersonate with OpenID Connect + authentication + default: '' + gcp-oidc-provider: + description: Default Workload Identity Provider associated with GitLab to [authenticate + with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/) + default: '' + gcp-snapshot-oidc-account: + description: Service Account to use to push the snapshot image _(only define if + different from default)_ + default: '' + gcp-snapshot-oidc-provider: + description: Workload Identity Provider to push the snapshot image _(only define + if different from default)_ + default: '' + gcp-release-oidc-account: + description: Service Account to use to push the release image _(only define if + different from default)_ + default: '' + gcp-release-oidc-provider: + description: Workload Identity Provider to push the release image _(only define + if different from default)_ + default: '' +--- variables: - TBC_GCP_PROVIDER_IMAGE: "registry.gitlab.com/to-be-continuous/tools/gcp-auth-provider:main" - GCP_OIDC_AUD: "$CI_SERVER_URL" - + TBC_GCP_PROVIDER_IMAGE: registry.gitlab.com/to-be-continuous/tools/gcp-auth-provider:main + GCP_OIDC_AUD: $[[ inputs.gcp-oidc-aud ]] + GCP_OIDC_ACCOUNT: $[[ inputs.gcp-oidc-account ]] + GCP_OIDC_PROVIDER: $[[ inputs.gcp-oidc-provider ]] + GCP_SNAPSHOT_OIDC_ACCOUNT: $[[ inputs.gcp-snapshot-oidc-account ]] + GCP_SNAPSHOT_OIDC_PROVIDER: $[[ inputs.gcp-snapshot-oidc-provider ]] + GCP_RELEASE_OIDC_ACCOUNT: $[[ inputs.gcp-release-oidc-account ]] + GCP_RELEASE_OIDC_PROVIDER: $[[ inputs.gcp-release-oidc-provider ]] + .docker-base: services: - name: "$TBC_TRACKING_IMAGE" diff --git a/templates/gitlab-ci-docker-vault.yml b/templates/gitlab-ci-docker-vault.yml index 79ed9e2..3c65ee4 100644 --- a/templates/gitlab-ci-docker-vault.yml +++ b/templates/gitlab-ci-docker-vault.yml @@ -1,13 +1,23 @@ # ===================================================================================================================== # === Vault template variant # ===================================================================================================================== +spec: + inputs: + vault-base-url: + description: The Vault server base API url + default: '' + vault-oidc-aud: + description: The `aud` claim for the JWT + default: $CI_SERVER_URL +--- variables: # variabilized vault-secrets-provider image - TBC_VAULT_IMAGE: "registry.gitlab.com/to-be-continuous/tools/vault-secrets-provider:master" + TBC_VAULT_IMAGE: registry.gitlab.com/to-be-continuous/tools/vault-secrets-provider:master + VAULT_BASE_URL: $[[ inputs.vault-base-url ]] # variables have to be explicitly declared in the YAML to be exported to the service VAULT_ROLE_ID: "$VAULT_ROLE_ID" VAULT_SECRET_ID: "$VAULT_SECRET_ID" - VAULT_OIDC_AUD: "$CI_SERVER_URL" + VAULT_OIDC_AUD: $[[ inputs.vault-oidc-aud ]] .docker-base: services: diff --git a/templates/gitlab-ci-docker.yml b/templates/gitlab-ci-docker.yml index e9f7637..0fcb3b0 100644 --- a/templates/gitlab-ci-docker.yml +++ b/templates/gitlab-ci-docker.yml @@ -13,6 +13,183 @@ # program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth # Floor, Boston, MA 02110-1301, USA. # ========================================================================================= +spec: + inputs: + build-tool: + description: The build tool to use for building container image + options: + - kaniko + - buildah + - dind + default: kaniko + kaniko-image: + description: |- + The image used to run kaniko + + _for kaniko build only_ + default: gcr.io/kaniko-project/executor:debug + buildah-image: + description: |- + The image used to run buildah + + _for buildah build only_ + default: quay.io/buildah/stable:latest + image: + description: |- + The image used to run the docker client + + _for Docker-in-Docker(dind) build only_ + default: registry.hub.docker.com/library/docker:latest + dind-image: + description: |- + The image used to run the Docker daemon + + _for Docker-in-Docker(dind) build only_ + default: registry.hub.docker.com/library/docker:dind + skopeo-image: + description: The image used to publish docker image with Skopeo + default: quay.io/skopeo/stable:latest + file: + description: The path to your `Dockerfile` + default: Dockerfile + context-path: + description: The Docker [context path](https://docs.docker.com/engine/reference/commandline/build/#build-with-path) (working directory) - _only set if you want a context path different from the Dockerfile location_ + default: '' + config-file: + description: Path to the [Docker configuration file](https://docs.docker.com/engine/reference/commandline/cli/#sample-configuration-file) (JSON) + default: .docker/config.json + snapshot-image: + description: Docker snapshot image + default: $CI_REGISTRY_IMAGE/snapshot:$CI_COMMIT_REF_SLUG + release-image: + description: Docker release image + default: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME + release-extra-tags-pattern: + description: |- + Defines the image tag pattern that `$DOCKER_RELEASE_IMAGE` should match to push extra tags (supports capturing groups) + + Defaults to [SemVer](https://semver.org/) pattern. + default: ^v?(?P<major>[0-9]+)\.(?P<minor>[0-9]+)\.(?P<patch>[0-9]+)(?P<suffix>(?P<prerelease>-[0-9A-Za-z-\.]+)?(?P<build>\+[0-9A-Za-z-\.]+)?)$ + release-extra-tags: + description: |- + Defines extra tags to publish the _release_ image + + Supports capturing group references from `$DOCKER_RELEASE_EXTRA_TAGS_PATTERN` (ex: `latest \g<major>.\g<minor> \g<major>`) + default: '' + build-args: + description: Additional docker/kaniko/buildah build arguments + default: '' + build-cache-disabled: + description: Disable the build cache + type: boolean + default: false + metadata: + description: Additional metadata to set as labels + default: >- + --label org.opencontainers.image.url=${CI_PROJECT_URL} + --label org.opencontainers.image.source=${CI_PROJECT_URL} + --label org.opencontainers.image.title=${CI_PROJECT_PATH} + --label org.opencontainers.image.ref.name=${CI_COMMIT_REF_NAME} + --label org.opencontainers.image.revision=${CI_COMMIT_SHA} + --label org.opencontainers.image.created=${CI_JOB_STARTED_AT} + publish-args: + description: Additional [`skopeo copy` arguments](https://github.com/containers/skopeo/blob/master/docs/skopeo-copy.1.md#options) + default: '' + prod-publish-strategy: + description: Defines the publish to production strategy. + options: + - none + - manual + - auto + default: manual + semrel-release-disabled: + description: Disable integration with the [semantic release template](https://gitlab.com/to-be-continuous/semantic-release/) + type: boolean + default: false + registry-mirror: + description: |- + URL of a Docker registry mirror to use instead of default `https://index.docker.io` + + _Used by `kaniko` and `dind` builds only_ + default: '' + container-registries-config-file: + description: |- + The [registries.conf](https://www.redhat.com/sysadmin/manage-container-registries) configuration to be used + + _Used by the `buildah` build only_ + default: '' + kaniko-snapshot-image-cache: + description: |- + Snapshot image repository that will be used to store cached layers. + + _Used by the `kaniko` build only_ + default: ${DOCKER_SNAPSHOT_IMAGE%:*}/cache + lint-enabled: + description: Enable dockerfile-lint + type: boolean + default: false + lint-image: + description: The docker image to lint your Dockerfile + default: registry.hub.docker.com/projectatomic/dockerfile-lint:latest + lint-args: + description: Additional `dockerfile_lint` arguments + default: '' + hadolint-disabled: + description: Disable Hadolint + type: boolean + default: false + hadolint-image: + description: The docker image to lint your Dockerfile with Hadolint + default: registry.hub.docker.com/hadolint/hadolint:latest-alpine + hadolint-args: + description: Additional `hadolint` arguments + default: '' + healthcheck-disabled: + description: Disable Health Check + type: boolean + default: false + healthcheck-timeout: + description: When testing an image, how long (in seconds) wait for the HealthCheck status + type: number + default: 60 + healthcheck-options: + description: Docker options for health check such as port mapping, environment... + default: '' + healthcheck-container-args: + description: Arguments sent to the running container for health check + default: '' + trivy-disabled: + description: Disable Trivy + type: boolean + default: false + trivy-image: + description: The docker image used to scan images with Trivy + default: registry.hub.docker.com/aquasec/trivy:latest + trivy-addr: + description: The Trivy server address + default: '' + trivy-security-level-threshold: + description: 'Severities of vulnerabilities to be displayed (comma separated values: `UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`)' + options: + - UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL + - LOW,MEDIUM,HIGH,CRITICAL + - MEDIUM,HIGH,CRITICAL + - HIGH,CRITICAL + - CRITICAL + default: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL + trivy-args: + description: Additional `trivy client` arguments + default: --ignore-unfixed --vuln-type os --exit-on-eol 1 + sbom-disabled: + description: Disable Software Bill of Materials + type: boolean + default: false + sbom-image: + default: registry.hub.docker.com/anchore/syft:debug + sbom-opts: + description: Options for syft used for SBOM analysis + default: --catalogers rpm-db-cataloger,alpm-db-cataloger,apk-db-cataloger,dpkg-db-cataloger,portage-cataloger,alpmdb-cataloger,apkdb-cataloger,dpkgdb-cataloger +--- # default workflow rules: Merge Request pipelines workflow: rules: @@ -57,40 +234,36 @@ workflow: variables: # variabilized tracking image - TBC_TRACKING_IMAGE: "registry.gitlab.com/to-be-continuous/tools/tracking:master" + TBC_TRACKING_IMAGE: registry.gitlab.com/to-be-continuous/tools/tracking:master + DOCKER_LINT_IMAGE: $[[ inputs.lint-image ]] + DOCKER_HADOLINT_IMAGE: $[[ inputs.hadolint-image ]] + DOCKER_IMAGE: $[[ inputs.image ]] + DOCKER_DIND_IMAGE: $[[ inputs.dind-image ]] + DOCKER_KANIKO_IMAGE: $[[ inputs.kaniko-image ]] + DOCKER_SKOPEO_IMAGE: $[[ inputs.skopeo-image ]] + DOCKER_BUILDAH_IMAGE: $[[ inputs.buildah-image ]] - DOCKER_HADOLINT_IMAGE: "registry.hub.docker.com/hadolint/hadolint:latest-alpine" - DOCKER_IMAGE: "registry.hub.docker.com/library/docker:latest" - DOCKER_DIND_IMAGE: "registry.hub.docker.com/library/docker:dind" - DOCKER_KANIKO_IMAGE: "gcr.io/kaniko-project/executor:debug" - DOCKER_SKOPEO_IMAGE: "quay.io/skopeo/stable:latest" - DOCKER_BUILDAH_IMAGE: "quay.io/buildah/stable:latest" - - # for retro-compatibility (deprecated & undocumented) - DOCKER_DOCKERFILE_PATH: "." - DOCKER_FILE: "$DOCKER_DOCKERFILE_PATH/Dockerfile" - DOCKER_CONFIG_FILE: ".docker/config.json" + DOCKER_FILE: $[[ inputs.file ]] + DOCKER_CONFIG_FILE: $[[ inputs.config-file ]] # When testing a Docker Health (test stage), how long (in seconds) wait for the HealthCheck status (https://docs.docker.com/engine/reference/builder/#healthcheck) - DOCKER_HEALTHCHECK_TIMEOUT: "60" + DOCKER_HEALTHCHECK_TIMEOUT: $[[ inputs.healthcheck-timeout ]] # Default Docker config uses the internal GitLab registry - DOCKER_SNAPSHOT_IMAGE: "$CI_REGISTRY_IMAGE/snapshot:$CI_COMMIT_REF_SLUG" - DOCKER_RELEASE_IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME" - - DOCKER_KANIKO_VERBOSITY: "info" + DOCKER_SNAPSHOT_IMAGE: $[[ inputs.snapshot-image ]] + DOCKER_RELEASE_IMAGE: $[[ inputs.release-image ]] - DOCKER_TRIVY_SECURITY_LEVEL_THRESHOLD: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL" - DOCKER_TRIVY_IMAGE: "registry.hub.docker.com/aquasec/trivy:latest" - DOCKER_TRIVY_ARGS: "--ignore-unfixed --vuln-type os --exit-on-eol 1" + DOCKER_TRIVY_SECURITY_LEVEL_THRESHOLD: $[[ inputs.trivy-security-level-threshold ]] + DOCKER_TRIVY_IMAGE: $[[ inputs.trivy-image ]] + DOCKER_TRIVY_ARGS: $[[ inputs.trivy-args ]] # SBOM genenration image and arguments - DOCKER_SBOM_IMAGE: "registry.hub.docker.com/anchore/syft:debug" - DOCKER_SBOM_OPTS: "--override-default-catalogers rpm-db-cataloger,alpm-db-cataloger,apk-db-cataloger,dpkg-db-cataloger,portage-cataloger" + DOCKER_SBOM_IMAGE: $[[ inputs.sbom-image ]] + DOCKER_SBOM_OPTS: $[[ inputs.sbom-opts ]] # default: one-click publish - DOCKER_PROD_PUBLISH_STRATEGY: manual - DOCKER_RELEASE_EXTRA_TAGS_PATTERN: "^v?(?P<major>[0-9]+)\\.(?P<minor>[0-9]+)\\.(?P<patch>[0-9]+)(?P<suffix>(?P<prerelease>-[0-9A-Za-z-\\.]+)?(?P<build>\\+[0-9A-Za-z-\\.]+)?)$" + DOCKER_PROD_PUBLISH_STRATEGY: $[[ inputs.prod-publish-strategy ]] + DOCKER_RELEASE_EXTRA_TAGS_PATTERN: $[[ inputs.release-extra-tags-pattern ]] # default production ref name (pattern) PROD_REF: '/^(master|main)$/' @@ -98,22 +271,30 @@ variables: INTEG_REF: '/^develop$/' # don't use CI_PROJECT_TITLE, kaniko doesn't support space in argument right now (https://github.com/GoogleContainerTools/kaniko/issues/1231) - DOCKER_METADATA: >- - --label org.opencontainers.image.url=${CI_PROJECT_URL} - --label org.opencontainers.image.source=${CI_PROJECT_URL} - --label org.opencontainers.image.title=${CI_PROJECT_PATH} - --label org.opencontainers.image.ref.name=${CI_COMMIT_REF_NAME} - --label org.opencontainers.image.revision=${CI_COMMIT_SHA} - --label org.opencontainers.image.created=${CI_JOB_STARTED_AT} + DOCKER_METADATA: $[[ inputs.metadata ]] # default to kaniko, possible options : kaniko|buildah|dind - DOCKER_BUILD_TOOL: - value: "kaniko" - options: - - "kaniko" - - "buildah" - - "dind" - description: "The build tool to use for building container image" + DOCKER_BUILD_TOOL: $[[ inputs.build-tool ]] + + DOCKER_CONTEXT_PATH: $[[ inputs.context-path ]] + DOCKER_RELEASE_EXTRA_TAGS: $[[ inputs.release-extra-tags ]] + DOCKER_BUILD_ARGS: $[[ inputs.build-args ]] + DOCKER_BUILD_CACHE_DISABLED: $[[ inputs.build-cache-disabled ]] + DOCKER_PUBLISH_ARGS: $[[ inputs.publish-args ]] + DOCKER_SEMREL_RELEASE_DISABLED: $[[ inputs.semrel-release-disabled ]] + DOCKER_REGISTRY_MIRROR: $[[ inputs.registry-mirror ]] + CONTAINER_REGISTRIES_CONFIG_FILE: $[[ inputs.container-registries-config-file ]] + KANIKO_SNAPSHOT_IMAGE_CACHE: $[[ inputs.kaniko-snapshot-image-cache ]] + DOCKER_LINT_ENABLED: $[[ inputs.lint-enabled ]] + DOCKER_LINT_ARGS: $[[ inputs.lint-args ]] + DOCKER_HADOLINT_DISABLED: $[[ inputs.hadolint-disabled ]] + DOCKER_HADOLINT_ARGS: $[[ inputs.hadolint-args ]] + DOCKER_HEALTHCHECK_DISABLED: $[[ inputs.healthcheck-disabled ]] + DOCKER_HEALTHCHECK_OPTIONS: $[[ inputs.healthcheck-options ]] + DOCKER_HEALTHCHECK_CONTAINER_ARGS: $[[ inputs.healthcheck-container-args ]] + DOCKER_TRIVY_DISABLED: $[[ inputs.trivy-disabled ]] + DOCKER_TRIVY_ADDR: $[[ inputs.trivy-addr ]] + DOCKER_SBOM_DISABLED: $[[ inputs.sbom-disabled ]] # ================================================== # Stages definition @@ -438,9 +619,9 @@ stages: kaniko_registry_mirror_option="--registry-mirror $(echo ${DOCKER_REGISTRY_MIRROR} | sed "s|^https*://||")" fi log_info "Build & deploy image $docker_image" - log_info "Kaniko command: /kaniko/executor --context $(docker_context_path) --dockerfile $DOCKER_FILE --destination $docker_image ${kaniko_cache_args} --verbosity $DOCKER_KANIKO_VERBOSITY $kaniko_registry_mirror_option $DOCKER_METADATA $DOCKER_BUILD_ARGS $*" + log_info "Kaniko command: /kaniko/executor --context $(docker_context_path) --dockerfile $DOCKER_FILE --destination $docker_image ${kaniko_cache_args} $kaniko_registry_mirror_option $DOCKER_METADATA $DOCKER_BUILD_ARGS $*" # shellcheck disable=SC2086 - /kaniko/executor --context "$(docker_context_path)" --dockerfile "$DOCKER_FILE" --destination "$docker_image" ${kaniko_cache_args} --verbosity $DOCKER_KANIKO_VERBOSITY $kaniko_registry_mirror_option $DOCKER_METADATA $DOCKER_BUILD_ARGS "$@" + /kaniko/executor ${TRACE+--verbosity debug} --context "$(docker_context_path)" --dockerfile "$DOCKER_FILE" --destination "$docker_image" ${kaniko_cache_args} $kaniko_registry_mirror_option $DOCKER_METADATA $DOCKER_BUILD_ARGS "$@" } # Used by containers tools like buildah, skopeo. -- GitLab