Automating SailPoint SaaS Connector Deployments with GitLab CI/CD

Automating SailPoint SaaS Connector Deployments with GitLab CI/CD

Continuous Integration and Continuous Deployment (CI/CD) practices are essential for modern software development, and SailPoint SaaS connector development is no exception. Automating the build, test, and deployment process saves time, reduces errors, and ensures consistency. GitLab CI/CD is a powerful tool that allows you to define and run your pipelines directly from your GitLab repository.

This post will guide you through setting up a GitLab CI/CD pipeline for your SailPoint SaaS connectors using a practical example configuration file (.gitlab-ci.yml).

I will note - I do not consider myself a GitLab or CI/CD expert by any means - if you have any suggestions or ideas for improvement, please share!

Download the full file here: gitlab-ci.yml (4.1 KB)

Prerequisites

Before you begin, ensure you have the following set up:

  • A GitLab Runner: This is the application that runs your CI/CD jobs. You can use shared runners provided by GitLab or set up your own specific runner.
  • A GitLab Repository: Your SailPoint SaaS connector code should be hosted in a GitLab repository.
  • SailPoint Tenant Credentials: You’ll need API credentials (Client ID and Client Secret) for your SailPoint tenant with permissions to manage connectors. These will be stored securely as GitLab CI/CD variables.

What is GitLab CI/CD?

GitLab CI/CD is a part of GitLab used to automate the stages of your software development lifecycle. You define a pipeline in a YAML file named .gitlab-ci.yml located in the root of your repository. This file tells the GitLab Runner (the agent that executes your jobs) what tasks to perform. These tasks are grouped into stages, and the stages run in a defined order. Common stages include building the code, running tests, and deploying the application.

The .gitlab-ci.yml File Explained

Let’s break down an example .gitlab-ci.yml file designed for a SailPoint SaaS connector. This pipeline automates the process of building the connector package, running tests, and deploying it to different SailPoint environments (dev, test, production) based on the Git branch.

# .gitlab-ci.yml (Excerpt - Full file provided previously)
stages:
  - build
  - test
  - deploy

variables:
  SAIL_BASE_URL: ${TENANT_BASE_URL}
  SAIL_CLIENT_ID: ${TENANT_CLIENT_ID}
  SAIL_CLIENT_SECRET: ${TENANT_CLIENT_SECRET}
  CONNECTOR_ALIAS: "${TENANT_CONN_NAME}"
  CLI_VERSION: "2.1.10"
  NODE_VERSION: "18"

# ... rest of the file

stages

stages:
  - build
  - test
  - deploy

This section defines the sequence of stages in our pipeline. Jobs assigned to the build stage will run first. Once all build jobs succeed, jobs in the test stage will run. Finally, if test jobs are successful, the deploy stage jobs will execute. This ensures a logical flow where code is built before it’s tested, and tested before it’s deployed.

variables

variables:
  SAIL_BASE_URL: ${TENANT_BASE_URL}
  SAIL_CLIENT_ID: ${TENANT_CLIENT_ID}
  SAIL_CLIENT_SECRET: ${TENANT_CLIENT_SECRET}
  CONNECTOR_ALIAS: "${TENANT_CONN_NAME}"
  CLI_VERSION: "2.1.10"
  NODE_VERSION: "18"

Here, we define variables that can be used throughout the pipeline jobs.

  • SAIL_BASE_URL, SAIL_CLIENT_ID, SAIL_CLIENT_SECRET: These are crucial for authenticating with your SailPoint tenant using the SailPoint CLI. Crucially, these values use GitLab’s variable syntax (${VARIABLE_NAME}). You should define TENANT_BASE_URL, TENANT_CLIENT_ID, and TENANT_CLIENT_SECRET as protected CI/CD variables within your GitLab project settings (Settings → CI/CD → Variables) rather than hardcoding sensitive credentials here.
  • CONNECTOR_ALIAS: This specifies the alias for your connector within SailPoint. Similar to the credentials, TENANT_CONN_NAME should be defined in GitLab’s CI/CD variables, potentially varying per environment if needed.
  • CLI_VERSION: Specifies the version of the SailPoint CLI to use during deployment.
  • NODE_VERSION: Defines the Node.js version for the Docker images used in the build and test stages.

