Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@ ADMIN_PASSWORD=AnsibleForms!123
# REST_ALLOWED_HOSTS=
# REST_DENIED_HOSTS=

# =============================================================================
# RTE (RUNTIME ENVIRONMENT)
# =============================================================================

# Token the app sends with every call to the RTE, at least 16 characters. Both containers
# read it from here, and data/seed.yaml hands it to the app's runner on every start.
RTE_TOKEN=AnsibleForms!RTE!Token!123

# =============================================================================
# CONFIG AND FORMS
# =============================================================================
Expand Down
40 changes: 37 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
name: CI

# Starts the stack exactly as a user does (docker compose up with the committed .env and
# data/) and logs in through the API. A green run means the compose file is valid, the
# pinned images pull and start together, the app reaches its database without waiting for
# it and creates its schema, and the sample data does not stop it from booting. That is
# data/), logs in through the API and runs the demo playbook on the RTE. A green run means
# the compose file is valid, the pinned images pull and start together, the app reaches
# its database without waiting for it and creates its schema, the sample data does not stop
# it from booting, and the seed hands playbooks to the RTE. That is
# what a Dependabot bump of the MySQL image has to prove before it is merged.

on:
Expand Down Expand Up @@ -41,6 +42,39 @@ jobs:
echo "::error::The app did not accept the admin login within 5 minutes."
exit 1

# AnsibleForms 7 runs no playbook itself: the rte container does, registered as the
# default runner by data/seed.yaml. Launch the demo playbook form and wait for its output.
- name: A playbook runs on the RTE
run: |
set -euo pipefail
URL=https://localhost
token="$(curl -fsSk -X POST -u 'admin:AnsibleForms!123' "$URL/api/v2/auth/login" | jq -r .token)"
auth=(-H "Authorization: Bearer $token" -H "Content-Type: application/json")
runner="$(curl -fsSk "${auth[@]}" "$URL/api/v2/runner" | jq -c '.records[] | select(.type == "rte" and .is_default == 1)')"
[ -n "$runner" ] || { echo "::error::data/seed.yaml registered no default rte runner."; exit 1; }
echo "Runner: $runner"
# the RTE starts once the app is healthy, so give it a moment to listen
check=""
for i in $(seq 1 30); do
check="$(curl -fsSk "${auth[@]}" -X POST "$URL/api/v2/runner/$(jq -r .id <<<"$runner")/check" || true)"
[ -n "$check" ] && break
sleep 5
done
[ -n "$check" ] || { echo "::error::The app cannot reach the RTE."; exit 1; }
echo "Test connection: $check"
id="$(curl -fsSk "${auth[@]}" -X POST "$URL/api/v2/job" -d '{"formName":"Demo Form Ansible No input","extravars":{}}' | jq -r .id)"
for i in $(seq 1 60); do
job="$(curl -fsSk "${auth[@]}" "$URL/api/v2/job/$id")"
status="$(jq -r .status <<<"$job")"
case "$status" in running|approve) sleep 3 ;; *) break ;; esac
done
output="$(jq -r .output <<<"$job")"
echo "Job $id ended $status"
[ "$status" = success ] || { echo "::error::The playbook job ended $status."; echo "$output"; exit 1; }
grep -q "Running on RTE" <<<"$output" || { echo "::error::The job did not run on the RTE."; exit 1; }
grep -q "Hi there and welcome to ansible" <<<"$output" || { echo "::error::The playbook output is missing."; exit 1; }
echo "The playbook ran on the RTE."

- name: The app never had to wait for its database
run: |
# the app only starts once MySQL is healthy, so it connects on the first try
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ The setup is a compose file, its settings and the data the containers start with

| Path | Holds |
|---|---|
| `docker-compose.yml` | the AnsibleForms and MySQL containers and their volumes |
| `docker-compose.yml` | the AnsibleForms, RTE and MySQL containers and their volumes |
| `.env` | the settings, with sample values that work out of the box |
| `data/` | what AnsibleForms and MySQL start with: config, sample forms and playbooks, custom functions, ansible and git settings |
| `data/` | what AnsibleForms and MySQL start with: config, sample forms and playbooks, custom functions, ansible and git settings, and the config seed that registers the RTE |

What the containers write at runtime (the database, logs, backups, keys) also lands in
`data/`, and `.gitignore` keeps it out of git.
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,17 @@ Everything under `data/` is mounted into the containers and survives a restart o
- A demo `config.yaml` with categories, roles and constants, and sample forms
- Sample maintenance playbooks and a `dummy.yaml` playbook
- Sample custom JavaScript functions and jq definitions to extend AnsibleForms
- Ansible, Python 3 and a set of Galaxy collections inside the image
- An RTE container that runs the playbooks, with ansible, Python 3 and a set of Galaxy collections
- A config seed, `data/seed.yaml`, that registers the RTE as the default runner

