Setting up CI/CD pipelines

Contents

This guide describes how you create a CI/CD pipeline that builds, tests and deploys your XP apps with your favorite CI/CD service.

CI/CD images

Enonic builds images specifically made to be used in CI/CD pipelines. Those images can be used with different CI/CD providers to create pipelines. Each image contains the newest minor version of Enonic XP 7, the newest version of the Enonic CLI and some other essentials for build pipelines. The image name format is enonic/enonic-ci:<Minor Version><Optional Postfix>. If you need Yarn or other Node tools, use the -node postfix images for all versions to 7.13.

Starting on version 7.14, the -node image has been made the base image while no image is made without these tools.

  • enonic/enonic-ci:7.12

  • enonic/enonic-ci:7.13

  • enonic/enonic-ci:7.12-node

  • enonic/enonic-ci:7.13-node

  • enonic/enonic-ci:7.14

You can see a full list of available images on Docker Hub.

Environmental variables

These variables are only needed if you plan to deploy your XP app in your pipeline.
  • ENONIC_CLI_REMOTE_URL is the address of the management port of the server you want to deploy to, i.e https://myserver.com:4848 or https://myserver.com:4443.

  • ENONIC_CLI_REMOTE_USER is the user used to deploy.

  • ENONIC_CLI_REMOTE_PASS is the password of the respective user.

  • CLIENT_CERT is an optional variable used only for mTLS based app deployment with github action

  • CLIENT_KEY is an optional variable used only for mTLS based app deployment with github action

  • APP_JAR is an optional variable in githubaction to set path and name of the jar file to be deployed. When not given, github action looks for a .jar file under build/libs.

You can define multiple servers and based on some conditions pick the right server to deploy to. For now we will keep it simple, but if you want to see how that is done see section: Deploying with conditions.

CI/CD providers

Depending on your CI/CD provider the pipeline configuration may vary. In this section we will show how to do minimal configuration so that every time you commit to your repository your app will be built, tested and deployed.

