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 defineTENANT_BASE_URL
,TENANT_CLIENT_ID
, andTENANT_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 thebuild
andtest
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 thebuild
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 ourNODE_VERSION
variable.script
: Contains the commands to execute.npm ci
installs dependencies cleanly based onpackage-lock.json
, andnpm run pack-zip
(assuming you have this script defined in yourpackage.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, thedist/
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 thedev
,test
, ormain
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 thebuild
job.script
: Installs dependencies (npm ci
) and runs the test suite (npm run test
- assuming this script is defined inpackage.json
and configured to output results).artifacts
: Saves thecoverage/
directory. It also specifiesreports: junit: junit.xml
, allowing GitLab to parse the test results fromjunit.xml
(if your test runner generates it) and display them in the GitLab UI.dependencies: - build
: This explicitly states that thetest
job depends on the artifacts produced by thebuild
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 frombuild
; it primarily controls job execution order when usingneeds
. Sincetest
followsbuild
instages
, 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 thedeploy
stage.image: ubuntu:latest
: Uses a basic Ubuntu image for the deployment tasks.script
: This is the core deployment logic:- Updates package lists and installs necessary tools (
wget
for downloading,jq
for parsing JSON). - Downloads the specified version of the SailPoint CLI Debian package.
- Installs the SailPoint CLI using
dpkg
. - Reads the connector
version
andname
frompackage.json
usingjq
. - Checks if a connector with the specified
CONNECTOR_ALIAS
already exists in the target tenant usingsail conn list
andgrep
. The|| true
prevents the pipeline from failing ifgrep
finds no matches. - 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. - 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 frompackage.json
.
- Updates package lists and installs necessary tools (
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 thedev
branch.deploy-test
runs only on successful commits to thetest
branch.deploy-prod
runs only on successful commits to themain
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 thebuild
andtest
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