## Running playbooks

AnsibleForms 7 runs no playbook in the app: the `rte` container runs them, from
`ghcr.io/ansibleforms/ansibleforms-rte:7`. The app reaches it on `http://rte:8000` with `RTE_TOKEN` from `.env`.

`data/seed.yaml` registers it as the default runner on every start, so it is read-only under Connections > Runners.
Ansible settings, roles and collections under `data/ansible/` are mounted into the RTE, not the app. When your
playbooks need more, build an image from the app's `Dockerfile.rte` and point the `rte` service at it.

## Kubernetes

Expand Down
12 changes: 12 additions & 0 deletions data/seed.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Config seed, applied by the app at every start (CONFIG_SEED_PATH in docker-compose.yml).
# It registers the rte container as the default runner, so playbook forms run there. Objects
# declared here are read-only in the interface; see https://ansibleforms.com/seed.
version: 1
runners:
items:
- name: rte
type: rte
description: The rte container of docker-compose.yml
uri: http://rte:8000
token: ${RTE_TOKEN}
is_default: true
44 changes: 38 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ services:
- DB_PASSWORD=$MYSQLDB_PASSWORD
- DB_PORT=$MYSQLDB_DOCKER_PORT
- PORT=$WEBAPP_DOCKER_PORT
# adding python path to add our custom persistent lib location
- PYTHONPATH=/usr/lib/python311.zip:/usr/lib/python3.11:/usr/lib/python3.11/lib-dynload:/usr/lib/python3.11/site-packages:/app/dist/persistent/python/lib
# The config seed registers the RTE below as the default runner (data/seed.yaml)
- CONFIG_SEED_PATH=/app/dist/persistent/seed.yaml
# allow interactive shell
stdin_open: true
# allow terminal
Expand All @@ -58,12 +58,44 @@ services:
- ./data:/app/dist/persistent # persistent app data
- ./data/ssh:$HOME_DIR/.ssh # persistent ssh keys
- ./data/git/.gitconfig:$HOME_DIR/.gitconfig # custom git config
- ./data/ansible/ansible.cfg:/etc/ansible/ansible.cfg # custom ansible.cfg
- ./data/ansible/roles:/etc/ansible/roles # custom ansible roles
- ./data/ansible/collections:/etc/ansible/collections # custom ansible collections
# Custom functions: the app reads these from its source folder, not from the persistent
# folder above, so they are mounted over the copies in the image.
- ./data/functions/custom.js:/app/dist/src/functions/custom.js # custom JavaScript functions (fnc.*)
- ./data/functions/jq.custom.definitions:/app/dist/src/functions/jq.custom.definitions # custom jq definitions
# - ./data/assets/images:/app/dist/views/img # 6 custom images (login_background_%theme%.jpg and logo_%theme%.svg | 2 for each theme = light, dark and color)

# AnsibleForms RTE (runtime environment): runs the playbooks. AnsibleForms 7 runs none in
# the app itself, so this is where ansible, the Python libraries and the collections live.
# The app reaches it on http://rte:8000 with RTE_TOKEN; it is not published on the host.
rte:
# Only start once the app is healthy: it creates the schema on the first start, and an
# RTE that finds no schema stops working without exiting, so a restart would not help
depends_on:
mysqldb:
condition: service_healthy
app:
condition: service_healthy
image: ghcr.io/ansibleforms/ansibleforms-rte:7
container_name: af_rte
restart: unless-stopped
# Load extra environment variables from file (the database, ENCRYPTION_SECRET, RTE_TOKEN)
env_file:
- ./.env
# Set environment variables
environment:
- DB_HOST=mysqldb
- DB_USER=$MYSQLDB_USER
- DB_PASSWORD=$MYSQLDB_PASSWORD
- DB_PORT=$MYSQLDB_DOCKER_PORT
- PORT=8000
# Only the app calls it, inside the compose network
- HTTPS=0
# The libraries the ansibleforms_pip_install playbook installs into the persistent folder
- PYTHONPATH=/app/dist/persistent/python/lib
volumes:
# The same folders as the app: playbooks, repositories, uploads and the SSH key
- ./data:/app/dist/persistent # persistent app data
- ./data/ssh:$HOME_DIR/.ssh # persistent ssh keys
- ./data/git/.gitconfig:$HOME_DIR/.gitconfig # custom git config
- ./data/ansible/ansible.cfg:/etc/ansible/ansible.cfg # custom ansible.cfg
- ./data/ansible/roles:/etc/ansible/roles # custom ansible roles
- ./data/ansible/collections:/etc/ansible/collections # custom ansible collections
Loading