Skip to content

lfkdev/ansible-link

Repository files navigation

Ansible Link Logo

ANSIBLE LINK

RESTful API for executing Ansible playbooks remotely

Python Linux Flask-RESTx Ansible Runner

Features

  • Playbook Execution Asynchronous playbook executions with real-time status updates.
  • Playbook History Keep track of playbook executions and their status.
  • API Documentation Swagger UI documentation for easy exploration of the API endpoints.
  • Metrics Exposes Prometheus metrics for playbook runs, durations, and active jobs.
  • Webhook Notifications Send notifications to Slack, Discord, or custom webhooks for job events.

NOTE Project is usable but still in early development.

Motivation

Searched for a way to run our playbooks automated without the need of AWX or other big projects while still being more stable and less error-prone than custom bash scripts. So I made Ansible-Link. This projects aims to be a KISS way to run ansible jobs remotely. Essentially a RESTful API sitting on top of ansible-runner.

Prerequisites

  • Ansible CLI installed
  • Your playbooks and inventory files

Installation

The fastest way to set up Ansible-Link is by using the provided install.sh script:

Download and run the install script:

wget https://raw.githubusercontent.com/lfkdev/ansible-link/main/install.sh
sudo bash install.sh

This script will:

  • Check for necessary dependencies and install them if missing.
  • Download and install Ansible-Link.
  • Set up a Python virtual environment.
  • Configure a systemd service for Ansible-Link.

After the installation, you can start using Ansible-Link immediately. You probably need to change some config values for your ansible environment /opt/ansible-link/config.yml

playbook_dir: '/etc/ansible/'
inventory_file: '/etc/ansible/environments/hosts'
...

To add more workers or change the user, modify /etc/systemd/system/ansible-link.service

⚠️ Note: Currently, only Ubuntu versions 16.04 and higher, or Debian versions 9 and higher are officially supported. Other operating systems might also work but have not been tested. You can clone the repository and perform a manual installation if you are using a different OS.

API Documentation

The API documentation is available via the Swagger UI.

Ansible Link Docs

API Endpoints

  • POST /ansible/playbook: Execute a playbook
  • GET /ansible/jobs: List all jobs
  • GET /ansible/job/<job_id>: Get job status
  • GET /ansible/job/<job_id>/output: Get job output
  • GET /health: Health check endpoint

Configuration

The API configuration is stored in the config.yml file. If you move your config to a different location you can use ANSIBLE_LINK_CONFIG_PATH

$ export ANSIBLE_LINK_CONFIG_PATH='/etc/ansible-link/config.yml'

You can customize the following settings:

# webhook
# webhook:
#   url: "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
#   type: "slack" # "slack", "discord" or "generic" supported
#   timeout: 5  # optional, default 5 seconds

# flask
host: '127.0.0.1'
port: 5001
debug: false

# ansible-runner
suppress_ansible_output: false
omit_event_data: false
only_failed_event_data: false

# promtetheus
metrics_port: 9090

# general
playbook_dir: '/etc/ansible/'
inventory_file: '/etc/ansible/environments/hosts'
job_storage_dir: '/var/lib/ansible-link/job-storage'
log_level: 'INFO'

# ansible-link
playbook_whitelist: []
# playbook_whitelist:
#   - monitoring.yml
#   - mariadb.yml

The whitelist supports full regex, you could go wild:

playbook_whitelist:
  # Allow all playbooks in the 'test' directory
  - ^test/.*\.ya?ml$

  # Allow playbooks starting with 'prod_' or 'dev_'
  - ^(prod|dev)_.*\.ya?ml$

  # Allow specific playbooks
  - ^(backup|restore|maintenance)\.ya?ml$

Leave empty to allow all playbooks. This is for the backend, you could also use the limit arg from ansible-runner in the request directly.

Prod environment

You can use the install script install.sh to get a production-ready environment for Ansible-Link.

The install script will:

  • Set up a Python VENV
  • Configure a systemd service to manage Ansible-Link
  • Utilize Gunicorn as the WSGI server
  • Start Ansible-Link to liste only on localhost

You can use a webserver like Caddy to add Basic Authentication and TLS to your Ansible-Link setup.

Also, if you're using a webserver, you can change Gunicorn to use sockets instead. For example:

ExecStart=$VENV_DIR/bin/gunicorn --workers 1 --bind unix:$INSTALL_DIR/ansible_link.sock -m 007 wsgi:application

unitD example

[Unit]
Description=Ansible Link Service
After=network.target

[Service]
ExecStart=$VENV_DIR/bin/gunicorn --workers 1 --bind 127.0.0.1:$ANSIBLE_LINK_PORT wsgi:application
WorkingDirectory=$INSTALL_DIR
Restart=always
User=root

[Install]
WantedBy=multi-user.target

Example setup:

├── etc/
│   └── ansible/
│       ├── playbooks/
│       │   └── some_playbooks.yml
│       └── inventory/
│           ├── production
│           └── staging
│
├── opt/
│   └── ansible-link/
│       ├── ansible-link.py
│       └── config.yml
│
└── var/
    └── lib/
        └── ansible-link/
            └── job-storage/
                └── playbook_name_20230624_130000_job_id.json