Drone

  1. Setup Drone for your project.

  2. Set required environmental variables for your project for Drone.

  3. Create pipeline file .drone.yml in your repository:

    kind: pipeline
    type: docker
    name: default
    
    steps:
      - name: build & test app
        image: enonic/enonic-ci:{version}
        commands:
          - /setup_sandbox.sh
          - enonic project build
    
      - name: deploy App
        image: enonic/enonic-ci:{version}
        environment:
          ENONIC_CLI_REMOTE_URL:
            from_secret: ENONIC_CLI_REMOTE_URL
          ENONIC_CLI_REMOTE_USER:
            from_secret: ENONIC_CLI_REMOTE_USER
          ENONIC_CLI_REMOTE_PASS:
            from_secret: ENONIC_CLI_REMOTE_PASS
        commands:
          - enonic app install --file build/libs/*.jar
  4. Push file to repository:

    git add .drone.yml
    git commit -m "Add Drone pipeline"
    git push
  5. Wait for Drone to build, test and deploy your app to your server.

CircleCI

  1. Setup CircleCI for your project.

  2. Set required environmental variables for your project for CircleCI.

  3. Create pipeline file .circleci/config.yml in your repository:

    version: 2.0
    jobs:
      build:
        working_directory: ~/app
        docker:
          - image: enonic/enonic-ci:{version}
        steps:
          - checkout
          - run:
              name: Setup sandbox
              command: /setup_sandbox.sh
          - run:
              name: Build and Test App
              command: enonic project build
          - run:
              name: Deploy App
              command: enonic app install --file build/libs/*.jar
  4. Push file to repository:

    git add .circleci/config.yml
    git commit -m "Add CircleCI pipeline"
    git push
  5. Wait for CircleCI to build, test and deploy your app to your server.

Github Actions

  1. Setup Github Actions for your project.

  2. Set required environmental variables for your project for Github Actions.

  3. Create pipeline file .github/workflows/enonic.yml in your repository:

    name: Enonic CI Server deploy script
    
    on: [<triggering_event>]
    
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - id: build
            uses: enonic/release-tools/action-app-build@master
          - id: deploy
            uses: enonic/action-app-deploy@main
            with:
              url: ${{ secrets.ENONIC_CLI_REMOTE_URL }}
              username: ${{ secrets.ENONIC_CLI_REMOTE_USER }}
              password: ${{ secrets.ENONIC_CLI_REMOTE_PASS }}
              client_cert: ${{ secrets.CLIENT_CERT }}
              client_key: ${{ secrets.CLIENT_KEY }}
              app_jar: "<path and name of the app>"
  4. Push file to repository:

    git add .github/workflows/enonic.yml
    git commit -m "Add Github Actions pipeline"
    git push
  5. Wait for Github Actions to build, test and deploy your app to your server.

Travis CI

  1. Setup Travis CI for your project.

  2. Set required environmental variables for your project for Travis CI.

  3. Create pipeline file .travis.yml in your repository:

    Travis does not allow you to run custom images, so we will use their prebuilt images instead and deploy your app with curl.
    language: java
    
    jdk:
      - openjdk11
    
    # No need to specify build/test step, Travis CI does that for us
    
    after_success:
      # We pipe the curl command to xargs echo to be able
      # to view the output in the Travis dashboard
      - |
        curl -X POST -f -s -S -o - \
          -u $ENONIC_CLI_REMOTE_USER:$ENONIC_CLI_REMOTE_PASS \
          -F "file=@$(find build/libs/ -name '*.jar')" \
          $ENONIC_CLI_REMOTE_URL/app/install | xargs echo
  4. Push file to repository:

    git add .travis.yml
    git commit -m "Add Travis CI pipeline"
    git push
  5. Wait for Travis CI to build, test and deploy your app to your server.

Jenkins

  1. Setup Jenkins for your project.

  2. Set required environmental variables for your project for Jenkins.

  3. Create pipeline file Jenkinsfile in your repository:

    pipeline {
      agent {
        docker {
          image 'enonic/enonic-ci:{version}'
        }
      }
      environment {
        ENONIC_CLI_REMOTE_URL  = credentials('jenkins-enonic-url')
        ENONIC_CLI_REMOTE_USER = credentials('jenkins-enonic-user')
        ENONIC_CLI_REMOTE_PASS = credentials('jenkins-enonic-pass')
      }
      stages {
        stage('Build and Test App') {
          steps {
            sh '/setup_sandbox.sh'
            sh 'enonic project build'
          }
        }
        stage('Deploy App') {
          steps {
            sh 'enonic app install --file build/libs/*.jar'
          }
        }
      }
    }
  4. Push file to repository:

    git add Jenkinsfile
    git commit -m "Add Jenkins pipeline"
    git push
  5. Wait for Jenkins to build, test and deploy your app to your server.

Deploying with conditions

In this section we are going to take the pipeline a step further. Instead of building, testing and deploying on every commit, we will introduce some conditions.

Using Drone

Conditions

In this example our conditions will be:

  • Deploy to testing server on:

    • All pull requests to master branch

  • Deploy to staging server on:

    • All commits in master branch

  • Deploy to production server on:

    • All builds that are promoted to production

Drone CLI

To setup secrets and do promotions it is easiest to use the Drone CLI. So start by installing the Drone CLI.

Now you have to get your access token for the Drone CLI. You can find it in your user profile on the drone server. If you are using the Drone Cloud service you can view it here.

To test your access token run the commands:

export DRONE_SERVER=https://cloud.drone.io
export DRONE_TOKEN=<your_access_token>
drone info

Creating secrets

Now create secrets for testing, staging and production servers. To do this run the commands:

ORG=<your_github_username_or_organization>

drone orgsecret add $ORG testing-url http://<xp_testing_server>:4848 --allow-pull-request
drone orgsecret add $ORG testing-user <xp_testing_user> --allow-pull-request
drone orgsecret add $ORG testing-pass <xp_testing_pass> --allow-pull-request

drone orgsecret add $ORG staging-url http://<xp_staging_server>:4848
drone orgsecret add $ORG staging-user <xp_staging_user>
drone orgsecret add $ORG staging-pass <xp_staging_pass>

drone orgsecret add $ORG production-url http://<xp_production_server>:4848
drone orgsecret add $ORG production-user <xp_production_user>
drone orgsecret add $ORG production-pass <xp_production_pass>

Create pipelines

Now we follow the same steps as before for Drone, but now our .drone.yml looks a bit different:

kind: pipeline
type: docker
name: "Test Environment"

# Deploy to testing on pull requests to master branch
trigger:
  branch:
    - master
  event:
    include:
      - pull_request

steps:
  - name: build & test app
    image: enonic/enonic-ci:{version}
    commands:
      - /setup_sandbox.sh
      - enonic project build

  - name: deploy
    image: enonic/enonic-ci:{version}
    environment:
      ENONIC_CLI_REMOTE_URL:
        from_secret: testing-url
      ENONIC_CLI_REMOTE_USER:
        from_secret: testing-user
      ENONIC_CLI_REMOTE_PASS:
        from_secret: testing-pass
    commands:
      - enonic app install --file build/libs/*.jar

---
kind: pipeline
type: docker
name: "Staging Environment"

# Deploy to staging push to master branch
trigger:
  branch:
    - master
  event:
    include:
      - push

steps:
  - name: build & test app
    image: enonic/enonic-ci:{version}
    commands:
      - /setup_sandbox.sh
      - enonic project build

  - name: deploy
    image: enonic/enonic-ci:{version}
    environment:
      ENONIC_CLI_REMOTE_URL:
        from_secret: staging-url
      ENONIC_CLI_REMOTE_USER:
        from_secret: staging-user
      ENONIC_CLI_REMOTE_PASS:
        from_secret: staging-pass
    commands:
      - enonic app install --file build/libs/*.jar

---
kind: pipeline
type: docker
name: "Production Environment"

# Deploy to production on promoted builds
trigger:
  target:
    - production

steps:
  - name: build & test app
    image: enonic/enonic-ci:{version}
    commands:
      - /setup_sandbox.sh
      - enonic project build

  - name: deploy
    image: enonic/enonic-ci:{version}
    environment:
      ENONIC_CLI_REMOTE_URL:
        from_secret: production-url
      ENONIC_CLI_REMOTE_USER:
        from_secret: production-user
      ENONIC_CLI_REMOTE_PASS:
        from_secret: production-pass
    commands:
      - enonic app install --file build/libs/*.jar

Promoting builds to production

Once you are happy with a build that is running on the staging server, you can promote it to production. That is done by running the command:

drone build promote <your_github_username_or_organization/github_repo> <build_number> production

Using CircleCi

Conditions

In this example our conditions will be:

  • Build app on:

    • All commits

  • Deploy to testing server on:

    • All commits in branches called feature-<SOMETHING>

  • Deploy to staging server on:

    • All commits in master branch

    • All tags

  • Deploy to production server on:

    • All tags with format vX.Y.Z, but only after manual approval. Note that tags have to strictly follow this rule so tag v1.2.3-rc1 will for example not be deployed.

To do this we are going to use CircleCI and their workflow API.

Create contexts

Before we create the workflow, we first need to create 3 contexts in our organization. We will call them:

  • testing-server

  • staging-server

  • production-server

Each of these contexts should have those 3 Environmental variables to configure deployment to their respective servers.

Create workflow

Now we follow the same steps as before for CircleCI, but now our .circleci/config.yml looks a bit different:

version: 2.1

executors:
  xp-executor:
    docker:
      - image: enonic/enonic-ci:{version}
    working_directory: ~/app

jobs:
  build:
    executor: xp-executor
    steps:
      - checkout
      - run:
          name: Setup sandbox
          command: /setup_sandbox.sh
      - run:
          name: Build App
          command: enonic project build
      - persist_to_workspace:
          root: ~/app/build
          paths:
            - libs
  deploy:
    executor: xp-executor
    steps:
      - attach_workspace:
          at: ~/app/build
      - run:
          name: Deploy App
          command: enonic app install --file build/libs/*.jar

workflows:
  xp-ci-cd:
    jobs:
      - build: # Build and test all commits and tags (but not deploy)
          filters:
            tags:
              only: /.*/
            branches:
              only: /.*/
      - deploy: # Deploy feature-<SOMETHING> branches to test server
          name: deploy-testing
          context: testing-server
          requires:
            - build
          filters:
            branches:
              only: /^feature-.*/
      - deploy: # Deploy master branch and all tags to staging server
          name: deploy-staging
          context: staging-server
          requires:
            - build
          filters:
            tags:
              only: /.*/
            branches:
              only: master
      - deploy-prod-approval: # Require approval to deploy to production server
          type: approval
          requires:
            - build
          filters:
            tags:
              only: /^v[0-9]+\.[0-9]+\.[0-9]+$/
            branches:
              ignore: /.*/
      - deploy: # Deploy tags with format vX.Y.Z to production server
          name: deploy-production
          context: production-server
          requires:
            - build
            - deploy-prod-approval
          filters:
            tags:
              only: /^v[0-9]+\.[0-9]+\.[0-9]+$/
            branches:
              ignore: /.*/

Now push this file to your repository and you are done. Now you have a fully functional CI/CD pipeline for your XP app.

To approve production deployments you have to open up your CircleCI project workflow page, pick running workflow that is on hold and approve the deploy-prod-approval job.

FAQ

Build prompts a question to select sandbox

This happens when the entrypoint is overwritten the CI image. To fix this issue run the command /setup_sandbox.sh before enonic project build in your pipeline. This is done in the examples for Drone, CircleCI and Jenkins.


Contents

Contents