Skip to content

GitLab CI template for Cloud Foundry

This project implements a generic GitLab CI template for Cloud Foundry environments.

Overview

This template implements continuous delivery/continuous deployment for projects hosted on Cloud Foundry platforms.

It provides several features, usable in different modes.

Review environments

The template supports review environments: those are dynamic and ephemeral environments to deploy your ongoing developments (a.k.a. feature or topic branches).

When enabled, it deploys the result from upstream build stages to a dedicated and temporary environment. It is only active for non-production, non-integration branches.

It is a strict equivalent of GitLab's Review Apps feature.

It also comes with a cleanup job (accessible either from the environments page, or from the pipeline view).

Integration environment

If you're using a Git Workflow with an integration branch (such as Gitflow), the template supports an integration environment.

When enabled, it deploys the result from upstream build stages to a dedicated environment. It is only active for your integration branch (develop by default).

Production environments

Lastly, the template supports 2 environments associated to your production branch (master by default):

  • a staging environment (an iso-prod environment meant for testing and validation purpose),
  • the production environment.

You're free to enable whichever or both, and you can also choose your deployment-to-production policy:

  • continuous deployment: automatic deployment to production (when the upstream pipeline is successful),
  • continuous delivery: deployment to production can be triggered manually (when the upstream pipeline is successful).

Usage

Include

In order to include this template in your project, add the following to your gitlab-ci.yml:

include:
  - project: 'to-be-continuous/cloud-foundry'
    ref: '1.6.1'
    file: '/templates/gitlab-ci-cf.yml'

Global configuration

The Cloud Foundry template uses some global configuration used throughout all jobs.

Name description default value
CF_CLI_IMAGE the Docker image used to run CF CLI commands
⚠️ set the version required by your Cloud Foundry server
governmentpaas/cf-cli
CF_MANIFEST_BASENAME CF manifest file basename (without extension nor env suffix) manifest
CF_URL Default CF API url has to be defined
πŸ”’ CF_USER Default CF user name has to be defined
πŸ”’ CF_PASSWORD Default CF user password has to be defined
CF_ORG Default CF organization for project has to be defined
CF_BASE_APP_NAME Base application name $CI_PROJECT_NAME (see GitLab doc)
CF_DEFAULT_DOMAIN default CF domain (only define if you want to use a different domain from CF default) none
CF_SCRIPTS_DIR directory where CF scripts (manifest, hook scripts) are located . (root project dir)

Secrets management

Here are some advices about your secrets (variables marked with a πŸ”’):

  1. Manage them as project or group CI/CD variables:
    • masked to prevent them from being inadvertently displayed in your job logs,
    • protected if you want to secure some secrets you don't want everyone in the project to have access to (for instance production secrets).
  2. In case a secret contains characters that prevent it from being masked, simply define its value as the Base64 encoded value prefixed with @b64@: it will then be possible to mask it and the template will automatically decode it prior to using it.
  3. Don't forget to escape special characters (ex: $ -> $$).

Environments configuration

As seen above, the Cloud Foundry template may support up to 4 environments (review, integration, staging and production).

Each deployment job produces output variables that are propagated to downstream jobs (using dotenv artifacts):

  • environment_type: set to the type of environment (review, integration, staging or production),
  • environment_name: the application name (see below),
  • environment_url: set to $CI_ENVIRONMENT_URL.

They may be freely used in downstream jobs (for instance to run acceptance tests against the latest deployed environment).

Here are configuration details for each environment.

Review environments

Review environments are dynamic and ephemeral environments to deploy your ongoing developments (a.k.a. feature or topic branches).

They are disabled by default and can be enabled by setting the CF_REVIEW_SPACE variable (see below).

Here are variables supported to configure review environments:

Name description default value
CF_REVIEW_SPACE CF space for review env none (disabled)
CF_REVIEW_URL CF API url for review env (only define if different from default) $CF_URL
πŸ”’ CF_REVIEW_USER CF user name for review env (only define if different from default) $CF_USER
πŸ”’ CF_REVIEW_PASSWORD CF user password for review env (only define if different from default) $CF_PASSWORD
CF_REVIEW_ORG CF organization for review env (only define if different from default) $CF_ORG
CF_REVIEW_APP_NAME Application name for review env "${CF_BASE_APP_NAME}-${CI_ENVIRONMENT_SLUG}"
CF_REVIEW_DOMAIN CF domain for review env $CF_DEFAULT_DOMAIN
CF_REVIEW_HOST_NAME application host name for review env "${CF_BASE_APP_NAME}-${CI_ENVIRONMENT_SLUG}" (ex: myproject-review-fix-bug-12)
CF_REVIEW_ZERODOWNTIME Enables zero-downtime deployment on review env false
CF_REVIEW_ENVIRONMENT_SCHEME The review environment protocol scheme https
CF_REVIEW_ENVIRONMENT_DOMAIN The review environment domain none
CF_REVIEW_RETIRED_APP_SUFFIX If set, the app old version is not deleted/overriden but renamed with this suffix none