build Job

build:
  stage: build
  image: node:${NODE_VERSION}
  script:
    - npm ci
    - npm run pack-zip
  artifacts:
    paths:
      - dist/
    expire_in: 1 week
  rules:
    - if: $CI_COMMIT_BRANCH == "dev"
    - if: $CI_COMMIT_BRANCH == "test"
    - if: $CI_COMMIT_BRANCH == "main"
  timeout: 10m
  retry:
    max: 2
    when:
      - runner_system_failure
      - stuck_or_timeout_failure

This job defines the tasks for the build stage.

  • stage: build: Assigns this job to the build stage.
  • image: node:${NODE_VERSION}: Specifies that this job should run in a Docker container using the official Node.js image, tagged with the version defined in our NODE_VERSION variable.
  • script: Contains the commands to execute. npm ci installs dependencies cleanly based on package-lock.json, and npm run pack-zip (assuming you have this script defined in your package.json) packages your connector code into a zip file suitable for deployment.
  • artifacts: Defines files or directories to save after the job completes successfully. Here, the dist/ directory (containing the packaged connector zip) is saved as an artifact. These artifacts can be downloaded or used by jobs in later stages. expire_in: 1 week sets how long the artifacts are kept.
  • rules: Control when the job runs. This job is configured to run only for commits on the dev, test, or main branches.
  • timeout: Sets a maximum execution time for the job.
  • retry: Configures automatic retries (up to 2 times) if the job fails due to specific runner issues.

test Job

test:
  stage: test
  image: node:${NODE_VERSION}
  script:
    - npm ci
    - npm run test
  artifacts:
    paths:
      - coverage/
    reports:
      junit:
        - junit.xml
    expire_in: 1 week
  dependencies:
    - build
  rules:
    - if: $CI_COMMIT_BRANCH == "dev"
    - if: $CI_COMMIT_BRANCH == "test"
    - if: $CI_COMMIT_BRANCH == "main"
  timeout: 10m
  retry:
    max: 2
    when:
      - runner_system_failure
      - stuck_or_timeout_failure

This job handles the test stage.

  • stage: test, image, rules, timeout, retry: Similar configuration to the build job.
  • script: Installs dependencies (npm ci) and runs the test suite (npm run test - assuming this script is defined in package.json and configured to output results).
  • artifacts: Saves the coverage/ directory. It also specifies reports: junit: junit.xml, allowing GitLab to parse the test results from junit.xml (if your test runner generates it) and display them in the GitLab UI.
  • dependencies: - build: This explicitly states that the test job depends on the artifacts produced by the build job. While not strictly necessary here (as artifacts are passed by default between stages), it makes the dependency clear. Note: This configuration doesn’t automatically download artifacts from build; it primarily controls job execution order when using needs. Since test follows build in stages, the artifacts are available.

Note: if you don’t have tests written you’ll want to modify your package.json file to have npm test run jest --coverage --passWithNoTests.

.deploy_template (YAML Anchor)

.deploy_template: &deploy_template
  stage: deploy
  image: ubuntu:latest
  script:
    - apt-get update && apt-get install -y wget jq
    - wget "https://github.com/sailpoint-oss/sailpoint-cli/releases/download/${CLI_VERSION}/sail_${CLI_VERSION}_linux_amd64.deb"
    - dpkg -i sail_${CLI_VERSION}_linux_amd64.deb
    - PACKAGE_VERSION=$(jq -r .version package.json)
    - CONNECTOR_NAME=$(jq -r .name package.json)
    # Simplified check for connector existence
    - |
      CONNECTOR_EXISTS=$(sail conn list | grep -c "${CONNECTOR_ALIAS}" || true)
      echo "Found ${CONNECTOR_EXISTS} matches for connector '${CONNECTOR_ALIAS}'"
      if [ "${CONNECTOR_EXISTS}" -eq 0 ]; then
        echo "Connector $CONNECTOR_ALIAS not found. Creating it..."
        sail conn create "$CONNECTOR_ALIAS" || echo "Failed to create connector, continuing anyway..."
      else
        echo "Connector $CONNECTOR_ALIAS already exists."
      fi
    - echo "Uploading connector..."
    - sail conn upload -c "$CONNECTOR_ALIAS" -f ./dist/$CONNECTOR_NAME-$PACKAGE_VERSION.zip./dist/$CONNECTOR_NAME-$PACKAGE_VERSION.zip
  timeout: 15m
  retry:
    max: 2
    when:
      - runner_system_failure
      - stuck_or_timeout_failure

