Gardience in your pipeline: Jenkins, GitHub Actions, and GitLab CI
Check fleet health before a deploy, run a playbook in check mode on every merge request, and run approved changes from Jenkins, GitHub Actions, or GitLab CI with the gdn command line and the REST API.
Most teams already have a pipeline. Gardience fits into it with two pieces you already have on your server:
gdn, the command line, downloaded from your own console. It runs commands, playbooks, and runbooks, and waits for the result.- The REST API at
/gardience/api/v1, for read-only checks such as "is every web server compliant?"
Both use an API key, so the pipeline needs no SSH keys or WinRM access to your machines. Every change still goes through Gardience's permission checks, approvals, change windows, and audit log. If the key requires approval, the job waits while a second person approves the run.
Set up once#
- In the console, open Settings > API keys and create a key. A read key is enough for checks. An operate key can run things. Give each pipeline its own key, so the audit log shows which pipeline did what.
- Store the key in your CI system's secret store as
GARDIENCE_KEY, and the console address asGARDIENCE_URL. - If the console uses a self-signed certificate, store
config/tls/gardience.crtfrom the server as well, and pointGARDIENCE_CAat it. - Run the job on an agent or runner that can reach the console. Gardience runs inside your network, so this is usually a self-hosted runner.
gdn needs only Python 3. It reads GARDIENCE_URL, GARDIENCE_KEY, and GARDIENCE_CA from the environment, so no login step is needed.
Exit codes are what a pipeline needs: 0 success, 1 the request could not be made, 2 the run finished but did not succeed.
GitHub Actions#
Use the gdn action:
name: deploy
on:
pull_request:
push:
branches: [main]
jobs:
playbook:
runs-on: [self-hosted, linux]
steps:
- uses: actions/checkout@v4
- uses: gardience/gdn-action@v1
with:
server: ${{ vars.GARDIENCE_URL }}
api-key: ${{ secrets.GARDIENCE_KEY }}
ca-cert: ${{ vars.GARDIENCE_CA }}
args: playbook site.yml -l web ${{ github.event_name == 'pull_request' && '--check' || '' }} --reason "${{ github.repository }}@${{ github.sha }}"Pull requests get a dry run. A merge to main runs it for real.
Jenkins#
pipeline {
agent { label 'linux' }
environment {
GARDIENCE_URL = 'https://gardience.example.com'
GARDIENCE_KEY = credentials('gardience-key')
GARDIENCE_CA = credentials('gardience-ca') // a "secret file" credential
}
stages {
stage('Get gdn') {
steps {
sh 'curl -fsSL --cacert "$GARDIENCE_CA" -o gdn "$GARDIENCE_URL/gardience/cli/gdn" && chmod +x gdn'
}
}
stage('Check') {
steps { sh './gdn playbook site.yml -l web --check --reason "$JOB_NAME #$BUILD_NUMBER"' }
}
stage('Apply') {
when { branch 'main' }
steps { sh './gdn playbook site.yml -l web --reason "$JOB_NAME #$BUILD_NUMBER"' }
}
}
}GitLab CI#
stages: [check, apply]
.gdn:
image: python:3.12-slim
tags: [inside-network]
before_script:
- apt-get update -qq && apt-get install -y -qq curl >/dev/null
- curl -fsSL --cacert "$GARDIENCE_CA" -o gdn "$GARDIENCE_URL/gardience/cli/gdn" && chmod +x gdn
check:
extends: .gdn
stage: check
script:
- ./gdn playbook site.yml -l web --check --reason "$CI_PROJECT_PATH!$CI_MERGE_REQUEST_IID"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
apply:
extends: .gdn
stage: apply
script:
- ./gdn playbook site.yml -l web --reason "$CI_PROJECT_PATH@$CI_COMMIT_SHORT_SHA"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHSet GARDIENCE_CA as a variable of type File so GitLab writes it to disk and passes the path.
A compliance gate#
To stop a deploy when machines are out of compliance or have stopped reporting, read /api/v1/fleet with a read key:
bad=$(curl -fsS --cacert "$GARDIENCE_CA" -H "Authorization: Bearer $GARDIENCE_KEY" \
"$GARDIENCE_URL/gardience/api/v1/fleet" \
| jq -r '.nodes[] | select(.node | startswith("web")) | select(.compliant == false or .stale) | .node')
if [ -n "$bad" ]; then echo "Not compliant: $bad"; exit 1; fiEach node in the answer also carries pending_updates, reboot_pending, baseline_score, and failing_controls, so you can gate on whichever matters to you. See the REST API reference.
Other things a pipeline can do#
| Goal | Command |
|---|---|
| Run a command on a group | gdn run web 'systemctl is-active nginx' |
| Run a runbook | gdn runbook restart-service -e nodegroup=web -e service=nginx |
| Start without waiting | add --no-wait, then gdn status RUN_ID later |
| Fleet status | gdn nodes web |
To manage Gardience's own setup as code (runbooks, node groups, schedules, change windows), use the Terraform provider in the same pipeline.
If you want a Gardience plugin for your CI system instead of these few lines, tell us which one.
Try Gardience on your own machines
One supported console for Windows and Linux on open-source Salt: patching, a scored STIG/CIS-aligned baseline, drift repair, event-driven automation, two-person approval and a tamper-evident audit log. Flat yearly pricing, not per node.
Free 90-day trial (25 nodes)See it work Compare