AAP 2.6 CI/CD Pipeline Integration: GitOps Workflows with Jenkins, GitLab, and GitHub Actions
By Luca Berton · Published 2024-01-01 · Category: installation
Integrate AAP 2.6 into CI/CD pipelines with Jenkins, GitLab CI, and GitHub Actions. GitOps workflows, webhook triggers, Infrastructure as Code testing.
CI/CD with AAP 2.6
AAP 2.6 fits into CI/CD pipelines in two ways: as a target (pipelines trigger AAP jobs) and as an orchestrator (AAP workflows drive the full deployment). Most enterprises use both — CI builds and tests in the pipeline, then hands off to AAP for infrastructure deployment.
See also: Ansible vs GitHub Actions: Key Differences & When to Use Each (2026)
Integration Patterns
| Pattern | How It Works | Best For | |---------|-------------|----------| | Webhook trigger | Git push → AAP launches job | Simple GitOps | | API trigger | Pipeline calls AAP REST API | Full pipeline control | | Pipeline plugin | Native Jenkins/GitLab plugin | Tight integration | | Hybrid | Pipeline builds → AAP deploys | Separation of concerns |
GitHub Actions
Trigger AAP Job from GitHub Actions
# .github/workflows/deploy.yml
name: Deploy via AAP
on:
push:
branches: [main]
paths:
- 'ansible/**'
- 'inventory/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Trigger AAP Job Template
uses: actions/github-script@v7
env:
AAP_URL: ${{ secrets.AAP_URL }}
AAP_TOKEN: ${{ secrets.AAP_TOKEN }}
with:
script: |
const response = await fetch(
`${process.env.AAP_URL}/api/controller/v2/job_templates/42/launch/`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.AAP_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
extra_vars: {
git_sha: context.sha,
git_ref: context.ref,
deployer: context.actor,
version: `${context.runNumber}`
}
})
}
);
const job = await response.json();
console.log(`AAP Job launched: ${job.id}`);
core.setOutput('job_id', job.id);
- name: Wait for AAP Job
uses: actions/github-script@v7
env:
AAP_URL: ${{ secrets.AAP_URL }}
AAP_TOKEN: ${{ secrets.AAP_TOKEN }}
with:
script: |
const jobId = '${{ steps.trigger.outputs.job_id }}';
let status = 'pending';
while (['pending', 'waiting', 'running'].includes(status)) {
await new Promise(r => setTimeout(r, 10000));
const res = await fetch(
`${process.env.AAP_URL}/api/controller/v2/jobs/${jobId}/`,
{ headers: { 'Authorization': `Bearer ${process.env.AAP_TOKEN}` } }
);
const job = await res.json();
status = job.status;
console.log(`Job ${jobId}: ${status}`);
}
if (status !== 'successful') {
core.setFailed(`AAP job ${jobId} finished with status: ${status}`);
}
AAP Webhook Receiver
Configure the job template to accept GitHub webhooks directly:
- name: Configure webhook-triggered template
ansible.platform.job_template:
controller_host: "{{ gateway_url }}"
controller_username: "{{ controller_user }}"
controller_password: "{{ controller_pass }}"
name: "Deploy on Push"
project: "Infrastructure"
playbook: "deploy.yml"
inventory: "Production"
webhook_service: github
webhook_credential: "GitHub Webhook Secret"
state: present
GitHub webhook URL: https://gateway.example.org/api/controller/v2/job_templates/
See also: Automate Ansible Galaxy Roles with GitHub Actions
GitLab CI
GitLab Pipeline
# .gitlab-ci.yml
stages:
- lint
- test
- deploy
lint:
stage: lint
image: quay.io/ansible/ansible-navigator:latest
script:
- ansible-navigator lint playbooks/ -m stdout
test:
stage: test
image: quay.io/ansible/ansible-navigator:latest
script:
- ansible-navigator run playbooks/test.yml -m stdout --check --diff -i inventory/staging
deploy_staging:
stage: deploy
script:
- |
JOB=$(curl -s -k -X POST \
-H "Authorization: Bearer $AAP_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"extra_vars\": {\"env\": \"staging\", \"version\": \"$CI_COMMIT_SHORT_SHA\"}}" \
"$AAP_URL/api/controller/v2/job_templates/42/launch/")
JOB_ID=$(echo $JOB | jq -r '.id')
echo "Launched AAP job $JOB_ID"
# Poll for completion
while true; do
STATUS=$(curl -s -k \
-H "Authorization: Bearer $AAP_TOKEN" \
"$AAP_URL/api/controller/v2/jobs/$JOB_ID/" | jq -r '.status')
echo "Job $JOB_ID: $STATUS"
[ "$STATUS" = "successful" ] && break
[ "$STATUS" = "failed" ] || [ "$STATUS" = "error" ] || [ "$STATUS" = "canceled" ] && exit 1
sleep 10
done
environment:
name: staging
only:
- main
deploy_production:
stage: deploy
script:
- |
curl -s -k -X POST \
-H "Authorization: Bearer $AAP_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"extra_vars\": {\"env\": \"production\", \"version\": \"$CI_COMMIT_SHORT_SHA\"}}" \
"$AAP_URL/api/controller/v2/workflow_job_templates/10/launch/"
environment:
name: production
when: manual
only:
- main
Jenkins
Jenkins Pipeline
// Jenkinsfile
pipeline {
agent any
environment {
AAP_URL = credentials('aap-url')
AAP_TOKEN = credentials('aap-token')
}
stages {
stage('Lint') {
steps {
sh 'ansible-navigator lint playbooks/ -m stdout --ee false'
}
}
stage('Deploy to Staging') {
steps {
script {
def response = httpRequest(
url: "${AAP_URL}/api/controller/v2/job_templates/42/launch/",
httpMode: 'POST',
customHeaders: [[name: 'Authorization', value: "Bearer ${AAP_TOKEN}"]],
contentType: 'APPLICATION_JSON',
requestBody: """{"extra_vars": {
"env": "staging",
"version": "${env.BUILD_NUMBER}",
"git_sha": "${env.GIT_COMMIT}"
}}"""
)
def job = readJSON text: response.content
env.AAP_JOB_ID = job.id
echo "AAP Job launched: ${job.id}"
}
}
}
stage('Wait for Staging') {
steps {
script {
waitForAAPJob(env.AAP_JOB_ID)
}
}
}
stage('Approve Production') {
steps {
input message: 'Deploy to production?', ok: 'Deploy'
}
}
stage('Deploy to Production') {
steps {
script {
def response = httpRequest(
url: "${AAP_URL}/api/controller/v2/workflow_job_templates/10/launch/",
httpMode: 'POST',
customHeaders: [[name: 'Authorization', value: "Bearer ${AAP_TOKEN}"]],
contentType: 'APPLICATION_JSON',
requestBody: """{"extra_vars": {"env": "production", "version": "${env.BUILD_NUMBER}"}}"""
)
def job = readJSON text: response.content
waitForAAPJob(job.id)
}
}
}
}
}
def waitForAAPJob(jobId) {
timeout(time: 30, unit: 'MINUTES') {
while (true) {
def resp = httpRequest(
url: "${AAP_URL}/api/controller/v2/jobs/${jobId}/",
customHeaders: [[name: 'Authorization', value: "Bearer ${AAP_TOKEN}"]]
)
def job = readJSON text: resp.content
echo "Job ${jobId}: ${job.status}"
if (job.status == 'successful') return
if (job.status in ['failed', 'error', 'canceled']) {
error "AAP job ${jobId} ${job.status}"
}
sleep 10
}
}
}
See also: Automate Ansible Collection Testing with GitHub Actions
GitOps Workflow Pattern
[Developer pushes to Git]
↓
[CI Pipeline: lint + test]
↓ pass
[Pipeline triggers AAP webhook]
↓
[AAP: Project Sync (git pull)]
↓
[AAP: Deploy to Staging]
↓ success
[AAP: Run Integration Tests]
↓ success
[AAP: Approval Gate (workflow)]
↓ approved
[AAP: Deploy to Production (rolling)]
↓
[AAP: Post-deploy Verify + Notify]
AAP Workflow for Full GitOps
- name: Create GitOps deployment workflow
ansible.platform.workflow_job_template:
controller_host: "{{ gateway_url }}"
controller_username: "{{ controller_user }}"
controller_password: "{{ controller_pass }}"
name: "GitOps Deploy"
organization: "Operations"
webhook_service: github
survey_enabled: true
survey_spec:
name: "Deploy Parameters"
description: ""
spec:
- question_name: "Environment"
variable: target_env
type: multiplechoice
choices: ["staging", "production"]
required: true
- question_name: "Version/SHA"
variable: deploy_version
type: text
required: true
state: present
Best Practices
Credential Management
# Store AAP credentials as CI/CD secrets, never in code
# GitHub: Settings → Secrets → AAP_URL, AAP_TOKEN
# GitLab: Settings → CI/CD → Variables
# Jenkins: Credentials → Secret text
# Use short-lived tokens
- name: Create CI/CD service credential
ansible.platform.token:
controller_host: "{{ gateway_url }}"
controller_username: "{{ controller_user }}"
controller_password: "{{ controller_pass }}"
description: "GitLab CI Token"
scope: "write"
state: present
Idempotent Deployments
# Playbooks should be safe to re-run
- name: Deploy application (idempotent)
hosts: webservers
tasks:
- name: Deploy version
ansible.builtin.template:
src: app-config.j2
dest: /etc/myapp/config.yml
notify: restart myapp
- name: Ensure desired version running
ansible.builtin.uri:
url: "http://localhost:8080/health"
return_content: true
register: health
until: health.json.version == deploy_version
retries: 30
delay: 10
FAQ
Should the CI pipeline or AAP own the deployment?
Use CI for building, testing, and artifact creation. Use AAP for infrastructure deployment. AAP provides RBAC, credential injection, audit logging, and rollback that CI tools lack.
How do I handle rollbacks?
Configure an AAP workflow with a rollback path. On job failure, the workflow triggers a rollback job template that deploys the previous known-good version. Store version history in AAP survey variables or external CMDB.
Can I use AAP Collections in CI pipelines?
Yes. Install ansible.controller or ansible.platform in your pipeline and use the collection modules to interact with AAP programmatically — launching jobs, creating inventories, managing credentials.
How do I test Ansible playbooks in CI?
Use Molecule for role-level testing, ansible-navigator run --check for syntax/dry-run validation, and ansible-lint for style checks. Run integration tests against ephemeral infrastructure in staging before production.
What about branch-based deployments?
Use AAP project branches — set the SCM branch dynamically via extra_vars or survey. Feature branches deploy to dev environments, main deploys to staging/production.
Conclusion
AAP 2.6 integrates seamlessly into CI/CD pipelines through webhooks, REST API, and native plugins. Whether using GitHub Actions, GitLab CI, or Jenkins, the pattern is consistent: CI handles build and test, AAP handles deployment with enterprise controls. GitOps workflows combine both for fully automated, auditable infrastructure delivery.
Related Articles
• AAP 2.6 REST API Guide: Automate the Automation Platform • AAP 2.6 Workflow Templates: Advanced Multi-Step Automation Guide • AAP 2.6 Notifications and Webhooks: Slack, Teams, Email, and Custom Integrations • Ansible GitOps Infrastructure as Code with Git Workflows and AAP • AAP 2.6 Credential Management: Vaults, External Secrets, and Machine CredentialsCategory: installation