Note: By default review environment.url will be built as ${CF_REVIEW_ENVIRONMENT_SCHEME}://${$CI_PROJECT_NAME}-${CI_ENVIRONMENT_SLUG}.${CF_REVIEW_ENVIRONMENT_DOMAIN}

Integration environment

The integration environment is the environment associated to your integration branch (develop by default).

It is disabled by default and can be enabled by setting the CF_INTEG_SPACE variable (see below).

Here are variables supported to configure the integration environment:

Name description default value
CF_INTEG_SPACE CF space for integration env none (disabled)
CF_INTEG_URL CF API url for integration env (only define if different from default) $CF_URL
πŸ”’ CF_INTEG_USER CF user name for integration env (only define if different from default) $CF_USER
πŸ”’ CF_INTEG_PASSWORD CF user password for integration env (only define if different from default) $CF_PASSWORD
CF_INTEG_ORG CF organization for integration env (only define if different from default) $CF_ORG
CF_INTEG_APP_NAME Application name for integration env "${CF_BASE_APP_NAME}-integration"
CF_INTEG_DOMAIN CF domain for integration env $CF_DEFAULT_DOMAIN
CF_INTEG_HOST_NAME application host name for integration env "${CF_BASE_APP_NAME}-integration"
CF_INTEG_ZERODOWNTIME Enables zero-downtime deployment on integration env false
CF_INTEG_ENVIRONMENT_URL The integration environment url including scheme (ex: https://my-application-integration.nonpublic.domain.com). Do not use variable inside variable definition as it will result in a two level cascade variable and gitlab does not allow that. none
CF_INTEG_RETIRED_APP_SUFFIX If set, the app old version is not deleted/overriden but renamed with this suffix none

Staging environment

The staging environment is an iso-prod environment meant for testing and validation purpose associated to your production branch (master by default).

It is disabled by default and can be enabled by setting the CF_STAGING_SPACE variable (see below).

Here are variables supported to configure the staging environment:

Name description default value
CF_STAGING_SPACE CF space for staging env none (disabled)
CF_STAGING_URL CF API url for staging env (only define if different from default) $CF_URL
πŸ”’ CF_STAGING_USER CF user name for staging env (only define if different from default) $CF_USER
πŸ”’ CF_STAGING_PASSWORD CF user password for staging env (only define if different from default) $CF_PASSWORD
CF_STAGING_ORG CF organization for staging env (only define if different from default) $CF_ORG
CF_STAGING_APP_NAME Application name for staging env "${CF_BASE_APP_NAME}-staging"
CF_STAGING_DOMAIN CF domain for staging env $CF_DEFAULT_DOMAIN
CF_STAGING_HOST_NAME application host name for staging env "${CF_BASE_APP_NAME}-staging"
CF_STAGING_ZERODOWNTIME Enables zero-downtime deployment on staging env false
Β CF_STAGING_ENVIRONMENT_URL The staging environment url including scheme (ex: https://my-application-staging.nonpublic.domain.com). Do not use variable inside variable definition as it will result in a two level cascade variable and gitlab does not allow that. none
CF_STAGING_RETIRED_APP_SUFFIX If set, the app old version is not deleted/overriden but renamed with this suffix none

Production environment

The production environment is the final deployment environment associated with your production branch (master by default).

It is disabled by default and can be enabled by setting the CF_PROD_SPACE variable (see below).

Here are variables supported to configure the production environment:

Name description default value
CF_PROD_SPACE CF space for production env none (disabled)
CF_PROD_URL CF API url for production env (only define if different from default) $CF_URL
πŸ”’ CF_PROD_USER CF user name for production env (only define if different from default) $CF_USER
πŸ”’ CF_PROD_PASSWORD CF user password for production env (only define if different from default) $CF_PASSWORD
CF_PROD_ORG CF organization for production env (only define if different from default) $CF_ORG
CF_PROD_APP_NAME Application name for production env $CF_BASE_APP_NAME
CF_PROD_DOMAIN CF domain for production env $CF_DEFAULT_DOMAIN
CF_PROD_HOST_NAME application host name for production env $CF_BASE_APP_NAME
AUTODEPLOY_TO_PROD Determines the production deployment policy. When set, deployment to production is automatic (continuous deployment). When not set (default), deployment to production is manual (continuous delivery). none (continuous delivery)
CF_PROD_ZERODOWNTIME Enables zero-downtime deployment on production env true
CF_PROD_ENVIRONMENT_URL Β The production environment url including scheme (ex: https://my-application.public.domain.com) Do not use variable inside variable definition as it will result in a two level cascade variable and gitlab does not allow that. none
CF_PROD_RETIRED_APP_SUFFIX If set, the app old version is not deleted/overriden but renamed with this suffix none

Hook scripts

Deployment jobs also support optional hook scripts from your project, located in the $CF_SCRIPTS_DIR directory (root project dir by default, but may be overridden).

deployment hooks

The following hook scripts are supported on deployment jobs:

  • cf-pre-push.sh is executed before running cf push, to perform specific environment pre-initialization (for e.g. create required services)
  • cf-pre-start.sh this hook script is only necessary if your application has dependencies that cannot be described in the manifest but instead require the CF CLI (for e.g. mapping internal routes or binding services with arbitrary parameters).
    When found, the cf push operation is executed with the --no-start option, then this hook script is executed, and finally your app is cf start-ed.
  • cf-readiness-check is executed after the application has been started (cf push or cf start depending on cf-pre-start.sh presence) to wait & check for the application to be ready (if not found, the template assumes the application was successfully started),
  • cf-post-push.sh is executed at the end of the deploy operation, to perform specific environment post-initialization (for e.g. run tasks or schedule job).
  • cf-post-bluegreen.sh is executed at the end of the bluegreen deployment, to perform specific environment post-initialization (for e.g. run tasks or schedule job).

Deployment hook scripts may use the following environment variables:

  • $appname: the application target name to use in this environment
  • $env: the environment type (review, integration, staging or production)
  • $tmpappname: the application current name being used during the deployment in this environment (different from $appname during a blue/green deployment for instance)
  • $hostname: the current hostname being used during the deployment in this environment
  • $domain: the Cloud Foundry domain being used during the deployment in this environment
  • any GitLab CI environment.

cleanup hooks

The following hook scripts are supported on cleanup jobs:

  • cf-pre-cleanup.sh is executed before env cleanup, to perform specific environment pre-cleanup
  • cf-post-cleanup.sh is executed after env cleanup, to perform specific environment post-cleanup (for e.g. delete bound services)

Cleanup hook scripts may use the following environment variables:

Cleanup job limitations

When using this template, you have to be aware of one limitation (bug) with the cleanup job.

By default, the cleanup job triggered automatically on branch deletion will fail due to not being able to fetch the Git branch prior to executing the job (sounds obvious as the branch was just deleted). This is pretty annoying, but as you may see above, deleting an env may require scripts from the project...

So, what can be done about that?

  1. if your project doesn't require any delete script (in other words a simple cf delete is enough to clean-up everything): you could simply override the cleanup job Git strategy to prevent from fetching the branch code:
    cf-cleanup-review:
      variables:
        GIT_STRATEGY: none
    
  2. in any other case, we're just sorry about this bug, but there is not much we can do:
    • remind to delete your review env manually before deleting the branch
    • otherwise you'll have to do it afterwards from your computer (using cf CLI) or from the Cloud Foundry console.

Manifest processing

Deployment jobs support a versatile way to evaluate the deployment manifest.

Manifest file lookup

  1. look for a specific manifest-$env.yml in your project (e.g. manifest-staging.yml for staging environment),
  2. fallbacks to default manifest.yml.

Variables replacement support

Deployment jobs support Cloud Foundry variables replacement with the following variables:

  • ((appname)): the application target name to use in this environment
  • ((env)): the environment type (review, integration, staging or production)
  • ((hostname)): the current hostname being used during the deployment in this environment
  • merged with file cf-vars-$env.yml if present in your project (e.g. cf-vars-staging.yml for staging environment).
  • or merged with file cf-vars.yml if present in your project and no cf-vars-$env.yml file was found.

⚠️ your cf-vars-$env.yml or cf-vars.yml files may contain variable patterns such as ${MY_SECRET}. If so, those patterns will be evaluated (replaced) with actual environment values. Multiline variables must be surrounded by double quotes ("). This is a safe way of managing your application secrets.

Routes management

Deployment jobs support several ways of managing mapped routes.

Here is the general recommendation (for each one of your environments):

  • if the application is mapped to one single route:
    • do not declare any route in the manifest,
    • set the $CF_xxx_HOST_NAME variable to override the hostname to use (or leave unset to use default),
    • set the $CF_DEFAULT_DOMAIN or $CF_xxx_DOMAIN variable to define the domain to use (or leave unset if you wish to use the default CF domain).
  • if the application is mapped to several routes:
    • declare the routes in your manifest, possibly using ((appname)) and ((hostname)) variables,
    • set the $CF_xxx_HOST_NAME variable to override the hostname to use (or leave unset to use default),
    • $CF_DEFAULT_DOMAIN or $CF_xxx_DOMAIN variables won't be used.

cf-cleanup-all-review job

This job allows destroying all review environments at once (in order to save cloud resources).

It is disabled by default and can be controlled using the $CLEANUP_ALL_REVIEW variable:

  1. automatically executed if $CLEANUP_ALL_REVIEW set to force,
  2. manual job enabled from any master branch pipeline if $CLEANUP_ALL_REVIEW set to true (or any other value),

The first value force can be used in conjunction with a scheduled pipeline to cleanup cloud resources for instance everyday at 6pm or on friday evening.

The second one simply enables the (manual) cleanup job on the master branch pipeline.

Anyway destroyed review environments will be automatically re-created the next time a developer pushes a new commit on a feature branch.

⚠️ in case of scheduling the cleanup, you'll probably have to create an almost empty branch without any other template (no need to build/test/analyse your code if your only goal is to cleanup environments).

Variants

Vault variant

This variant allows delegating your secrets management to a Vault server.

Configuration

In order to be able to communicate with the Vault server, the variant requires the additional configuration parameters:

Name description default value
VAULT_BASE_URL The Vault server base API url none
πŸ”’ VAULT_ROLE_ID The AppRole RoleID must be defined
πŸ”’ VAULT_SECRET_ID The AppRole SecretID must be defined

Usage

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

include:
  # main template
  - project: 'to-be-continuous/cloud-foundry'
    ref: '1.6.1'
    file: '/templates/gitlab-ci-cf.yml'
  # Vault variant
  - project: 'to-be-continuous/cloud-foundry'
    ref: '1.6.1'
    file: '/templates/gitlab-ci-cf-vault.yml'

variables:
    # Secrets managed by Vault
    CF_USER: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/my-app/cf/noprod?field=user"
    CF_PASSWORD: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/my-app/cf/noprod?field=password"
    CF_PROD_USER: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/my-app/cf/prod?field=user"
    CF_PROD_PASSWORD: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/my-app/cf/prod?field=password"
    VAULT_BASE_URL: "https://vault.acme.host/v1"
    # $VAULT_ROLE_ID and $VAULT_SECRET_ID defined as a secret CI/CD variable

Examples

Ajax front-end

Context

  • Review environment enabled,
  • Continuous deployment to production,
  • internal route for review envs,
  • internet route for production.

.gitlab-ci.yml

include:
  - project: 'to-be-continuous/cloud-foundry'
    ref: '1.6.1'
    file: '/templates/gitlab-ci-cf.yml'

# Global variables
variables:
  # specific project variables
  # TODO
  # Cloud Foundry CI template variables
  CF_URL: "https://api.cloud-foundry.acme.host"
  CF_ORG: "MyProject" # one single Organization for all spaces
  # CF_USER and CF_PASSWORD are defined as protected project CI/CD variables
  CF_REVIEW_SPACE: "Integration"
  CF_REVIEW_ENVIRONMENT_DOMAIN: "apps.cloud-foundry.acme.host" # intranet route
  # CF_STAGING_SPACE not defined: no staging environment; continuous deployment
  CF_PROD_SPACE: "Production"
  # CF_DEFAULT_DOMAIN not defined: use CF default domain by default
  CF_PROD_DOMAIN: "acme.com" # on prod: use my own internet domain
  CF_PROD_ENVIRONMENT_URL: "https://frontend.myproject.acme.com" # internet route
  AUTODEPLOY_TO_PROD: "true"

# Pipeline steps
stages:
  - build
  - deploy
  - production

build:
  stage: build
  script:
    - echo "build the front-end (TODO)"
  only:
    refs:
      - branches

manifest generation stuff

When deploying to review environment, the application name is generated (after the project and branch name). So the project should either not specify the application name in the manifest, or use a specific manifest-review.yml with variabilized ((appname)).

Spring Boot back-end

Context

  • uses a MySQL service,
  • Review environment enabled,
  • Continuous delivery: automatic deployment to staging env, automatic functional tests on staging, and manual deployment to production.

.gitlab-ci.yml

include:
  - project: 'to-be-continuous/cloud-foundry'
    ref: '1.6.1'
    file: '/templates/gitlab-ci-cf.yml'

# Global variables
variables:
  # specific project variables
  # TODO
  # Cloud Foundry CI template variables
  CF_URL: "https://api.cloud-foundry.acme.host"
  CF_ORG: "MyProject" # one single Organization for all spaces
  # CF_USER and CF_PASSWORD are defined as protected project CI/CD variables
  CF_REVIEW_SPACE: "Integration" # enables review environments
  CF_REVIEW_ENVIRONMENT_SCHEME: "https"
  CF_REVIEW_ENVIRONMENT_DOMAIN: "apps.cloud-foundry.acme.host" # intranet route
  CF_STAGING_SPACE: "Preprod" # enables staging environment
  CF_STAGING_ENVIRONMENT_URL: "https://backend-staging.apps.cloud-foundry.acme.host" # intranet route
  CF_PROD_SPACE: "Production" # enables prpoduction environment
  # CF_xxx_DOMAIN not defined: use CF default domain for review and staging; routes are declared explicitly for production
  CF_PROD_ENVIRONMENT_URL: "https://backend.myproject.acme.com" # internet route

# Pipeline steps
stages:
  - build
  - deploy
  - production

build:
  stage: build
  script:
    - mvn $MAVEN_CLI_OPTS clean package
  artifacts:
    paths:
      - target/*.jar
      - manifest.yml
  only:
    refs:
      - branches

manifest generation stuff

manifest.yml
---
applications:
- path: target/myproject-backend-1.0.0-SNAPSHOT.jar
  memory: 768m
  instances: 1
  services:
    - ((appname))-db

This file will be used for review and staging environments, and uses variables for the MySQL service name (built from ((appname))).

The manifest doesn't need to specify the application name because it is explicitly set by the template deployment scripts.

It doesn't declare any route, therefore delegates the routes management to the template:

  • review apps will be mapped to default CF domain with "${$CI_PROJECT_NAME}-${CI_ENVIRONMENT_SLUG}" hostname
  • staging app will be mapped to default CF domain with "${$CI_PROJECT_NAME}-staging" hostname
manifest-production.yml
---
applications:
- path: target/myproject-backend-1.0.0-SNAPSHOT.jar
  memory: 2G
  instances: 3
  routes:
  # internet route
  - route: backend.myproject.acme.com
  # intranet route
  - route: myproject-backend.apps.cloud-foundry.acme.host
  services:
    - myproject-backend-db

This file will be used for production env only.

The manifest doesn't use variables as it is only used for production env.

It also uses specific scalability settings (instances & memory).

hook scripts

cf-pre-push.sh

This script should ensure the required database is created, naming the database service after the application target name ($appname).

#!/bin/bash
dbname="${appname:-myproject-backend}-db"
echo "maybe create database '$dbname'..."
cf service $dbname || cf create-service c-mysql 100mb $dbname
cf-readiness-check.sh

This script - when found by the template - is used to wait & check for the application to be ready.

It uses the provided $hostname and $domain variables to build absolute urls to the application.

It is supposed to exit with status 0 on success (the template will go on with deployment), or any non-0 value in case of error (the template will stop and as much as possible revert the ongoing deployment).

#!/bin/bash
for attempt in {1..5}
do
  echo "Testing application readiness ($attempt/5)..."
  if curl --fail --silent --insecure --write-out "\n--> response status: %{http_code}\n" https://$hostname.$domain/actuator/health
  then
    echo "[INFO] healthcheck responsed: success"
    exit 0
  fi
  sleep 3
done

echo "[ERROR] max attempts reached: failed"
exit 1
cf-post-cleanup.sh

This script should cleanup the database, naming the database service after the application target name ($appname).

#!/bin/bash
dbname="${appname:-myproject-backend}-db"
echo "maybe delete database '$dbname'..."
cf delete-service $dbname -f

GitLab compatibility

ℹ️ This template is actually tested and validated on GitLab Community Edition instance version 13.12.11