This section uses a YAML feature called an anchor (&deploy_template). It defines a template named deploy_template (note the leading dot ., which prevents it from being run as a standalone job). This template contains the common configuration and script steps for deploying the connector.

  • stage: deploy: Assigns jobs using this template to the deploy stage.
  • image: ubuntu:latest: Uses a basic Ubuntu image for the deployment tasks.
  • script: This is the core deployment logic:
    1. Updates package lists and installs necessary tools (wget for downloading, jq for parsing JSON).
    2. Downloads the specified version of the SailPoint CLI Debian package.
    3. Installs the SailPoint CLI using dpkg.
    4. Reads the connector version and name from package.json using jq.
    5. Checks if a connector with the specified CONNECTOR_ALIAS already exists in the target tenant using sail conn list and grep. The || true prevents the pipeline from failing if grep finds no matches.
    6. Conditionally creates the connector (sail conn create) if it doesn’t exist. It includes || echo "Failed..." to prevent the pipeline from stopping if creation fails (e.g., due to permissions), allowing the upload step to proceed, which might be desired if the connector exists but wasn’t listed correctly.
    7. Uploads the connector package (sail conn upload) using the alias (-c) and the path to the zipped artifact (-f). The artifact path is constructed using the connector name and version read from package.json.
  • timeout, retry: Standard job control settings.

deploy-dev, deploy-test, deploy-prod Jobs

deploy-dev:
  <<: *deploy_template # Inherits from the template
  environment:
    name: dev
  rules:
    - if: $CI_COMMIT_BRANCH == "dev"
      when: on_success # Run only if previous stages succeed
  needs:
    - build
    - test

deploy-test:
  <<: *deploy_template
  environment:
    name: test
  rules:
    - if: $CI_COMMIT_BRANCH == "test"
      when: on_success
  needs:
    - build
    - test

deploy-prod:
  <<: *deploy_template
  environment:
    name: production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: on_success
  needs:
    - build
    - test

These are the actual deployment jobs, one for each environment (development, testing, production).

  • <<: *deploy_template: This uses the YAML merge key (<<) and alias (*deploy_template) to inherit all the configuration from the .deploy_template. This avoids repeating the script and other settings.
  • environment: name: <env_name>: Associates the job with a GitLab Environment (e.g., dev, test, production). This helps track deployments in the GitLab UI.
  • rules: Defines the specific conditions for each deployment job.
    • deploy-dev runs only on successful commits to the dev branch.
    • deploy-test runs only on successful commits to the test branch.
    • deploy-prod runs only on successful commits to the main branch.
    • when: on_success ensures these jobs only run if all jobs in the preceding stages (build, test) completed successfully.
  • needs: - build - test: Specifies that these deploy jobs require both the build and test jobs to complete successfully before they can start. This allows for potential parallel execution if stages were structured differently, but here it primarily reinforces the dependency on the successful completion of prior stages and ensures artifacts are available.

Conclusion

By implementing a GitLab CI/CD pipeline like the one described, you can significantly streamline your SailPoint SaaS connector development workflow. This automation ensures that every change pushed to your key branches is automatically built, tested, and deployed, leading to faster feedback cycles, improved quality, and more reliable deployments. Remember to configure your sensitive tenant credentials and connector aliases securely using GitLab’s CI/CD variables. Happy automating!

Additional Resources

Get started with GitLab CI/CD | GitLab Docs
Runners | GitLab Docs

6 Likes

Great work @adunker !

1 Like

Thanks a lot Alex Dunker..

1 Like