Webhook Configuration

Ansible-Link supports sending webhook notifications for job events. You can configure webhooks for Slack, Discord, or a generic endpoint. Add the following to your config.yml:

webhook:
  url: "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
  type: "slack"  # Options: slack, discord, generic
  timeout: 5  # Optional, defaults to 5 seconds

or leave it commented out to disable webhooks

  • url The webhook URL for your chosen platform.
  • type The type of webhook (slack, discord, or generic).
  • timeout The timeout for webhook requests in seconds (optional, default is 5 seconds).

Only Slack and Discord are supported for now, you can also use generic which will send the base JSON payload:

{
    "event_type": event_type,
    "job_id": job_data['job_id'],
    "playbook": job_data['playbook'],
    "status": job_data['status'],
    "timestamp": datetime.now().isoformat()
}

The following notifcations are sent:

  • Job Started
  • Job Completed (success or failure)
  • Job Error

View webhook.py for more info.

Usage

Below are examples demonstrating how to use ansible-link API compared to Ansible CLI.


$ ansible-playbook site.yml
{
  "playbook": "site.yml"
}
curl -X POST http://your-ansible-link-server/ansible/playbook \
  -H "Content-Type: application/json" \
  -d '{"playbook": "site.yml"}'

$ ansible-playbook deploy.yml -e version=1.5.0 environment=staging
{
  "playbook": "deploy.yml",
  "vars": {
    "version": "1.5.0",
    "environment": "staging"
  }
}

$ ansible-playbook site.yml --tags "update,packages" -vv
{
  "playbook": "site.yml",
  "tags": "update,packages",
  "verbosity": 2
}

$ ansible-playbook restore.yml --limit "databases" --forks 3
{
  "playbook": "restore.yml",
  "limit": "databases",
  "forks": 3
}

$ ansible-playbook site.yml -i custom_inventory.ini -e '{"key1": "value1", "key2": "value2"}' --tags "provision,configure" --skip-tags "cleanup" --limit "webservers:&staged" --forks 10 -vvv
{
  "playbook": "site.yml",
  "inventory": "custom_inventory.ini",
  "vars": {
    "key1": "value1",
    "key2": "value2"
  },
  "tags": "provision,configure",
  "skip_tags": "cleanup",
  "limit": "webservers:&staged",
  "forks": 10,
  "verbosity": 3
}

$ ansible-playbook site.yml -i custom_inventory.ini -e environment=production --diff --check
{
  "playbook": "site.yml",
  "inventory": "custom_inventory.ini",
  "vars": {
    "environment": "production"
  },
  "cmdline": "--diff --check"
}

Ansible-Link supports the following native parameters:

  • playbook: The name of the playbook to run (required)
  • inventory: Path to the inventory file
  • vars (extravars): A dictionary of additional variables to pass to the playbook
  • limit: A host pattern to further constrain the list of hosts
  • verbosity: Control the output level of ansible-playbook
  • forks: Specify number of parallel processes to use
  • tags: Only run plays and tasks tagged with these values
  • skip_tags: Only run plays and tasks whose tags do not match these values
  • cmdline: Any additional command line options to pass to ansible-playbook

Which means you can always use cmdline if your arg is not natively supported, like:

{
  "playbook": "site.yml",
  "cmdline": "--diff --check -e environment=production -i /etc/ansible/test/custom_inventory.ini"
}

Output

Ansible-Link will save each job as .json with the following info (from ansible-runner):

{
  "status": "successfull",
  "playbook": "<playbook_name>",
  "inventory": null,
  "vars": {
    "customer": "emind"
  },
 "start_time": "2024-06-24T15:32:35.380662",
  "stdout": "<ANSIBLE PLAYBOOK OUTPUT>",
  "stderr": "",
  "stats": {
    "skipped": {
      "<playbook_name>": 8
    },
    "ok": {
      "<playbook_name>": 28
    },
    "dark": {},
    "failures": {
      "<playbook_name>": 1
    },
    "ignored": {},
    "rescued": {},
    "processed": {
      "<playbook_name>": 1
    },
    "changed": {}
  }
}

essentially showing everything ansible-playbook would display.

Note After submitting a request to the API, you will receive a job ID. You can use this job ID to check the status and retrieve the output of the playbook run using the /ansible/job/<job_id> and /ansible/job/<job_id>/output endpoints respectively.

Metrics

Ansible-Link exposes the following metrics:

PLAYBOOK_RUNS = Counter('ansible_link_playbook_runs_total', 'Total number of playbook runs', ['playbook', 'status'])
PLAYBOOK_DURATION = Histogram('ansible_link_playbook_duration_seconds', 'Duration of playbook runs in seconds', ['playbook'])
ACTIVE_JOBS = Gauge('ansible_link_active_jobs', 'Number of currently active jobs')

The metrics can be used to set alerts, track the history of jobs, monitor performance and so on

Security Considerations

  • Use TLS in production
  • Add basic auth

Contributing

Contributions are always welcome - if you find any issues or have suggestions for improvements, please open an issue or submit a pull request.

License

This project is licensed under the MPL2 License. See the LICENSE file for more information.