├── samples ├── env │ └── .keep ├── gluster │ └── dummy ├── inventory │ └── .keep ├── project │ ├── INSERT_PLAYBOOKS_HERE │ ├── runnertest.yml │ └── probe-disks.yml └── ceph │ ├── README.md │ └── new_osd.json ├── runner_service ├── services │ ├── __init__.py │ ├── utils.py │ ├── groups.py │ └── hosts.py ├── static │ ├── Ansible_logo.png │ ├── utils.js │ └── main.css ├── controllers │ ├── base.py │ ├── __init__.py │ ├── utils.py │ ├── api.py │ ├── metrics.py │ ├── groups.py │ └── jobs.py ├── __init__.py ├── cache.py ├── app.py ├── templates │ └── api.html └── metrics.py ├── tests ├── data │ ├── artifacts │ │ └── 53b955f2-b79a-11e8-8be9-c85b7671906d │ │ │ ├── rc │ │ │ ├── status │ │ │ └── job_events │ │ │ ├── 2-992a67f3-deb5-4b6d-9ebd-fd680ad79c0e.json │ │ │ ├── 3-c85b7671-906d-f1b8-c363-000000000008.json │ │ │ ├── 40-27df7f53-1cac-498b-a716-754305472172.json │ │ │ ├── 8-c85b7671-906d-f1b8-c363-00000000000a.json │ │ │ ├── 48-c85b7671-906d-f1b8-c363-00000000000e.json │ │ │ ├── 4-c85b7671-906d-f1b8-c363-000000000012.json │ │ │ ├── 9-4be01772-502d-472e-9e1a-2b12ac9fdcb8.json │ │ │ ├── 10-df2fd54b-e0a8-4e43-a48c-ff2227172770.json │ │ │ ├── 11-b1080bd8-2d64-43ea-9d0b-05a21f416fbd.json │ │ │ ├── 16-c85b7671-906d-f1b8-c363-00000000000c.json │ │ │ ├── 32-c85b7671-906d-f1b8-c363-00000000000d.json │ │ │ ├── 12-c85b7671-906d-f1b8-c363-00000000000b.json │ │ │ ├── 50-906885d5-a67e-413d-914b-60b41a924192.json │ │ │ ├── 14-de2fde11-9d21-4b72-a6c5-30fdbc454831.json │ │ │ ├── 15-e736786d-7cfd-4f61-8a17-5bbdf989b89e.json │ │ │ ├── 13-fc55d76c-7ffa-46a4-b1e3-919f38eeee51.json │ │ │ ├── 28-51e73fac-1bb0-42cf-b855-c9576e7589be.json │ │ │ ├── 30-bd8d3f5b-4401-40fa-a3d1-2a5eaa9c557b.json │ │ │ ├── 49-e084d030-cd3d-4c76-a4d3-03d032c4dc8f.json │ │ │ ├── 18-f087fd62-88cd-4160-b111-d1433f31aae5.json │ │ │ ├── 19-73a53195-a0e1-4818-addd-780257cb812e.json │ │ │ ├── 21-c1c24f3a-f30b-4c25-bb3d-c4207d71ba61.json │ │ │ ├── 23-022145c9-5950-49c0-bf93-f40c8f7e5cf7.json │ │ │ ├── 24-cb720873-f855-456e-84e6-d261840939b9.json │ │ │ ├── 26-15cc1b59-5937-46ae-a4be-fff835c85695.json │ │ │ ├── 17-0a7f687d-4512-4086-bedb-7ee1c78a74dc.json │ │ │ ├── 20-6d212245-ef3c-48e7-8a2f-3e35513657fc.json │ │ │ ├── 22-af8c13fe-c867-44cf-bd15-b4d5524fbc5c.json │ │ │ ├── 27-76765c7c-4dae-4dca-9780-186a05c8a939.json │ │ │ ├── 25-281e4850-0212-4685-9f1e-d7bbb703f283.json │ │ │ ├── 41-097855c7-fc5e-41e4-8968-4e2107d3fc5f.json │ │ │ ├── 46-6845c18a-780c-46b7-820a-6bab972c67e0.json │ │ │ ├── 33-c67654a2-01c8-46dc-ac30-9f24a4c3a536.json │ │ │ ├── 34-46848398-717c-480c-b7ec-904304ee200e.json │ │ │ ├── 35-4eedda69-f0a0-4a00-929d-897a1ce4d29d.json │ │ │ ├── 36-fe767c67-c117-4a33-98db-5d6e22365400.json │ │ │ ├── 37-7ac43b8d-96c7-4e49-ad21-f2399db715c4.json │ │ │ ├── 38-804e4887-26f2-4a2d-8828-bf27a2f161df.json │ │ │ ├── 39-d806fd8d-ead2-4731-a820-1a360b3a2cd2.json │ │ │ ├── 43-f2700669-4ddc-4189-b610-227af206939a.json │ │ │ ├── 44-ce7a38e6-e02c-4236-93e1-84629a9bfe06.json │ │ │ ├── 45-bcd384f7-a136-4163-994a-8fd5e3895920.json │ │ │ ├── 29-ffc0ff84-5870-4631-9b71-07550e0bd2e1.json │ │ │ └── 31-59691cae-5a41-48dc-b5bb-90f5e6fba53d.json │ └── project │ │ └── testplaybook.yml ├── TODO ├── test_api_generic.py ├── common.py └── test_api_events.py ├── .travis-pre.sh ├── MANIFEST.in ├── requirements.txt ├── misc ├── dashboards │ ├── screenshot.png │ └── README.md ├── packaging │ ├── logrotate │ │ └── ansible-runner-service │ ├── apache │ │ ├── README.md │ │ ├── wsgi.patch │ │ ├── ovirt_log.patch │ │ └── ansible-runner-service.spec │ ├── README │ ├── ars_source.spec │ ├── ansible-runner-service.spec │ └── nginx │ │ └── ansible-runner-service-nginx.spec ├── nginx │ ├── uwsgi.ini │ ├── supervisord.conf │ ├── ars_site_nginx.conf │ ├── nginx.conf │ ├── generate_client_cert.sh │ ├── certificates_data.custom │ └── generate_certs.sh ├── systemd │ └── ansible-runner-service.service └── docker │ ├── ansible-runner-service.service │ ├── Dockerfile │ └── README.md ├── screenshots ├── runner-service-api.gif └── ansible-runner-service-API.png ├── packaging └── gunicorn │ ├── ansible-runner-service │ ├── ansible-runner-service.service │ ├── wsgi.patch │ ├── ovirt_log.patch │ └── ansible-runner-service.spec ├── .gitignore ├── ISSUES ├── tox.ini ├── config.yaml ├── wsgi.py ├── logging.yaml ├── .travis.yml ├── TODO ├── Dockerfile.python27 ├── setup.py ├── Dockerfile └── Dockerfile.ubi8 /samples/env/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/gluster/dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/inventory/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /runner_service/services/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/project/INSERT_PLAYBOOKS_HERE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/rc: -------------------------------------------------------------------------------- 1 | 0 -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/status: -------------------------------------------------------------------------------- 1 | successful -------------------------------------------------------------------------------- /.travis-pre.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -u 5 | 6 | pip install tox; 7 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include runner_service/templates * 2 | recursive-include runner_service/static * 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ansible_runner==1.3.0 2 | cryptography 3 | PyYAML 4 | pyOpenSSL 5 | flask 6 | flask-restful 7 | flake8 8 | -------------------------------------------------------------------------------- /misc/dashboards/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-runner-service/HEAD/misc/dashboards/screenshot.png -------------------------------------------------------------------------------- /screenshots/runner-service-api.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-runner-service/HEAD/screenshots/runner-service-api.gif -------------------------------------------------------------------------------- /runner_service/static/Ansible_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-runner-service/HEAD/runner_service/static/Ansible_logo.png -------------------------------------------------------------------------------- /screenshots/ansible-runner-service-API.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-runner-service/HEAD/screenshots/ansible-runner-service-API.png -------------------------------------------------------------------------------- /packaging/gunicorn/ansible-runner-service: -------------------------------------------------------------------------------- 1 | /var/log/ovirt-engine/ansible-runner-service.log { 2 | monthly 3 | missingok 4 | compress 5 | nocreate 6 | rotate 1 7 | } 8 | -------------------------------------------------------------------------------- /misc/packaging/logrotate/ansible-runner-service: -------------------------------------------------------------------------------- 1 | /var/log/ovirt-engine/ansible-runner-service.log { 2 | weekly 3 | missingok 4 | compress 5 | create 6 | rotate 4 7 | } 8 | -------------------------------------------------------------------------------- /tests/TODO: -------------------------------------------------------------------------------- 1 | 2 | test start up call prod + dev ok, anything else fails 3 | 4 | check pre-reqs are in place 5 | 6 | is API reachable 7 | 8 | Drive each API method 9 | 10 | confirm multiple playbooks can run concurrently 11 | -------------------------------------------------------------------------------- /samples/ceph/README.md: -------------------------------------------------------------------------------- 1 | ## Ceph samples 2 | 3 | | file name | Description | 4 | | ----------| ------------| 5 | | new_osd.json | Add an osd to an existing cluster Use the new_osd.json sample file to describe your target OSD enviroment, then submit the POST request | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | samples/artifacts/* 4 | samples/roles/* 5 | OLD_STUFF/* 6 | __pycache__ 7 | dist 8 | build 9 | *.egg-info 10 | .idea 11 | venv 12 | .tox 13 | # Virtual env folders 14 | bin 15 | include 16 | lib* 17 | pip-self* 18 | .py[cod] 19 | __pycache__ 20 | -------------------------------------------------------------------------------- /packaging/gunicorn/ansible-runner-service.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Ansible Runner Service 3 | After=network.target 4 | 5 | [Service] 6 | ExecStart=/usr/bin/gunicorn-3 -b localhost:50001 -w 2 runner_service.wsgi:application 7 | Restart=always 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /samples/project/runnertest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # 3 | # VARS_LIST : time_delay 4 | # 5 | - name: test Playbook 6 | hosts: 7 | - localhost 8 | gather_facts: False 9 | tasks: 10 | - name: Step 1 11 | command: sleep {{ time_delay }} 12 | - name: Step 2 13 | command: sleep {{ time_delay }} 14 | tags: 15 | - onlyme 16 | -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/2-992a67f3-deb5-4b6d-9ebd-fd680ad79c0e.json: -------------------------------------------------------------------------------- 1 | {"uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "counter": 2, "stdout": "", "start_line": 1, "end_line": 1, "created": "2018-09-13T21:16:50.316667", "pid": 26662, "event_data": {"pid": 26662, "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml"}, "event": "playbook_on_start"} -------------------------------------------------------------------------------- /misc/nginx/uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | 3 | # General settings 4 | logto = /var/log/uwsgi.log 5 | 6 | # App settings 7 | chdir = /root/ansible-runner-service 8 | mount = /=wsgi:application 9 | manage-script-name 10 | buffer-size = 32768 11 | enable-threads = true 12 | 13 | # Communication with Nginx 14 | socket = /tmp/AnsibleRunnerService.sock 15 | 16 | # Concurrency settings 17 | cheaper = 1 18 | processes = %(%k + 1) 19 | -------------------------------------------------------------------------------- /tests/data/project/testplaybook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: test Playbook execution against localhost 4 | hosts: 5 | - localhost 6 | gather_facts: False 7 | tasks: 8 | - name: Just go to sleep for a second 9 | command: sleep 1 10 | - name: Just go to sleep for a second 11 | command: sleep 1 12 | tags: solo 13 | - name: Just go to sleep for a second 14 | command: sleep 1 15 | -------------------------------------------------------------------------------- /misc/nginx/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | 4 | [program:uwsgi] 5 | command=uwsgi --ini /root/ansible-runner-service/uwsgi.ini --die-on-term 6 | stdout_logfile=/dev/stdout 7 | stdout_logfile_maxbytes=0 8 | stderr_logfile=/dev/stderr 9 | stderr_logfile_maxbytes=0 10 | 11 | [program:nginx] 12 | command=/usr/sbin/nginx 13 | stdout_logfile=/dev/stdout 14 | stdout_logfile_maxbytes=0 15 | stderr_logfile=/dev/stderr 16 | stderr_logfile_maxbytes=0 17 | -------------------------------------------------------------------------------- /runner_service/controllers/base.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | 3 | 4 | class BaseResource(Resource): 5 | state_to_http = { 6 | "OK": 200, 7 | "STARTED": 202, 8 | "INVALID": 400, 9 | "NOAUTH": 401, 10 | "FORBIDDEN": 403, 11 | "NOCONN": 404, 12 | "NOTFOUND": 404, 13 | "UNKNOWN": 404, 14 | "LOCKED": 409, 15 | "UNSUPPORTED": 415, 16 | "FAILED": 500, 17 | "TIMEOUT": 504 18 | } 19 | -------------------------------------------------------------------------------- /misc/systemd/ansible-runner-service.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Ansible Runner based light-weight RESTful web service 3 | 4 | Requires=network-online.target 5 | After=network-online.target 6 | Wants=network-online.target 7 | 8 | [Service] 9 | LimitNOFILE=1048576 10 | LimitNPROC=1048576 11 | Type=simple 12 | User=root 13 | Group=root 14 | ExecStart=/usr/bin/ansible_runner_service 15 | Restart=on-failure 16 | StartLimitInterval=30min 17 | StartLimitBurst=3 18 | 19 | [Install] 20 | WantedBy=multi-user.target 21 | -------------------------------------------------------------------------------- /runner_service/static/utils.js: -------------------------------------------------------------------------------- 1 | 2 | function showme() { 3 | 4 | var tr = this; 5 | 6 | // if data is selected, the user is trying to copy text so simply 7 | // exit 8 | var selection_type = window.getSelection().type; 9 | if (selection_type == "Range") { 10 | return; 11 | } 12 | 13 | var td = this.getElementsByTagName("td")[0]; 14 | details = td.getElementsByTagName("div")[0]; 15 | details.style.display = details.style.display === 'none' ? 'inline-block' : 'none'; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /ISSUES: -------------------------------------------------------------------------------- 1 | 1. Ansible runner redefines the logging level of the root logger, that propogates 2 | to this projects module loggers. As such once you run a playbook, all module logging 3 | disappears. Is this a bug problem when embedding ansible_runner in other solutions? 4 | 5 | WORKAROUND: Redefine the root logger's level again after the call to ansible_runner 6 | 7 | 2. Security. Current the request methods are wrapped by an auth method that doesn't 8 | do anything. Need to decide the best approach to provide security: basic httpauth, 9 | httpauth + token, or all the way to oauth...maybe others? 10 | 11 | 3. 12 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # tox (https://tox.readthedocs.io/) is a tool for running tests. 2 | # 3 | 4 | [tox] 5 | envlist = py27,py36,setup,linter 6 | 7 | [testenv] 8 | deps = 9 | ansible_runner 10 | cryptography 11 | PyYAML 12 | pyOpenSSL 13 | flask 14 | flask-restful 15 | pylint 16 | flake8 17 | pyjwt 18 | 19 | [testenv:setup] 20 | commands = {envpython} setup.py test 21 | 22 | [testenv:linter] 23 | commands = 24 | flake8 --ignore=E501 ansible_runner_service.py runner_service/ 25 | 26 | [testenv:py36] 27 | commands = 28 | {envpython} -m unittest discover -s tests -p "test_*.py" 29 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 1 3 | 4 | # playbooks_root_dir 5 | # location of the playbooks that the service will start 6 | # playbooks_root_dir: './samples' 7 | 8 | # port 9 | # tcp port for the service to listen to 10 | # port: 5001 11 | # 12 | # ip_address 13 | # Specific IP address to bind to 14 | # ip_address: '0.0.0.0' 15 | target_user: root 16 | 17 | #event_cache_size: 3 18 | 19 | # maximum age of an artifact folder in days 20 | # set to 0 to disable the automatic removal of old artifact folders 21 | # artifacts_remove_age: 7 22 | 23 | # how frequently the old artifacts should be removed in days 24 | # artifacts_remove_frequency: 1 25 | -------------------------------------------------------------------------------- /runner_service/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .playbooks import (ListPlaybooks, # noqa: F401 3 | PlaybookState, 4 | StartPlaybook, 5 | StartTaggedPlaybook) 6 | from .api import API # noqa: F401 7 | from .hosts import Hosts, HostMgmt, HostDetails # noqa: F401 8 | from .jobs import ListEvents, GetEvent # noqa: F401 9 | from .groups import ListGroups, ManageGroups # noqa: F401 10 | from .metrics import PrometheusMetrics # noqa: F401 11 | from .vars import HostVars, GroupVars # noqa: F401 12 | -------------------------------------------------------------------------------- /runner_service/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .utils import RunnerServiceError # noqa: F401 3 | 4 | from .inventory import (AnsibleInventory, # noqa: F401 5 | InventoryGroupEmpty, 6 | InventoryWriteError, 7 | InventoryGroupExists, 8 | InventoryHostMissing, 9 | InventoryGroupMissing, 10 | InventoryRequestInvalid, 11 | InventoryreadError, 12 | InventoryCorruptError, 13 | InventoryOperationNotAllowed) 14 | 15 | __version__ = '1.0.3' 16 | -------------------------------------------------------------------------------- /samples/ceph/new_osd.json: -------------------------------------------------------------------------------- 1 | { 2 | "cluster": "ceph", 3 | "ceph_origin": "repository", 4 | "ceph_repository": "community", 5 | "ceph_stable_release": "luminous", 6 | "fetch_directory": "/home/paul/git/ansible-runner-service/samples/project/app-data", 7 | "containerized_deployment": true, 8 | "ceph_docker_version": "12.2", 9 | "monitor_interface": "eth0", 10 | "public_network": "10.90.90.0/24", 11 | "cluster_network": "10.90.90.0/24", 12 | "cephx": true, 13 | "osd_objectstore": "filestore", 14 | "devices": [ 15 | "/dev/vdd" 16 | ], 17 | "dmcrypt": false, 18 | "osd_scenario": "collocated" 19 | } 20 | -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/3-c85b7671-906d-f1b8-c363-000000000008.json: -------------------------------------------------------------------------------- 1 | {"uuid": "c85b7671-906d-f1b8-c363-000000000008", "counter": 3, "stdout": "\r\nPLAY [probe hosts for free disks] **********************************************", "start_line": 1, "end_line": 3, "created": "2018-09-13T21:16:50.362064", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "name": "probe hosts for free disks", "pattern": "osds", "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml"}, "event": "playbook_on_play_start"} -------------------------------------------------------------------------------- /wsgi.py: -------------------------------------------------------------------------------- 1 | import runner_service.configuration as configuration 2 | from runner_service.app import create_app 3 | from ansible_runner_service import setup_common_environment, remove_artifacts_init 4 | 5 | 6 | """ 7 | WSGI config for Ansible Runner Service 8 | 9 | It exposes the WSGI callable as a module-level variable named ``application``. 10 | 11 | """ 12 | 13 | # wsgi entry point is only for production servers 14 | configuration.init(mode='prod') 15 | 16 | # Setup log and ssh and other things present in all the environments 17 | setup_common_environment() 18 | 19 | # Setup remove of artifacts 20 | remove_artifacts_init() 21 | 22 | # The object to be managed by uwsgi 23 | application = create_app() 24 | -------------------------------------------------------------------------------- /runner_service/cache.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | # define dict based variables to act as caches across other modules 4 | 5 | class RunnerStats(object): 6 | 7 | playbook_status = { 8 | "successful": 0, 9 | "failed": 0, 10 | "canceled": 0, 11 | "timeout": 0} 12 | 13 | event_stats = { 14 | "ok": 0, 15 | "failed": 0, 16 | "skipped": 0, 17 | "unreachable": 0, 18 | "no_hosts": 0, 19 | "file_diff": 0, 20 | "async_failed": 0, 21 | "async_ok": 0, 22 | "async_poll": 0} 23 | 24 | 25 | event_cache = {} 26 | 27 | runner_cache = defaultdict(dict) 28 | 29 | runner_stats = RunnerStats() 30 | -------------------------------------------------------------------------------- /logging.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | version: 1 4 | disable_existing_loggers: False 5 | formatters: 6 | simple: 7 | format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 8 | 9 | handlers: 10 | console: 11 | class: logging.StreamHandler 12 | level: DEBUG 13 | formatter: simple 14 | stream: ext://sys.stdout 15 | 16 | file_handler: 17 | class: logging.handlers.RotatingFileHandler 18 | level: DEBUG 19 | formatter: simple 20 | filename: ansible-runner-service.log 21 | maxBytes: 10485760 # 10MB 22 | backupCount: 20 23 | encoding: utf8 24 | 25 | root: 26 | level: DEBUG 27 | handlers: [console, file_handler] 28 | -------------------------------------------------------------------------------- /misc/packaging/apache/README.md: -------------------------------------------------------------------------------- 1 | This spec file is used when you need to deploy ansible-runner-service without SSL authentication 2 | on the Apache. The only thing you need to do is to configure Apache as follows and Ansible runner 3 | service will be running on port 5001, served by Apache: 4 | 5 | ``` 6 | Listen 5001 7 | 8 | LogLevel debug 9 | WSGIDaemonProcess runner user=user group=user threads=2 10 | WSGIProcessGroup runner 11 | WSGIScriptAlias / /var/www/runner/runner.wsgi 12 | CustomLog "logs/ansible_runner_log" "%h %l %u %t \"%r\" %>s %b" 13 | ErrorLog "logs/ansible_runner_error_log" 14 | 15 | ``` 16 | 17 | Where `/var/www/runner/runner.wsgi` is `wsgi.py` file copied from root of this repo. 18 | -------------------------------------------------------------------------------- /packaging/gunicorn/wsgi.patch: -------------------------------------------------------------------------------- 1 | diff --git a/wsgi.py b/wsgi.py 2 | index d66f9c5..93fc875 100644 3 | --- a/wsgi.py 4 | +++ b/wsgi.py 5 | @@ -1,6 +1,8 @@ 6 | +#!/usr/bin/python 7 | + 8 | import runner_service.configuration as configuration 9 | from runner_service.app import create_app 10 | -from ansible_runner_service import setup_common_environment, remove_artifacts_init 11 | +from runner_service.ansible_runner_service import setup_logging, remove_artifacts_init 12 | 13 | 14 | """ 15 | @@ -14,7 +16,7 @@ It exposes the WSGI callable as a module-level variable named ``application``. 16 | configuration.init(mode='prod') 17 | 18 | # Setup log and ssh and other things present in all the environments 19 | -setup_common_environment() 20 | +setup_logging() 21 | 22 | # Setup remove of artifacts 23 | remove_artifacts_init() 24 | -------------------------------------------------------------------------------- /misc/packaging/apache/wsgi.patch: -------------------------------------------------------------------------------- 1 | diff --git a/wsgi.py b/wsgi.py 2 | index d66f9c5..93fc875 100644 3 | --- a/wsgi.py 4 | +++ b/wsgi.py 5 | @@ -1,6 +1,8 @@ 6 | +#!/usr/bin/python 7 | + 8 | import runner_service.configuration as configuration 9 | from runner_service.app import create_app 10 | -from ansible_runner_service import setup_common_environment, remove_artifacts_init 11 | +from runner_service.ansible_runner_service import setup_logging, remove_artifacts_init 12 | 13 | 14 | """ 15 | @@ -14,7 +16,7 @@ It exposes the WSGI callable as a module-level variable named ``application``. 16 | configuration.init(mode='prod') 17 | 18 | # Setup log and ssh and other things present in all the environments 19 | -setup_common_environment() 20 | +setup_logging() 21 | 22 | # Setup remove of artifacts 23 | remove_artifacts_init() 24 | -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/40-27df7f53-1cac-498b-a716-754305472172.json: -------------------------------------------------------------------------------- 1 | {"uuid": "27df7f53-1cac-498b-a716-754305472172", "counter": 40, "stdout": "", "start_line": 44, "end_line": 44, "created": "2018-09-13T21:16:55.284669", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Update hosts freedisk list", "task_args": "host_disk={{host_disk + [item.item]}}", "remote_addr": "con-3", "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000d", "event_loop": "{{ pv_status.results}}", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-3", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:59"}, "event": "runner_on_skipped"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/8-c85b7671-906d-f1b8-c363-00000000000a.json: -------------------------------------------------------------------------------- 1 | {"uuid": "c85b7671-906d-f1b8-c363-00000000000a", "counter": 8, "stdout": "\r\nTASK [setup] *******************************************************************", "start_line": 8, "end_line": 10, "created": "2018-09-13T21:16:52.038742", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "setup", "task_args": "host_disk=[]", "name": "setup", "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "is_conditional": false, "task_uuid": "c85b7671-906d-f1b8-c363-00000000000a", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:48"}, "event": "playbook_on_task_start"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/48-c85b7671-906d-f1b8-c363-00000000000e.json: -------------------------------------------------------------------------------- 1 | {"uuid": "c85b7671-906d-f1b8-c363-00000000000e", "counter": 48, "stdout": "\r\nTASK [RESULTS] *****************************************************************", "start_line": 49, "end_line": 51, "created": "2018-09-13T21:16:55.411342", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "RESULTS", "task_args": "var=free_disks", "name": "RESULTS", "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "is_conditional": false, "task_uuid": "c85b7671-906d-f1b8-c363-00000000000e", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "debug", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:65"}, "event": "playbook_on_task_start"} -------------------------------------------------------------------------------- /misc/nginx/ars_site_nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 5001 ssl; 3 | 4 | ssl_certificate "/etc/ansible-runner-service/certs/server/server.crt"; 5 | ssl_certificate_key "/etc/ansible-runner-service/certs/server/server.key"; 6 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 7 | 8 | ssl_client_certificate "/etc/ansible-runner-service/certs/server/ca.crt"; 9 | ssl_verify_client on; 10 | 11 | ssl_session_cache shared:SSL:1m; 12 | ssl_session_timeout 10m; 13 | ssl_ciphers HIGH:!aNULL:!MD5; 14 | ssl_prefer_server_ciphers on; 15 | 16 | ignore_invalid_headers off; 17 | underscores_in_headers on; 18 | 19 | location / { 20 | try_files $uri @AnsibleRunnerService; 21 | } 22 | location @AnsibleRunnerService { 23 | include uwsgi_params; 24 | uwsgi_pass unix:///tmp/AnsibleRunnerService.sock; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/4-c85b7671-906d-f1b8-c363-000000000012.json: -------------------------------------------------------------------------------- 1 | {"uuid": "c85b7671-906d-f1b8-c363-000000000012", "counter": 4, "stdout": "\r\nTASK [Gathering Facts] *********************************************************", "start_line": 3, "end_line": 5, "created": "2018-09-13T21:16:50.382542", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Gathering Facts", "task_args": "gather_subset=all, gather_timeout=10", "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "is_conditional": false, "task_uuid": "c85b7671-906d-f1b8-c363-000000000012", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "setup", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:25", "name": "Gathering Facts"}, "event": "playbook_on_task_start"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/9-4be01772-502d-472e-9e1a-2b12ac9fdcb8.json: -------------------------------------------------------------------------------- 1 | {"uuid": "4be01772-502d-472e-9e1a-2b12ac9fdcb8", "counter": 9, "stdout": "\u001b[0;32mok: [con-2]\u001b[0m", "start_line": 10, "end_line": 11, "created": "2018-09-13T21:16:52.124922", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "setup", "task_args": "host_disk=[]", "remote_addr": "con-2", "res": {"changed": false, "ansible_facts": {"host_disk": []}, "_ansible_no_log": false}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000a", "event_loop": null, "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-2", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:48"}, "event": "runner_on_ok"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/10-df2fd54b-e0a8-4e43-a48c-ff2227172770.json: -------------------------------------------------------------------------------- 1 | {"uuid": "df2fd54b-e0a8-4e43-a48c-ff2227172770", "counter": 10, "stdout": "\u001b[0;32mok: [con-3]\u001b[0m", "start_line": 11, "end_line": 12, "created": "2018-09-13T21:16:52.126659", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "setup", "task_args": "host_disk=[]", "remote_addr": "con-3", "res": {"changed": false, "ansible_facts": {"host_disk": []}, "_ansible_no_log": false}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000a", "event_loop": null, "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-3", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:48"}, "event": "runner_on_ok"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/11-b1080bd8-2d64-43ea-9d0b-05a21f416fbd.json: -------------------------------------------------------------------------------- 1 | {"uuid": "b1080bd8-2d64-43ea-9d0b-05a21f416fbd", "counter": 11, "stdout": "\u001b[0;32mok: [con-1]\u001b[0m", "start_line": 12, "end_line": 13, "created": "2018-09-13T21:16:52.195388", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "setup", "task_args": "host_disk=[]", "remote_addr": "con-1", "res": {"changed": false, "ansible_facts": {"host_disk": []}, "_ansible_no_log": false}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000a", "event_loop": null, "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-1", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:48"}, "event": "runner_on_ok"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/16-c85b7671-906d-f1b8-c363-00000000000c.json: -------------------------------------------------------------------------------- 1 | {"uuid": "c85b7671-906d-f1b8-c363-00000000000c", "counter": 16, "stdout": "\r\nTASK [check if disk is free] ***************************************************", "start_line": 18, "end_line": 20, "created": "2018-09-13T21:16:52.883218", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "check if disk is free", "task_args": "_raw_params=pvcreate --test /dev/{{ item }}", "name": "check if disk is free", "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "is_conditional": false, "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54"}, "event": "playbook_on_task_start"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/32-c85b7671-906d-f1b8-c363-00000000000d.json: -------------------------------------------------------------------------------- 1 | {"uuid": "c85b7671-906d-f1b8-c363-00000000000d", "counter": 32, "stdout": "\r\nTASK [Update hosts freedisk list] **********************************************", "start_line": 35, "end_line": 37, "created": "2018-09-13T21:16:55.149954", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Update hosts freedisk list", "task_args": "host_disk={{host_disk + [item.item]}}", "name": "Update hosts freedisk list", "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "is_conditional": false, "task_uuid": "c85b7671-906d-f1b8-c363-00000000000d", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:59"}, "event": "playbook_on_task_start"} -------------------------------------------------------------------------------- /packaging/gunicorn/ovirt_log.patch: -------------------------------------------------------------------------------- 1 | diff --git a/logging.yaml b/logging.yaml 2 | index 8357727..16a0c1c 100644 3 | --- a/logging.yaml 4 | +++ b/logging.yaml 5 | @@ -7,21 +7,13 @@ formatters: 6 | format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 7 | 8 | handlers: 9 | - console: 10 | - class: logging.StreamHandler 11 | - level: DEBUG 12 | - formatter: simple 13 | - stream: ext://sys.stdout 14 | - 15 | file_handler: 16 | - class: logging.handlers.RotatingFileHandler 17 | + class: logging.handlers.WatchedFileHandler 18 | level: DEBUG 19 | formatter: simple 20 | - filename: ansible-runner-service.log 21 | + filename: /var/log/ovirt-engine/ansible-runner-service.log 22 | - maxBytes: 10485760 # 10MB 23 | - backupCount: 20 24 | encoding: utf8 25 | 26 | root: 27 | level: DEBUG 28 | - handlers: [console, file_handler] 29 | + handlers: [file_handler] 30 | -------------------------------------------------------------------------------- /misc/packaging/apache/ovirt_log.patch: -------------------------------------------------------------------------------- 1 | diff --git a/logging.yaml b/logging.yaml 2 | index 8357727..16a0c1c 100644 3 | --- a/logging.yaml 4 | +++ b/logging.yaml 5 | @@ -7,21 +7,13 @@ formatters: 6 | format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 7 | 8 | handlers: 9 | - console: 10 | - class: logging.StreamHandler 11 | - level: DEBUG 12 | - formatter: simple 13 | - stream: ext://sys.stdout 14 | - 15 | file_handler: 16 | - class: logging.handlers.RotatingFileHandler 17 | + class: logging.handlers.WatchedFileHandler 18 | level: DEBUG 19 | formatter: simple 20 | - filename: ansible-runner-service.log 21 | + filename: /var/log/ovirt-engine/ansible-runner-service.log 22 | - maxBytes: 10485760 # 10MB 23 | - backupCount: 20 24 | encoding: utf8 25 | 26 | root: 27 | level: DEBUG 28 | - handlers: [console, file_handler] 29 | + handlers: [file_handler] 30 | -------------------------------------------------------------------------------- /misc/docker/ansible-runner-service.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Ansible Runner Rest API Service 3 | After=docker.service 4 | 5 | # todo 6 | # - add ansible hosts to the bind mounts 7 | # - add .ssh keys to the bind mounts 8 | # - selinux 9 | # - assumes the image is in the local image repo already 10 | 11 | [Service] 12 | EnvironmentFile=-/etc/environment 13 | ExecStartPre=-/usr/bin/docker stop runner-service 14 | ExecStartPre=-/usr/bin/docker rm runner-service 15 | ExecStart=/usr/bin/docker run --rm --net=host \ 16 | --memory=2g \ 17 | --cpu-quota=200000 \ 18 | -p 5001:5001/tcp \ 19 | -v /usr/share/ansible-runner-service:/usr/share/ansible-runner-service \ 20 | -v /etc/ansible-runner-service:/etc/ansible-runner-service \ 21 | --name=runner-service \ 22 | runner-service:latest 23 | ExecStopPost=-/usr/bin/docker stop runner-service 24 | Restart=always 25 | RestartSec=10s 26 | TimeoutStartSec=120 27 | TimeoutStopSec=15 28 | 29 | [Install] 30 | WantedBy=multi-user.target 31 | -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/12-c85b7671-906d-f1b8-c363-00000000000b.json: -------------------------------------------------------------------------------- 1 | {"uuid": "c85b7671-906d-f1b8-c363-00000000000b", "counter": 12, "stdout": "\r\nTASK [Get a list of block devices (excludes loop and child devices)] ***********", "start_line": 13, "end_line": 15, "created": "2018-09-13T21:16:52.233734", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Get a list of block devices (excludes loop and child devices)", "task_args": "_raw_params=lsblk -n --o NAME --nodeps --exclude 7", "name": "Get a list of block devices (excludes loop and child devices)", "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "is_conditional": false, "task_uuid": "c85b7671-906d-f1b8-c363-00000000000b", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:51"}, "event": "playbook_on_task_start"} -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | - "2.7" 5 | 6 | addons: 7 | apt: 8 | packages: 9 | - openssh-server 10 | - software-properties-common 11 | 12 | cache: pip 13 | 14 | branches: 15 | only: 16 | - master 17 | - nginx 18 | 19 | before_install: 20 | - sudo apt-add-repository --yes ppa:ansible/ansible 21 | - sudo apt-get update 22 | - sudo apt-get --yes install ansible 23 | 24 | install: 25 | - pip install -r requirements.txt 26 | - python setup.py -q install 27 | - pip install coverage 28 | - pip install codecov 29 | 30 | before_script: 31 | - cp -pr tests /tmp 32 | - cp ansible_runner_service.py /tmp 33 | 34 | script: 35 | - flake8 --ignore=E501 ansible_runner_service.py runner-service/ 36 | - cd /tmp/tests && python -m unittest discover -p "test_*.py" 37 | - coverage run -m unittest discover -p "test_*.py" 38 | 39 | after_success: 40 | - codecov 41 | 42 | after_script: 43 | - ls -al ~/.ssh 44 | - cat ~/.ssh/authorized_keys 45 | 46 | # To trigger travis builds 47 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 2 | Validation on content Type 3 | validation of uuid format 4 | validate playbook name format 5 | validate event name format 6 | 7 | Security on the Api 8 | - should we hold a small db (sqlite3) containing users/password/tokens/expiry date 9 | - cache the credentials to prevent repeated i/o to sqlite db 10 | - have a login api endpoint, that returns a token 11 | - all normal requests must use the tokens - fail basicauth 12 | - could support multiple users with a db, and token architecture 13 | - with expiring tokens the "exploit window" is reduced 14 | - use https as the api endpoint 15 | 16 | What to do about Hosts? 17 | - post - hostname and root password in the payload, extend utils.py/SSHClient class? 18 | passwordless ssh - return 200 if login successful 19 | - put request update a host to belong to a given groups 20 | 21 | Should all POSTs optionally expect a host= parameter to restrict the runner 22 | - check that the host given in in /etc/ansible/hosts first :) 23 | 24 | 25 | What about DoS? 26 | - should we record the frequency of the calls, and if too much just abort the request? 27 | -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/50-906885d5-a67e-413d-914b-60b41a924192.json: -------------------------------------------------------------------------------- 1 | {"uuid": "906885d5-a67e-413d-914b-60b41a924192", "counter": 50, "stdout": "\r\nPLAY RECAP *********************************************************************\r\n\u001b[0;33mcon-1\u001b[0m : \u001b[0;32mok=5 \u001b[0m \u001b[0;33mchanged=2 \u001b[0m unreachable=0 failed=0 \r\n\u001b[0;33mcon-2\u001b[0m : \u001b[0;32mok=6 \u001b[0m \u001b[0;33mchanged=2 \u001b[0m unreachable=0 failed=0 \r\n\u001b[0;33mcon-3\u001b[0m : \u001b[0;32mok=4 \u001b[0m \u001b[0;33mchanged=2 \u001b[0m unreachable=0 failed=0 \r\n", "start_line": 74, "end_line": 80, "created": "2018-09-13T21:16:55.644643", "pid": 26662, "event_data": {"skipped": {"con-3": 1}, "ok": {"con-2": 6, "con-3": 4, "con-1": 5}, "artifact_data": {}, "changed": {"con-2": 2, "con-3": 2, "con-1": 2}, "pid": 26662, "dark": {}, "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "failures": {}, "processed": {"con-2": 1, "con-3": 1, "con-1": 1}}, "event": "playbook_on_stats"} -------------------------------------------------------------------------------- /misc/dashboards/README.md: -------------------------------------------------------------------------------- 1 | # Integrating with Prometheus and Grafana 2 | 3 | ## Scraping the data into Prometheus 4 | The ansible-runner-service process uses https, so to there are a couple of additional options needed to get Prometheus to scrape the data. 5 | 6 | Here's an example (used during testing); 7 | ``` 8 | - job_name: "ansible-runner-service" 9 | scrape_interval: 5s 10 | scheme: https 11 | tls_config: 12 | insecure_skip_verify: true 13 | static_configs: 14 | - targets: ["rh460p:5001"] 15 | 16 | ``` 17 | 18 | *obviously change the target!* 19 | 20 | ## Using a Dashboard 21 | This directory has an example dashboard called ```ansible-runner-service-metrics.json``` which illustrates the kind of insights the 22 | /metrics endpoint can provide. Import it into you grafana instance and you should see something like; 23 | 24 | ![runner service dashboard](./screenshot.png) 25 | 26 | The dashboard uses a template variable to represent the host so you could use the same dashboard to report against multiple instances of the ansible-runner-service. As you can see above, the dashboard also provides CPU busy information. To see this data, you need to have node_exporter (0.16 or above) installed too. 27 | -------------------------------------------------------------------------------- /misc/packaging/README: -------------------------------------------------------------------------------- 1 | EL7 build 2 | 3 | Assumptions 4 | - you have a valid rpmbuild environment! 5 | 6 | 7 | Known Issues 8 | - using the rpm depends on 1.1.1 of ansible_runner - but this isn't currently 9 | available outside pip, so the spec file is currently missing the dependency 10 | on ansible_runner 11 | - these steps cover a manual build process and as such the spec may need to 12 | change to support automated rpm build environments. 13 | 14 | 15 | Building your rpm based on the 0.8 release 16 | - copy .spec file to your SPECS directory 17 | - download from github to SOURCES directory using spectool 18 | spectool -g -R -d '_version 0.8' -d '_release 0' SPECS/ansible-runner-service.spec 19 | - cd SPECS and build the rpm with 20 | rpmbuild -bb --define '_version 0.8' --define '_release 0' ansible-runner-service.spec 21 | 22 | 23 | Build Environment 24 | CentOS7 25 | 26 | 27 | Resulting RPM Tested Against 28 | - CentOS 7.4, Python 2.7.5 29 | 30 | 31 | Container build 32 | 33 | The file allows to produce an rpm to install Ansible Runner Service without any dependency. 34 | This is specially suited for use in containers where the dependencies are going to be especified directly in the Dockerfile. 35 | -------------------------------------------------------------------------------- /runner_service/controllers/utils.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from flask import request 3 | from ..services.utils import APIResponse 4 | from .base import BaseResource 5 | from runner_service import configuration 6 | 7 | import logging 8 | logger = logging.getLogger(__name__) 9 | 10 | def log_request(logger): 11 | ''' 12 | wrapper function for HTTP request logging 13 | ''' 14 | def real_decorator(f): 15 | @wraps(f) 16 | def wrapper(*args, **kwargs): 17 | """ Look at the request, and log the details """ 18 | # logger.info("{}".format(request.url)) 19 | logger.debug("Request received, content-type :" 20 | "{}".format(request.content_type)) 21 | if request.content_type == 'application/json': 22 | sfx = ", parms={}".format(request.get_json()) 23 | else: 24 | sfx = '' 25 | logger.info("{} - {} {}{}".format(request.remote_addr, 26 | request.method, 27 | request.path, 28 | sfx)) 29 | return f(*args, **kwargs) 30 | return wrapper 31 | 32 | return real_decorator 33 | -------------------------------------------------------------------------------- /runner_service/services/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import yaml 4 | 5 | import runner_service.configuration as configuration 6 | 7 | 8 | def playbook_exists(playbook_name): 9 | playbook_path = os.path.join(configuration.settings.playbooks_root_dir, 10 | "project", 11 | playbook_name) 12 | return os.path.exists(playbook_path) 13 | 14 | 15 | def build_pb_path(play_uuid): 16 | return os.path.join(configuration.settings.playbooks_root_dir, 17 | "artifacts", 18 | play_uuid) 19 | 20 | 21 | def writeYAML(data, path_name): 22 | try: 23 | with open(path_name, "w") as yaml_file: 24 | yaml_file.write(yaml.safe_dump(data, 25 | default_flow_style=False, 26 | explicit_start=True)) 27 | except IOError as e: 28 | return False 29 | else: 30 | return True 31 | 32 | 33 | def loadYAML(path_name): 34 | with open(path_name, 'r') as yaml_in: 35 | data = yaml.safe_load(yaml_in) 36 | return data 37 | 38 | 39 | class APIResponse(object): 40 | def __init__(self): 41 | self.status = '' 42 | self.msg = '' 43 | self.data = dict() 44 | -------------------------------------------------------------------------------- /Dockerfile.python27: -------------------------------------------------------------------------------- 1 | FROM ansible/ansible-runner:1.3.2 2 | 3 | # Install dependencies 4 | RUN yum -y install bash wget unzip gcc \ 5 | python-devel python-setuptools \ 6 | nginx supervisor 7 | 8 | RUN pip install cryptography PyYAML netaddr notario\ 9 | pyOpenSSL flask flask-restful uwsgi && \ 10 | rm -rf /var/cache/yum 11 | 12 | # Prepare folders for shared access and ssh 13 | RUN mkdir -p /etc/ansible-runner-service && \ 14 | mkdir -p /root/.ssh && \ 15 | mkdir -p /usr/share/ansible-runner-service/{artifacts,env,project,inventory,client_cert} 16 | 17 | # Install Ansible Runner Service 18 | WORKDIR /root 19 | COPY ./*.py ansible-runner-service/ 20 | COPY ./*.yaml ansible-runner-service/ 21 | COPY ./runner_service ansible-runner-service/runner_service 22 | COPY ./samples ansible-runner-service/samples 23 | 24 | # Put configuration files in the right places 25 | # Nginx configuration 26 | COPY misc/nginx/nginx.conf /etc/nginx/ 27 | # Ansible Runner Service nginx virtual server 28 | COPY misc/nginx/ars_site_nginx.conf /etc/nginx/conf.d 29 | # Ansible Runner Service uwsgi settings 30 | COPY misc/nginx/uwsgi.ini /root/ansible-runner-service 31 | # Supervisor start sequence 32 | COPY misc/nginx/supervisord.conf /root/ansible-runner-service 33 | 34 | # Start services 35 | CMD ["/usr/bin/supervisord", "-c", "/root/ansible-runner-service/supervisord.conf"] 36 | -------------------------------------------------------------------------------- /misc/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | # For more information on configuration, see: 2 | # * Official English Documentation: http://nginx.org/en/docs/ 3 | # * Official Russian Documentation: http://nginx.org/ru/docs/ 4 | 5 | user root; 6 | worker_processes auto; 7 | error_log /var/log/nginx/error.log info; 8 | pid /run/nginx.pid; 9 | 10 | # turn off daemon mode to be watched by supervisord 11 | daemon off; 12 | 13 | # Load dynamic modules. See /usr/share/nginx/README.dynamic. 14 | include /usr/share/nginx/modules/*.conf; 15 | 16 | events { 17 | worker_connections 1024; 18 | } 19 | 20 | http { 21 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 22 | '$status $body_bytes_sent "$http_referer" ' 23 | '"$http_user_agent" "$http_x_forwarded_for"'; 24 | 25 | access_log /var/log/nginx/access.log main; 26 | 27 | sendfile on; 28 | tcp_nopush on; 29 | tcp_nodelay on; 30 | keepalive_timeout 65; 31 | types_hash_max_size 2048; 32 | client_max_body_size 0; 33 | 34 | include /etc/nginx/mime.types; 35 | default_type application/octet-stream; 36 | 37 | # Load modular configuration files from the /etc/nginx/conf.d directory. 38 | # See http://nginx.org/en/docs/ngx_core_module.html#include 39 | # for more information. 40 | include /etc/nginx/conf.d/*.conf; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /misc/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | # python2 CentOS packages 4 | # python2-flask-restful python-flask python-crypto pyOpenSSL 5 | # python2-psutil python-pip 6 | # python-daemon (pulled in by pip3 install of ansible-runner) 7 | # python-wheel 8 | # PyYAML 9 | 10 | 11 | # Install Ansible Runner 12 | RUN yum -y install epel-release && \ 13 | yum -y install bash wget unzip ansible \ 14 | pexpect python-daemon bubblewrap gcc \ 15 | bzip2 openssh openssh-clients python2-psutil\ 16 | python36 python36-devel python36-setuptools && \ 17 | localedef -c -i en_US -f UTF-8 en_US.UTF-8 18 | RUN easy_install-3.6 -d /usr/lib/python3.6/site-packages pip && \ 19 | ln -s /usr/lib/python3.6/site-packages/pip3 /usr/local/bin/pip3 20 | RUN /usr/local/bin/pip3 install cryptography docutils psutil PyYAML \ 21 | pyOpenSSL flask flask-restful && \ 22 | /usr/local/bin/pip3 install --no-cache-dir ansible-runner==1.3.2 && \ 23 | rm -rf /var/cache/yum 24 | 25 | RUN mkdir -p /etc/ansible-runner-service && \ 26 | mkdir -p /root/.ssh && \ 27 | mkdir -p /usr/share/ansible-runner-service/{artifacts,env,project,inventory} 28 | 29 | COPY ./ansible-runner-service.tar.gz /root/. 30 | WORKDIR /root 31 | RUN tar xvzf ansible-runner-service.tar.gz && \ 32 | cd ansible-runner-service && \ 33 | python36 setup.py install --record installed_files \ 34 | --single-version-externally-managed 35 | 36 | ENTRYPOINT ["/usr/local/bin/ansible_runner_service"] 37 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from setuptools import setup 4 | 5 | import distutils.command.install_scripts 6 | import shutil 7 | import re 8 | 9 | module_file = open("runner_service/__init__.py").read() 10 | metadata = dict(re.findall(r"__([a-z]+)__\s*=\s*'([^']+)'", module_file)) 11 | 12 | 13 | # idea from http://stackoverflow.com/a/11400431/2139420 14 | class StripExtension(distutils.command.install_scripts.install_scripts): 15 | """ 16 | Class to handle the stripping of .py extensions in for executable file 17 | names making them more user friendly 18 | """ 19 | def run(self): 20 | distutils.command.install_scripts.install_scripts.run(self) 21 | for script in self.get_outputs(): 22 | if script.endswith(".py"): 23 | shutil.move(script, script[:-3]) 24 | 25 | 26 | setup( 27 | name="ansible-runner-service", 28 | version=metadata['version'], 29 | description="Ansible runner based REST API", 30 | long_description="Ansible runner based light weight RESTful web service", 31 | author="Paul Cuzner", 32 | author_email="pcuzner@redhat.com", 33 | url="http://github.com/pcuzner/ansible-runner-service", 34 | license="Apache2", 35 | packages=[ 36 | "runner_service", 37 | "runner_service/controllers", 38 | "runner_service/services" 39 | ], 40 | scripts=[ 41 | 'ansible_runner_service.py' 42 | ], 43 | include_package_data=True, 44 | zip_safe=False, 45 | data_files=[], 46 | cmdclass={ 47 | "install_scripts": StripExtension 48 | } 49 | ) 50 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | # Install dependencies 4 | RUN yum -y install epel-release && \ 5 | yum -y install bash wget unzip \ 6 | pexpect python-daemon bubblewrap gcc \ 7 | bzip2 openssh openssh-clients python2-psutil\ 8 | python36 python36-devel python36-setuptools\ 9 | nginx supervisor && \ 10 | localedef -c -i en_US -f UTF-8 en_US.UTF-8 11 | 12 | RUN /usr/bin/python3 -m pip install ansible cryptography docutils psutil PyYAML \ 13 | pyOpenSSL flask flask-restful uwsgi netaddr notario && \ 14 | /usr/bin/python3 -m pip install --no-cache-dir ansible-runner==1.4.6 && \ 15 | rm -rf /var/cache/yum 16 | 17 | # Prepare folders for shared access and ssh 18 | RUN mkdir -p /etc/ansible-runner-service && \ 19 | mkdir -p /root/.ssh && \ 20 | mkdir -p /usr/share/ansible-runner-service/{artifacts,env,project,inventory,client_cert} 21 | 22 | # Install Ansible Runner 23 | WORKDIR /root 24 | COPY ./*.py ansible-runner-service/ 25 | COPY ./*.yaml ansible-runner-service/ 26 | COPY ./runner_service ansible-runner-service/runner_service 27 | COPY ./samples ansible-runner-service/samples 28 | 29 | # Put configuration files in the right places 30 | # Nginx configuration 31 | COPY misc/nginx/nginx.conf /etc/nginx/ 32 | # Ansible Runner Service nginx virtual server 33 | COPY misc/nginx/ars_site_nginx.conf /etc/nginx/conf.d 34 | # Ansible Runner Service uwsgi settings 35 | COPY misc/nginx/uwsgi.ini /root/ansible-runner-service 36 | # Supervisor start sequence 37 | COPY misc/nginx/supervisord.conf /root/ansible-runner-service 38 | 39 | # Start services 40 | CMD ["/usr/bin/supervisord", "-c", "/root/ansible-runner-service/supervisord.conf"] 41 | -------------------------------------------------------------------------------- /misc/nginx/generate_client_cert.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Certificates data, password and placement are customizable in this file 4 | source ./certificates_data.custom 5 | 6 | # Get user preferences for CN parameter in server/client certificates 7 | usage="$(basename "$0") [-h] [-c string] -- Generate self signed certificates for client 8 | 9 | where: 10 | -h show this help text 11 | -c use as CN parameter for the client certificate" 12 | 13 | CLIENT_CN="*" 14 | 15 | while getopts hc: option 16 | do 17 | case "${option}" 18 | in 19 | h) echo "$usage" 20 | exit 21 | ;; 22 | c) CLIENT_CN=${OPTARG};; 23 | \?) echo "illegal option" 24 | echo "$usage" 25 | exit 26 | ;; 27 | esac 28 | done 29 | 30 | CERT_IDENTITY_CLIENT=$CERT_IDENTITY_CLIENT$CLIENT_CN 31 | 32 | # create folders 33 | mkdir -p $BASE_PATH/client 34 | 35 | # Client ----------------------------------------------------------------------- 36 | 37 | echo "Create the Client Key and CSR" 38 | openssl genrsa -des3 -out $BASE_PATH/client/client.key.org -passout pass:$CERT_PASSWORD 4096 39 | # Remove password (avoid https client claiming for it in each request) 40 | openssl rsa -in $BASE_PATH/client/client.key.org -out $BASE_PATH/client/client.key -passin pass:$CERT_PASSWORD 41 | # Generate client certificate 42 | openssl req -new -sha256 -key $BASE_PATH/client/client.key -out $BASE_PATH/client/client.csr -passin pass:$CERT_PASSWORD -subj "$CERT_IDENTITY_CLIENT" 43 | 44 | echo "Sign the client certificate with our CA cert" 45 | openssl x509 -req -sha256 -days 365 -in $BASE_PATH/client/client.csr -CA $BASE_PATH/server/ca.crt -CAkey $BASE_PATH/server/ca.key -CAcreateserial -out $BASE_PATH/client/client.crt -passin pass:$CERT_PASSWORD 46 | -------------------------------------------------------------------------------- /runner_service/controllers/api.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flask_restful import Resource 4 | from flask import render_template, Response 5 | from .utils import log_request 6 | 7 | import logging 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class API(Resource): 12 | """ Show available API endpoints (this page)""" 13 | app = None 14 | 15 | @log_request(logger) 16 | def get(self): 17 | 18 | app = API.app 19 | routes = [] 20 | 21 | for rule in app.url_map.iter_rules(): 22 | if rule.endpoint != 'static': 23 | method_list = [m for m in sorted(list(rule.methods)) 24 | if m not in ["HEAD", "OPTIONS"]] 25 | doc_string = app.view_functions[rule.endpoint].__doc__ 26 | 27 | details = dict() 28 | 29 | tgt = app.view_functions[rule.endpoint].view_class 30 | for method in method_list: 31 | func = tgt.__dict__.get(method.lower()) 32 | if func.__doc__: 33 | doc_as_list = [_d.lstrip() 34 | for _d in func.__doc__.split('\n')] 35 | details[func.__name__.upper()] = doc_as_list 36 | 37 | routes.append( 38 | {"route": rule.rule, 39 | "description": doc_string, 40 | "details": details, 41 | "methods": method_list 42 | } 43 | ) 44 | 45 | srtd_routes = sorted(routes, key=lambda k: k['route']) 46 | template = os.path.join("api.html") 47 | return Response(render_template(template, 48 | routes=srtd_routes), 49 | mimetype='text/html') 50 | -------------------------------------------------------------------------------- /tests/test_api_generic.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | import logging 4 | import unittest 5 | 6 | sys.path.extend(["../", "./"]) 7 | from common import APITestCase # noqa 8 | 9 | # turn of normal logging that the ansible_runner_service will generate 10 | nh = logging.NullHandler() 11 | r = logging.getLogger() 12 | r.addHandler(nh) 13 | 14 | 15 | class TestAPIGeneric(APITestCase): 16 | 17 | def test_api(self): 18 | """- Test the API endpoint '/api' responds""" 19 | 20 | response = self.app.get("https://localhost:5001/api") 21 | 22 | self.assertEqual(response.status_code, 23 | 200) 24 | self.assertEqual(response.headers['Content-Type'], 25 | 'text/html; charset=utf-8') 26 | 27 | def test_metrics_content_type(self): 28 | """- Test the API endpoint '/metrics' responds with text""" 29 | 30 | response = self.app.get("https://localhost:5001/metrics") 31 | 32 | self.assertEqual(response.status_code, 33 | 200) 34 | self.assertEqual(response.headers['Content-Type'], 35 | 'text/html; charset=utf-8') 36 | 37 | def test_metrics_value(self): 38 | """- Test a value from API endpoint '/metrics' is correct""" 39 | 40 | response = self.app.get("https://localhost:5001/metrics") 41 | 42 | self.assertEqual(response.status_code, 43 | 200) 44 | 45 | payload_list = str(response.data).split('\n') 46 | for _m in payload_list: 47 | if _m.startswith('runner_service_playbook_count'): 48 | _, v = _m.split('}') 49 | self.assertEqual(int(v.strip()), 1) 50 | break 51 | 52 | 53 | if __name__ == "__main__": 54 | 55 | unittest.main(verbosity=2) 56 | -------------------------------------------------------------------------------- /misc/nginx/certificates_data.custom: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Data to customize self-signed test certificates 4 | 5 | # Certificate Fields meaning: 6 | # ------------------------------------------------------------------------------ 7 | # /C : Country Name (2 letter code) [XX] 8 | # /ST: State or Province Name (full name) 9 | # /L : Locality Name (eg, city) 10 | # /O : Organization Name (eg, company) 11 | # /OU: Organizational Unit Name (eg, section) 12 | # /CN: Common Name (eg, your name or your server's hostname) 13 | 14 | # NOTE: An SSL certificate must be associated with one or more host names. 15 | # The CN field is used to identify the server/computer. If a certain name 16 | # is used in this field , the certificate only will be valid/used to 17 | # identify this computer. 18 | # eg: 19 | # /CN=client1.myorg.company 20 | # The certificate only can be used in the computer with hostname: 21 | # "client1.myorg.company" 22 | # /CN=*.myorg.company 23 | # This is a wildcard certificate, and can be used by any computer 24 | # in the domain "myorg.company" 25 | 26 | 27 | # Data used for the server certificate 28 | # ------------------------------------------------------------------------------ 29 | CERT_IDENTITY="/C=US/ST=North Carolina/L=Raleigh/O=Red Hat/OU=Automation/CN=" 30 | 31 | # Data used for the client certificate 32 | # ------------------------------------------------------------------------------ 33 | CERT_IDENTITY_CLIENT="/C=US/ST=North Carolina/L=Raleigh/O=TestOrg/OU=TestOU/CN=" 34 | 35 | # Password used for the certificates 36 | # ------------------------------------------------------------------------------ 37 | CERT_PASSWORD="ansible" 38 | 39 | 40 | # Folder to store the generated certificates 41 | # ------------------------------------------------------------------------------ 42 | BASE_PATH="/etc/ansible-runner-service/certs" 43 | -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/14-de2fde11-9d21-4b72-a6c5-30fdbc454831.json: -------------------------------------------------------------------------------- 1 | {"uuid": "de2fde11-9d21-4b72-a6c5-30fdbc454831", "counter": 14, "stdout": "\u001b[0;33mchanged: [con-3]\u001b[0m", "start_line": 16, "end_line": 17, "created": "2018-09-13T21:16:52.828797", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Get a list of block devices (excludes loop and child devices)", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=lsblk -n --o NAME --nodeps --exclude 7, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873412.34-151315314055585/, _ansible_debug=False", "remote_addr": "con-3", "res": {"_ansible_parsed": true, "stderr_lines": [], "changed": true, "end": "2018-09-13 21:16:52.774397", "_ansible_no_log": false, "stdout": "vda\nvdb\nvdc", "cmd": ["lsblk", "-n", "--o", "NAME", "--nodeps", "--exclude", "7"], "start": "2018-09-13 21:16:52.767177", "delta": "0:00:00.007220", "stderr": "", "rc": 0, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "lsblk -n --o NAME --nodeps --exclude 7", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": ["vda", "vdb", "vdc"]}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000b", "event_loop": null, "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-3", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:51"}, "event": "runner_on_ok"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/15-e736786d-7cfd-4f61-8a17-5bbdf989b89e.json: -------------------------------------------------------------------------------- 1 | {"uuid": "e736786d-7cfd-4f61-8a17-5bbdf989b89e", "counter": 15, "stdout": "\u001b[0;33mchanged: [con-1]\u001b[0m", "start_line": 17, "end_line": 18, "created": "2018-09-13T21:16:52.834728", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Get a list of block devices (excludes loop and child devices)", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=lsblk -n --o NAME --nodeps --exclude 7, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873412.39-144014032789850/, _ansible_debug=False", "remote_addr": "con-1", "res": {"_ansible_parsed": true, "stderr_lines": [], "changed": true, "end": "2018-09-13 21:16:52.777951", "_ansible_no_log": false, "stdout": "vda\nvdb\nvdc\nvdd", "cmd": ["lsblk", "-n", "--o", "NAME", "--nodeps", "--exclude", "7"], "start": "2018-09-13 21:16:52.771923", "delta": "0:00:00.006028", "stderr": "", "rc": 0, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "lsblk -n --o NAME --nodeps --exclude 7", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": ["vda", "vdb", "vdc", "vdd"]}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000b", "event_loop": null, "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-1", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:51"}, "event": "runner_on_ok"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/13-fc55d76c-7ffa-46a4-b1e3-919f38eeee51.json: -------------------------------------------------------------------------------- 1 | {"uuid": "fc55d76c-7ffa-46a4-b1e3-919f38eeee51", "counter": 13, "stdout": "\u001b[0;33mchanged: [con-2]\u001b[0m", "start_line": 15, "end_line": 16, "created": "2018-09-13T21:16:52.826414", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Get a list of block devices (excludes loop and child devices)", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=lsblk -n --o NAME --nodeps --exclude 7, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873412.3-241853041360628/, _ansible_debug=False", "remote_addr": "con-2", "res": {"_ansible_parsed": true, "stderr_lines": [], "changed": true, "end": "2018-09-13 21:16:52.767520", "_ansible_no_log": false, "stdout": "vda\nvdb\nvdc\nvdd\nvde", "cmd": ["lsblk", "-n", "--o", "NAME", "--nodeps", "--exclude", "7"], "start": "2018-09-13 21:16:52.758519", "delta": "0:00:00.009001", "stderr": "", "rc": 0, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "lsblk -n --o NAME --nodeps --exclude 7", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": ["vda", "vdb", "vdc", "vdd", "vde"]}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000b", "event_loop": null, "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-2", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:51"}, "event": "runner_on_ok"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/28-51e73fac-1bb0-42cf-b855-c9576e7589be.json: -------------------------------------------------------------------------------- 1 | {"uuid": "51e73fac-1bb0-42cf-b855-c9576e7589be", "counter": 28, "stdout": "\u001b[0;33mchanged: [con-1] => (item=vdd)\u001b[0m", "start_line": 31, "end_line": 32, "created": "2018-09-13T21:16:54.796367", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "check if disk is free", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=pvcreate --test /dev/vdd, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873414.34-139784854554553/, _ansible_debug=False", "res": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.743414", "_ansible_item_label": "vdd", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", "stdout": " Physical volume \"/dev/vdd\" successfully created.", "changed": true, "delta": "0:00:00.050889", "cmd": ["pvcreate", "--test", "/dev/vdd"], "item": "vdd", "rc": 0, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdd", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [" Physical volume \"/dev/vdd\" successfully created."], "start": "2018-09-13 21:16:54.692525", "_ansible_ignore_errors": true, "_ansible_no_log": false}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-1", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54"}, "event": "runner_item_on_ok"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/30-bd8d3f5b-4401-40fa-a3d1-2a5eaa9c557b.json: -------------------------------------------------------------------------------- 1 | {"uuid": "bd8d3f5b-4401-40fa-a3d1-2a5eaa9c557b", "counter": 30, "stdout": "\u001b[0;33mchanged: [con-2] => (item=vde)\u001b[0m", "start_line": 33, "end_line": 34, "created": "2018-09-13T21:16:55.086639", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "check if disk is free", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=pvcreate --test /dev/vde, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873414.66-206704656338307/, _ansible_debug=False", "res": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated."], "_ansible_item_result": true, "end": "2018-09-13 21:16:55.013852", "_ansible_item_label": "vde", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", "stdout": " Physical volume \"/dev/vde\" successfully created.", "changed": true, "delta": "0:00:00.037357", "cmd": ["pvcreate", "--test", "/dev/vde"], "item": "vde", "rc": 0, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vde", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [" Physical volume \"/dev/vde\" successfully created."], "start": "2018-09-13 21:16:54.976495", "_ansible_ignore_errors": true, "_ansible_no_log": false}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-2", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54"}, "event": "runner_item_on_ok"} -------------------------------------------------------------------------------- /runner_service/services/groups.py: -------------------------------------------------------------------------------- 1 | 2 | from runner_service import (AnsibleInventory, 3 | InventoryGroupExists, 4 | InventoryGroupMissing 5 | ) 6 | from .utils import APIResponse 7 | 8 | import logging 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | def add_group(group_name): 13 | r = APIResponse() 14 | 15 | reserved_group_names = ['all'] 16 | if group_name in reserved_group_names: 17 | r.status, r.msg = "INVALID", \ 18 | "Group name '{}' is a reserved/system group " \ 19 | "name".format(group_name) 20 | return r 21 | 22 | inventory = AnsibleInventory(excl=True) 23 | if inventory.loaded: 24 | try: 25 | inventory.group_add(group_name) 26 | except InventoryGroupExists: 27 | r.status, r.msg = 'OK', 'Group already exists' 28 | else: 29 | r.status, r.msg = 'OK', 'Group {} added'.format(group_name) 30 | 31 | return r 32 | else: 33 | r.status, r.msg = 'LOCKED', 'Unable to lock the inventory file' 34 | return r 35 | 36 | 37 | def remove_group(group_name): 38 | r = APIResponse() 39 | inventory = AnsibleInventory(excl=True) 40 | if inventory.loaded: 41 | try: 42 | inventory.group_remove(group_name) 43 | except InventoryGroupMissing: 44 | r.status, r.msg = 'INVALID', "Group doesn't exist" 45 | else: 46 | r.status, r.msg = 'OK', 'Group {} removed'.format(group_name) 47 | 48 | return r 49 | else: 50 | r.status, r.msg = 'LOCKED', 'Unable to lock the inventory file' 51 | return r 52 | 53 | 54 | def get_groups(): 55 | r = APIResponse() 56 | inventory = AnsibleInventory() 57 | r.status, r.data = 'OK', {"groups": inventory.groups} 58 | return r 59 | 60 | 61 | def get_group_members(group_name): 62 | r = APIResponse() 63 | inventory = AnsibleInventory() 64 | try: 65 | group_hosts = inventory.group_show(group_name) 66 | except InventoryGroupMissing: 67 | r.status, r.msg = 'NOTFOUND', "Group doesn't exist" 68 | else: 69 | r.status, r.data = "OK", {"members": group_hosts} 70 | return r 71 | -------------------------------------------------------------------------------- /runner_service/app.py: -------------------------------------------------------------------------------- 1 | # import os 2 | # import logging 3 | from flask import Flask 4 | from flask_restful import Api 5 | 6 | from .controllers import (ListPlaybooks, 7 | PlaybookState, 8 | StartPlaybook, 9 | StartTaggedPlaybook, 10 | API, 11 | ListEvents, 12 | GetEvent, 13 | ListGroups, 14 | ManageGroups, 15 | Hosts, 16 | HostMgmt, 17 | HostVars, 18 | GroupVars, 19 | HostDetails, 20 | PrometheusMetrics 21 | ) 22 | 23 | from runner_service import configuration 24 | 25 | import logging 26 | logger = logging.getLogger(__name__) 27 | 28 | 29 | def create_app(): 30 | 31 | app = Flask("runner_service") 32 | 33 | # Apply any local configuration to the flask instance 34 | app.config.from_object(configuration.settings) 35 | 36 | api = Api(app) 37 | 38 | api.add_resource(ListPlaybooks, "/api/v1/playbooks") 39 | api.add_resource(StartPlaybook, "/api/v1/playbooks/") 40 | api.add_resource(StartTaggedPlaybook, "/api/v1/playbooks//tags/") # noqa: E501 41 | api.add_resource(PlaybookState, "/api/v1/playbooks/") 42 | 43 | api.add_resource(ListEvents, "/api/v1/jobs//events") 44 | api.add_resource(GetEvent, "/api/v1/jobs//events/") 45 | 46 | api.add_resource(ListGroups, "/api/v1/groups") 47 | api.add_resource(ManageGroups, "/api/v1/groups/") 48 | 49 | api.add_resource(Hosts, "/api/v1/hosts") 50 | api.add_resource(HostDetails, "/api/v1/hosts/") 51 | api.add_resource(HostMgmt, "/api/v1/hosts//groups/") 52 | 53 | api.add_resource(HostVars, "/api/v1/hostvars//groups/") # noqa: E501 54 | api.add_resource(GroupVars, "/api/v1/groupvars/") # noqa: E501 55 | 56 | api.add_resource(API, "/api") 57 | api.add_resource(PrometheusMetrics, "/metrics") 58 | 59 | # push the app object into the API class, so it can walk the 60 | # API endpoints. 61 | API.app = app 62 | 63 | return app 64 | -------------------------------------------------------------------------------- /tests/common.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | from base64 import b64encode 5 | import shutil 6 | 7 | import unittest 8 | from unittest.mock import patch 9 | 10 | sys.path.extend(["../", "./"]) 11 | from ansible_runner_service import main # noqa E402 12 | from runner_service import configuration # noqa 13 | 14 | 15 | def setup_dirs(dir_list=[]): 16 | """Create the sample directory structure for the API to work against""" 17 | root_dir = os.getcwd() 18 | samples = os.path.join(root_dir, 'samples') 19 | 20 | if os.path.exists(samples): 21 | shutil.rmtree(samples) 22 | 23 | os.mkdir(samples) 24 | 25 | for _d in dir_list: 26 | new_dir = os.path.join(samples, _d) 27 | os.mkdir(new_dir) 28 | 29 | 30 | def seed_dirs(seed_list): 31 | 32 | for seed_pair in seed_list: 33 | src, dest = seed_pair 34 | if os.path.isdir(src): 35 | shutil.copytree(src, dest) 36 | else: 37 | shutil.copyfile(src, dest) 38 | 39 | def fake_ssh_client(func): 40 | def wrapper(self, *args, **kwargs): 41 | with patch('runner_service.utils.SSHClient') as MockSSHCLient: 42 | instance = MockSSHCLient.return_value 43 | instance.connect.return_value = True 44 | 45 | func(self, *args, **kwargs) 46 | return wrapper 47 | 48 | class APITestCase(unittest.TestCase): 49 | app = None 50 | 51 | @classmethod 52 | def setUpClass(cls): 53 | """ 54 | Call the main method of the ansible_runner_service script to Start 55 | the daemon normally 56 | """ 57 | 58 | setup_dirs([ 59 | 'env', 60 | 'inventory', 61 | 'project', 62 | 'artifacts' 63 | ]) 64 | 65 | seed_dirs([ 66 | ('./data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d', 67 | './samples/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d'), 68 | ('./data/project/testplaybook.yml', 69 | './samples/project/testplaybook.yml') 70 | ]) 71 | 72 | configuration.init("dev") 73 | cls.config = configuration.settings 74 | cls.app = main(test_mode=True) 75 | 76 | @classmethod 77 | def tearDownClass(cls): 78 | root_dir = os.getcwd() 79 | samples = os.path.join(root_dir, 'samples') 80 | shutil.rmtree(samples) 81 | -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/49-e084d030-cd3d-4c76-a4d3-03d032c4dc8f.json: -------------------------------------------------------------------------------- 1 | {"uuid": "e084d030-cd3d-4c76-a4d3-03d032c4dc8f", "counter": 49, "stdout": "\u001b[0;32mok: [con-2 -> 127.0.0.1] => {\u001b[0m\r\n\u001b[0;32m \"free_disks\": {\u001b[0m\r\n\u001b[0;32m \"con-1\": {\u001b[0m\r\n\u001b[0;32m \"vdd\": {\u001b[0m\r\n\u001b[0;32m \"rotational\": true, \u001b[0m\r\n\u001b[0;32m \"sectors\": 41943040, \u001b[0m\r\n\u001b[0;32m \"sectorsize\": 512, \u001b[0m\r\n\u001b[0;32m \"size_bytes\": 21474836480, \u001b[0m\r\n\u001b[0;32m \"size_txt\": \"20.00 GB\"\u001b[0m\r\n\u001b[0;32m }\u001b[0m\r\n\u001b[0;32m }, \u001b[0m\r\n\u001b[0;32m \"con-2\": {\u001b[0m\r\n\u001b[0;32m \"vde\": {\u001b[0m\r\n\u001b[0;32m \"rotational\": true, \u001b[0m\r\n\u001b[0;32m \"sectors\": 41943040, \u001b[0m\r\n\u001b[0;32m \"sectorsize\": 512, \u001b[0m\r\n\u001b[0;32m \"size_bytes\": 21474836480, \u001b[0m\r\n\u001b[0;32m \"size_txt\": \"20.00 GB\"\u001b[0m\r\n\u001b[0;32m }\u001b[0m\r\n\u001b[0;32m }, \u001b[0m\r\n\u001b[0;32m \"con-3\": {}\u001b[0m\r\n\u001b[0;32m }\u001b[0m\r\n\u001b[0;32m}\u001b[0m", "start_line": 51, "end_line": 74, "created": "2018-09-13T21:16:55.640724", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "RESULTS", "task_args": "var=free_disks", "remote_addr": "con-2", "res": {"free_disks": {"con-2": {"vde": {"size_bytes": 21474836480, "size_txt": "20.00 GB", "sectorsize": 512, "sectors": 41943040, "rotational": true}}, "con-3": {}, "con-1": {"vdd": {"size_bytes": 21474836480, "size_txt": "20.00 GB", "sectorsize": 512, "sectors": 41943040, "rotational": true}}}, "_ansible_no_log": false, "_ansible_delegated_vars": {"ansible_delegated_host": "127.0.0.1", "ansible_host": "127.0.0.1"}, "changed": false, "_ansible_verbose_always": true}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000e", "event_loop": null, "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "debug", "host": "con-2", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:65"}, "event": "runner_on_ok"} -------------------------------------------------------------------------------- /runner_service/static/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; 3 | padding-left: 10px; 4 | padding-right: 10px; 5 | } 6 | 7 | .button { 8 | display: inline-block; 9 | color: white; 10 | padding: 5px; 11 | margin-left:10px; 12 | font-weight: bold; 13 | border-radius: 5px; 14 | } 15 | 16 | .get { 17 | background-color: purple; 18 | } 19 | 20 | .put { 21 | background-color: darkcyan; 22 | } 23 | 24 | .delete { 25 | background-color: darkorange; 26 | } 27 | 28 | .post { 29 | background-color: blue; 30 | } 31 | 32 | #endpoints { 33 | font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; 34 | border-collapse: collapse; 35 | } 36 | 37 | #endpoints tr:nth-child(even){ 38 | background-color: #f2f2f2; 39 | } 40 | 41 | #endpoints th { 42 | cursor: auto; 43 | } 44 | #endpoints tr:hover { 45 | background-color: gainsboro; 46 | cursor: pointer; 47 | } 48 | 49 | #endpoints td { 50 | padding-top: 12px; 51 | padding-bottom: 12px; 52 | padding-left: 5px; 53 | vertical-align: top; 54 | } 55 | 56 | #endpoints th { 57 | padding-top: 12px; 58 | padding-bottom: 12px; 59 | padding-left:5px; 60 | text-align: left; 61 | background-color: steelblue; 62 | color: white; 63 | } 64 | 65 | .logo { 66 | position: absolute; 67 | right:20px; 68 | top:1%; 69 | width: 69px; 70 | height: 85px; 71 | } 72 | 73 | .codeblock { 74 | background-color: darkslategray; 75 | display: block; 76 | color: snow; 77 | padding-top:10px; 78 | padding-bottom: 10px; 79 | padding-left: 10px; 80 | padding-right: 10px; 81 | border-radius:5px; 82 | font-size: .9em; 83 | font-family: "Lucida Console", Monaco, monospace; 84 | } 85 | 86 | .codeblock:hover { 87 | box-shadow: 0 0 8px darkslategray; 88 | } 89 | 90 | .api-text { 91 | font-family: "Lucida Console", Monaco, monospace; 92 | font-size: .8em; 93 | } 94 | .api-table { 95 | width:98%; 96 | padding-left:10px; 97 | } 98 | .api-row-container { 99 | width:100%; 100 | margin-right:10px; 101 | height:auto; 102 | } 103 | 104 | .info { 105 | position:relative; 106 | left:10%; 107 | top:-45px; 108 | display:inline-block; 109 | width:auto; 110 | } 111 | .method-info { 112 | width:100%; 113 | border-bottom: 1px solid silver; 114 | } 115 | -------------------------------------------------------------------------------- /misc/docker/README.md: -------------------------------------------------------------------------------- 1 | # Running ansible-runner-service in a container 2 | 3 | ## WARNING: THIS IS A WORK IN PROGRESS! 4 | 5 | ## Goal: 6 | Provide a container version of the ansible runner service running in 'prod' mode 7 | 8 | The container runs ansible runner service with bind mounts out to the local filesystem 9 | to provide runtime, playbooks and artifacts persistence. This approach allows you to 10 | install an rpm containing the playbooks specific to ceph or gluster, then map these 11 | through to the Container. 12 | 13 | The provided container uses CentOS with Python 3.6 (most of the development work is being done against Fedora, with Python3.6). 14 | 15 | 16 | ## Local Preparation 17 | 1. download the project to your home directory and untar/unzip 18 | 2. create an archive of the project called ansible-runner-service.tar.gz and 19 | store it in the misc/docker directory 20 | 3. set up you local environment to persist the config 21 | ``` 22 | sudo mkdir /etc/ansible-runner-service 23 | sudo mkdir -p /usr/share/ansible-runner-service/{artifacts,env,inventory, project} 24 | ``` 25 | 3.a. If you have selinux enabled you'll need to give the container permissions to these directories 26 | ``` 27 | cd /usr/share 28 | chcon -Rt container_file_t ansible-runner-service 29 | ``` 30 | 4. from the root of the ansible-runner-service directory 31 | ``` 32 | cp {logging,config}.yaml /etc/ansible-runner-service/. 33 | cp -r samples/project/* /usr/share/ansible-runner-service/project 34 | ``` 35 | 36 | ## Building (as root, or use sudo) 37 | 1. from the ansible-runner-service directory 38 | ``` 39 | cd misc/docker 40 | ``` 41 | 2. Build the container 42 | ``` 43 | docker build -f Dockerfile -t runner-service . 44 | ``` 45 | 46 | ## Running the container 47 | ### basic - no persistence (i.e. not much use, just a quick test) 48 | ``` 49 | docker run -d --network=host -p 5001:5001/tcp --name runner-service runner-service 50 | ``` 51 | 52 | ### with persistence 53 | Here's an example of using the container that perists state to the host's filesystem. 54 | ``` 55 | docker run -d --network=host -p 5001:5001/tcp -v /usr/share/ansible-runner-service:/usr/share/ansible-runner-service -v /etc/ansible-runner-service:/etc/ansible-runner-service --name runner-service runner-service 56 | ``` 57 | 58 | Be aware that the container will need access to these bind-mounted locations, so you may need to ensure file and selinux permissions are set correctly. 59 | 60 | 61 | At this point, the container persists the following content; 62 | - ssh keys 63 | - inventory 64 | - playbooks 65 | - artifacts (job/playbook run output) 66 | -------------------------------------------------------------------------------- /samples/project/probe-disks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # 3 | # Playbook to scan a set of hosts and return a dict indexed by host containing 4 | # a list of disks that are unused. Each disk is represented by a dict with the 5 | # following fields; 6 | # 7 | # size_txt (str) e.g 10.0GB 8 | # size_bytes (int) e.g. 21474836480 9 | # sectorsize (int) e.g. 512 10 | # sectors (int) e.g 41943040 11 | # 12 | # example output; 13 | # ok: [con-1 -> 127.0.0.1] => { 14 | # "free_disks": { 15 | # "con-1": { 16 | # "vdd": { 17 | # "rotational": true, 18 | # "sectors": 41943040, 19 | # "sectorsize": 512, 20 | # "size_bytes": 21474836480, 21 | # "size_txt": "20.00 GB" 22 | # } 23 | # }, 24 | 25 | - name: probe hosts for free disks 26 | hosts: 27 | - osds 28 | vars: 29 | free_disks: | 30 | {%- set disk_table = dict() %} 31 | {%- for host in play_hosts %} 32 | {%- set _x = disk_table.__setitem__(host, {}) %} 33 | {%- set _devdata = dict() %} 34 | {%- for disk in hostvars[host].host_disk %} 35 | {%- set _meta = hostvars[host]['ansible_devices'][disk] %} 36 | {%- set _x = _devdata.__setitem__(disk, dict(size_txt=_meta['size'], 37 | rotational=_meta['rotational']|bool, 38 | sectors=_meta['sectors']|int, 39 | sectorsize=_meta['sectorsize']|int, 40 | size_bytes=_meta['sectors']|int * _meta['sectorsize']|int)) %} 41 | {%- endfor %} 42 | {%- set _x = disk_table.__setitem__(host, _devdata) %} 43 | {%- endfor %} 44 | {{ disk_table }} 45 | 46 | gather_facts: true 47 | tasks: 48 | - name: setup 49 | set_fact: 50 | host_disk: [] 51 | - name: Get a list of block devices (excludes loop and child devices) 52 | command: lsblk -n --o NAME --nodeps --exclude 7 53 | register: lsblk_out 54 | - name: check if disk {{ item }} is free 55 | command: pvcreate --test /dev/{{ item }} 56 | ignore_errors: true 57 | register: pv_status 58 | with_items: "{{lsblk_out.stdout_lines}}" 59 | - name: Update hosts freedisk list 60 | set_fact: 61 | host_disk: "{{host_disk + [item.item]}}" 62 | ignore_errors: true 63 | when: item.rc == 0 64 | with_items: "{{ pv_status.results}}" 65 | - name: RESULTS 66 | debug: 67 | var: free_disks 68 | delegate_to: 127.0.0.1 69 | run_once: True 70 | -------------------------------------------------------------------------------- /runner_service/controllers/metrics.py: -------------------------------------------------------------------------------- 1 | 2 | from flask import make_response 3 | 4 | from .base import BaseResource 5 | from ..metrics import PrometheusStats 6 | 7 | 8 | class PrometheusMetrics(BaseResource): 9 | """Gather ansible playbook metrics for Prometheus monitoring""" 10 | 11 | def get(self): 12 | """ 13 | GET 14 | Return the current performance/usage counters in text format 15 | 16 | Example. 17 | ``` 18 | $ curl -k --key ./client.key --cert ./client.crt https://localhost:5001/metrics 19 | #HELP: runner_service_duration_scrape_secs - time taken to gather the data 20 | #TYPE: runner_service_duration_scrape_secs - gauge 21 | runner_service_duration_scrape_secs{hostname="rh460p"} 0 22 | #HELP: runner_service_event_status - event/task states for all playbooks executed since daemon started 23 | #TYPE: runner_service_event_status - count 24 | runner_service_event_status{hostname="rh460p",event_status="ok"} 12 25 | runner_service_event_status{hostname="rh460p",event_status="failed"} 3 26 | runner_service_event_status{hostname="rh460p",event_status="skipped"} 1 27 | runner_service_event_status{hostname="rh460p",event_status="unreachable"} 0 28 | runner_service_event_status{hostname="rh460p",event_status="no_hosts"} 0 29 | runner_service_event_status{hostname="rh460p",event_status="file_diff"} 0 30 | runner_service_event_status{hostname="rh460p",event_status="async_failed"} 0 31 | runner_service_event_status{hostname="rh460p",event_status="async_ok"} 0 32 | runner_service_event_status{hostname="rh460p",event_status="async_poll"} 0 33 | #HELP: runner_service_playbook_count - number of playbooks known to the service 34 | #TYPE: runner_service_playbook_count - gauge 35 | runner_service_playbook_count{hostname="rh460p"} 3 36 | #HELP: runner_service_playbooks_active - number of playbook jobs running 37 | #TYPE: runner_service_playbooks_active - gauge 38 | runner_service_playbooks_active{hostname="rh460p"} 0 39 | #HELP: runner_service_playbooks_status - playbook completion states since daemon started 40 | #TYPE: runner_service_playbooks_status - count 41 | runner_service_playbooks_status{hostname="rh460p",status="successful"} 1 42 | runner_service_playbooks_status{hostname="rh460p",status="failed"} 0 43 | runner_service_playbooks_status{hostname="rh460p",status="canceled"} 0 44 | runner_service_playbooks_status{hostname="rh460p",status="timeout"} 0 45 | ``` 46 | """ 47 | stats = PrometheusStats() 48 | stats.fetch() 49 | 50 | return make_response(stats.formatted, 200) 51 | -------------------------------------------------------------------------------- /Dockerfile.ubi8: -------------------------------------------------------------------------------- 1 | # Builder (Donor) image Centos8 2 | FROM docker.io/library/centos:8 as rpm 3 | 4 | # Build from UBI8 5 | FROM registry.access.redhat.com/ubi8/ubi:latest 6 | 7 | # Ansible Runner Version 8 | ARG ANSIBLE_RUNNER_VERSION="1.4.6" 9 | ARG SERVICE_DIR="/root/ansible-runner-service" 10 | 11 | # RPM package list 12 | ARG RPM_PKGS="\ 13 | bash wget unzip nginx supervisor python3-psutil \ 14 | python3-pexpect python3-daemon bubblewrap gcc \ 15 | bzip2 openssh openssh-clients python2-psutil \ 16 | python3 python3-devel python3-setuptools \ 17 | glibc-locale-source glibc-langpack-en \ 18 | " 19 | # Pip package list 20 | ARG PIP_PKGS="\ 21 | ansible cryptography docutils psutil PyYAML \ 22 | pyOpenSSL flask flask-restful uwsgi netaddr notario \ 23 | " 24 | 25 | # Load CentOS rpm repos 26 | COPY --from=rpm /etc/pki /etc/pki 27 | COPY --from=rpm /etc/os-release /etc/os-release 28 | COPY --from=rpm /etc/yum.repos.d /etc/yum.repos.d 29 | COPY --from=rpm /etc/redhat-release /etc/redhat-release 30 | 31 | # DNF Install Dependencies 32 | RUN set -ex \ 33 | && sed -i 's/enabled=1/enabled=0/g' /etc/yum/pluginconf.d/subscription-manager.conf \ 34 | && dnf update -y -q \ 35 | && dnf install -y -q epel-release \ 36 | && dnf install -y -q ${RPM_PKGS} \ 37 | && dnf clean all \ 38 | && rm -rf /var/cache/yum \ 39 | && echo 40 | 41 | # PIP Install Dependencies 42 | RUN set -ex \ 43 | && /usr/bin/python3 -m \ 44 | pip install ${PIP_PKGS} \ 45 | && /usr/bin/python3 -m \ 46 | pip install --no-cache-dir ansible-runner==${ANSIBLE_RUNNER_VERSION} \ 47 | && echo 48 | 49 | # Prepare folders for shared access and ssh 50 | RUN set -ex \ 51 | && mkdir -p \ 52 | /root/.ssh \ 53 | /etc/ansible-runner-service \ 54 | /usr/share/ansible-runner-service/{artifacts,env,project,inventory,client_cert} \ 55 | && localedef -c -i en_US -f UTF-8 en_US.UTF-8 \ 56 | && echo 57 | 58 | # Install Ansible Runner 59 | WORKDIR /root 60 | COPY ./*.py ${SERVICE_DIR}/ 61 | COPY ./*.yaml ${SERVICE_DIR}/ 62 | COPY ./samples ${SERVICE_DIR}/samples 63 | COPY ./runner_service ${SERVICE_DIR}/runner_service 64 | 65 | # Load configuration files 66 | # Nginx config 67 | COPY misc/nginx/nginx.conf /etc/nginx/ 68 | # Ansible Runner Service nginx virtual server 69 | COPY misc/nginx/ars_site_nginx.conf /etc/nginx/conf.d 70 | # Ansible Runner Service uwsgi settings 71 | COPY misc/nginx/uwsgi.ini ${SERVICE_DIR} 72 | # Supervisor start sequence 73 | COPY misc/nginx/supervisord.conf ${SERVICE_DIR} 74 | 75 | CMD ["/usr/bin/supervisord", "-c", "/root/ansible-runner-service/supervisord.conf"] 76 | -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/18-f087fd62-88cd-4160-b111-d1433f31aae5.json: -------------------------------------------------------------------------------- 1 | {"uuid": "f087fd62-88cd-4160-b111-d1433f31aae5", "counter": 18, "stdout": "\u001b[0;31mfailed: [con-3] (item=vda) => {\"changed\": true, \"cmd\": [\"pvcreate\", \"--test\", \"/dev/vda\"], \"delta\": \"0:00:00.025456\", \"end\": \"2018-09-13 21:16:53.356759\", \"item\": \"vda\", \"msg\": \"non-zero return code\", \"rc\": 5, \"start\": \"2018-09-13 21:16:53.331303\", \"stderr\": \" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vda not found (or ignored by filtering).\", \"stderr_lines\": [\" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\", \" Device /dev/vda not found (or ignored by filtering).\"], \"stdout\": \"\", \"stdout_lines\": []}\u001b[0m", "start_line": 21, "end_line": 22, "created": "2018-09-13T21:16:53.407323", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "check if disk is free", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=pvcreate --test /dev/vda, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873412.96-76086178183064/, _ansible_debug=False", "res": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vda not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.356759", "_ansible_item_label": "vda", "stdout": "", "_ansible_no_log": false, "changed": true, "msg": "non-zero return code", "start": "2018-09-13 21:16:53.331303", "delta": "0:00:00.025456", "cmd": ["pvcreate", "--test", "/dev/vda"], "item": "vda", "rc": 5, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vda", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vda not found (or ignored by filtering).", "_ansible_ignore_errors": true}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-3", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54"}, "event": "runner_item_on_failed"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/19-73a53195-a0e1-4818-addd-780257cb812e.json: -------------------------------------------------------------------------------- 1 | {"uuid": "73a53195-a0e1-4818-addd-780257cb812e", "counter": 19, "stdout": "\u001b[0;31mfailed: [con-1] (item=vda) => {\"changed\": true, \"cmd\": [\"pvcreate\", \"--test\", \"/dev/vda\"], \"delta\": \"0:00:00.025478\", \"end\": \"2018-09-13 21:16:53.405966\", \"item\": \"vda\", \"msg\": \"non-zero return code\", \"rc\": 5, \"start\": \"2018-09-13 21:16:53.380488\", \"stderr\": \" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vda not found (or ignored by filtering).\", \"stderr_lines\": [\" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\", \" Device /dev/vda not found (or ignored by filtering).\"], \"stdout\": \"\", \"stdout_lines\": []}\u001b[0m", "start_line": 22, "end_line": 23, "created": "2018-09-13T21:16:53.461229", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "check if disk is free", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=pvcreate --test /dev/vda, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873413.01-43542906818681/, _ansible_debug=False", "res": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vda not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.405966", "_ansible_item_label": "vda", "stdout": "", "_ansible_no_log": false, "changed": true, "msg": "non-zero return code", "start": "2018-09-13 21:16:53.380488", "delta": "0:00:00.025478", "cmd": ["pvcreate", "--test", "/dev/vda"], "item": "vda", "rc": 5, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vda", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vda not found (or ignored by filtering).", "_ansible_ignore_errors": true}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-1", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54"}, "event": "runner_item_on_failed"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/21-c1c24f3a-f30b-4c25-bb3d-c4207d71ba61.json: -------------------------------------------------------------------------------- 1 | {"uuid": "c1c24f3a-f30b-4c25-bb3d-c4207d71ba61", "counter": 21, "stdout": "\u001b[0;31mfailed: [con-3] (item=vdb) => {\"changed\": true, \"cmd\": [\"pvcreate\", \"--test\", \"/dev/vdb\"], \"delta\": \"0:00:00.024234\", \"end\": \"2018-09-13 21:16:53.803374\", \"item\": \"vdb\", \"msg\": \"non-zero return code\", \"rc\": 5, \"start\": \"2018-09-13 21:16:53.779140\", \"stderr\": \" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vdb not found (or ignored by filtering).\", \"stderr_lines\": [\" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\", \" Device /dev/vdb not found (or ignored by filtering).\"], \"stdout\": \"\", \"stdout_lines\": []}\u001b[0m", "start_line": 24, "end_line": 25, "created": "2018-09-13T21:16:53.850763", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "check if disk is free", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=pvcreate --test /dev/vdb, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873413.41-38829975832644/, _ansible_debug=False", "res": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdb not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.803374", "_ansible_item_label": "vdb", "stdout": "", "_ansible_no_log": false, "changed": true, "msg": "non-zero return code", "start": "2018-09-13 21:16:53.779140", "delta": "0:00:00.024234", "cmd": ["pvcreate", "--test", "/dev/vdb"], "item": "vdb", "rc": 5, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdb", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdb not found (or ignored by filtering).", "_ansible_ignore_errors": true}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-3", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54"}, "event": "runner_item_on_failed"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/23-022145c9-5950-49c0-bf93-f40c8f7e5cf7.json: -------------------------------------------------------------------------------- 1 | {"uuid": "022145c9-5950-49c0-bf93-f40c8f7e5cf7", "counter": 23, "stdout": "\u001b[0;31mfailed: [con-2] (item=vdc) => {\"changed\": true, \"cmd\": [\"pvcreate\", \"--test\", \"/dev/vdc\"], \"delta\": \"0:00:00.032768\", \"end\": \"2018-09-13 21:16:54.178122\", \"item\": \"vdc\", \"msg\": \"non-zero return code\", \"rc\": 5, \"start\": \"2018-09-13 21:16:54.145354\", \"stderr\": \" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vdc not found (or ignored by filtering).\", \"stderr_lines\": [\" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\", \" Device /dev/vdc not found (or ignored by filtering).\"], \"stdout\": \"\", \"stdout_lines\": []}\u001b[0m", "start_line": 26, "end_line": 27, "created": "2018-09-13T21:16:54.237266", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "check if disk is free", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=pvcreate --test /dev/vdc, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873413.81-32602543726064/, _ansible_debug=False", "res": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdc not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.178122", "_ansible_item_label": "vdc", "stdout": "", "_ansible_no_log": false, "changed": true, "msg": "non-zero return code", "start": "2018-09-13 21:16:54.145354", "delta": "0:00:00.032768", "cmd": ["pvcreate", "--test", "/dev/vdc"], "item": "vdc", "rc": 5, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdc", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdc not found (or ignored by filtering).", "_ansible_ignore_errors": true}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-2", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54"}, "event": "runner_item_on_failed"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/24-cb720873-f855-456e-84e6-d261840939b9.json: -------------------------------------------------------------------------------- 1 | {"uuid": "cb720873-f855-456e-84e6-d261840939b9", "counter": 24, "stdout": "\u001b[0;31mfailed: [con-3] (item=vdc) => {\"changed\": true, \"cmd\": [\"pvcreate\", \"--test\", \"/dev/vdc\"], \"delta\": \"0:00:00.021241\", \"end\": \"2018-09-13 21:16:54.223865\", \"item\": \"vdc\", \"msg\": \"non-zero return code\", \"rc\": 5, \"start\": \"2018-09-13 21:16:54.202624\", \"stderr\": \" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vdc not found (or ignored by filtering).\", \"stderr_lines\": [\" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\", \" Device /dev/vdc not found (or ignored by filtering).\"], \"stdout\": \"\", \"stdout_lines\": []}\u001b[0m", "start_line": 27, "end_line": 28, "created": "2018-09-13T21:16:54.288723", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "check if disk is free", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=pvcreate --test /dev/vdc, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873413.85-55344480308124/, _ansible_debug=False", "res": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdc not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.223865", "_ansible_item_label": "vdc", "stdout": "", "_ansible_no_log": false, "changed": true, "msg": "non-zero return code", "start": "2018-09-13 21:16:54.202624", "delta": "0:00:00.021241", "cmd": ["pvcreate", "--test", "/dev/vdc"], "item": "vdc", "rc": 5, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdc", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdc not found (or ignored by filtering).", "_ansible_ignore_errors": true}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-3", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54"}, "event": "runner_item_on_failed"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/26-15cc1b59-5937-46ae-a4be-fff835c85695.json: -------------------------------------------------------------------------------- 1 | {"uuid": "15cc1b59-5937-46ae-a4be-fff835c85695", "counter": 26, "stdout": "\u001b[0;31mfailed: [con-1] (item=vdc) => {\"changed\": true, \"cmd\": [\"pvcreate\", \"--test\", \"/dev/vdc\"], \"delta\": \"0:00:00.028400\", \"end\": \"2018-09-13 21:16:54.281095\", \"item\": \"vdc\", \"msg\": \"non-zero return code\", \"rc\": 5, \"start\": \"2018-09-13 21:16:54.252695\", \"stderr\": \" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vdc not found (or ignored by filtering).\", \"stderr_lines\": [\" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\", \" Device /dev/vdc not found (or ignored by filtering).\"], \"stdout\": \"\", \"stdout_lines\": []}\u001b[0m", "start_line": 29, "end_line": 30, "created": "2018-09-13T21:16:54.340325", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "check if disk is free", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=pvcreate --test /dev/vdc, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873413.9-166890175656185/, _ansible_debug=False", "res": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdc not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.281095", "_ansible_item_label": "vdc", "stdout": "", "_ansible_no_log": false, "changed": true, "msg": "non-zero return code", "start": "2018-09-13 21:16:54.252695", "delta": "0:00:00.028400", "cmd": ["pvcreate", "--test", "/dev/vdc"], "item": "vdc", "rc": 5, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdc", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdc not found (or ignored by filtering).", "_ansible_ignore_errors": true}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-1", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54"}, "event": "runner_item_on_failed"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/17-0a7f687d-4512-4086-bedb-7ee1c78a74dc.json: -------------------------------------------------------------------------------- 1 | {"uuid": "0a7f687d-4512-4086-bedb-7ee1c78a74dc", "counter": 17, "stdout": "\u001b[0;31mfailed: [con-2] (item=vda) => {\"changed\": true, \"cmd\": [\"pvcreate\", \"--test\", \"/dev/vda\"], \"delta\": \"0:00:00.024410\", \"end\": \"2018-09-13 21:16:53.316881\", \"item\": \"vda\", \"msg\": \"non-zero return code\", \"rc\": 5, \"start\": \"2018-09-13 21:16:53.292471\", \"stderr\": \" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vda not found (or ignored by filtering).\", \"stderr_lines\": [\" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\", \" Device /dev/vda not found (or ignored by filtering).\"], \"stdout\": \"\", \"stdout_lines\": []}\u001b[0m", "start_line": 20, "end_line": 21, "created": "2018-09-13T21:16:53.378522", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "check if disk is free", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=pvcreate --test /dev/vda, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873412.92-120379817599106/, _ansible_debug=False", "res": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vda not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.316881", "_ansible_item_label": "vda", "stdout": "", "_ansible_no_log": false, "changed": true, "msg": "non-zero return code", "start": "2018-09-13 21:16:53.292471", "delta": "0:00:00.024410", "cmd": ["pvcreate", "--test", "/dev/vda"], "item": "vda", "rc": 5, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vda", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vda not found (or ignored by filtering).", "_ansible_ignore_errors": true}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-2", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54"}, "event": "runner_item_on_failed"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/20-6d212245-ef3c-48e7-8a2f-3e35513657fc.json: -------------------------------------------------------------------------------- 1 | {"uuid": "6d212245-ef3c-48e7-8a2f-3e35513657fc", "counter": 20, "stdout": "\u001b[0;31mfailed: [con-2] (item=vdb) => {\"changed\": true, \"cmd\": [\"pvcreate\", \"--test\", \"/dev/vdb\"], \"delta\": \"0:00:00.026404\", \"end\": \"2018-09-13 21:16:53.746712\", \"item\": \"vdb\", \"msg\": \"non-zero return code\", \"rc\": 5, \"start\": \"2018-09-13 21:16:53.720308\", \"stderr\": \" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vdb not found (or ignored by filtering).\", \"stderr_lines\": [\" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\", \" Device /dev/vdb not found (or ignored by filtering).\"], \"stdout\": \"\", \"stdout_lines\": []}\u001b[0m", "start_line": 23, "end_line": 24, "created": "2018-09-13T21:16:53.804615", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "check if disk is free", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=pvcreate --test /dev/vdb, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873413.38-168651788105916/, _ansible_debug=False", "res": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdb not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.746712", "_ansible_item_label": "vdb", "stdout": "", "_ansible_no_log": false, "changed": true, "msg": "non-zero return code", "start": "2018-09-13 21:16:53.720308", "delta": "0:00:00.026404", "cmd": ["pvcreate", "--test", "/dev/vdb"], "item": "vdb", "rc": 5, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdb", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdb not found (or ignored by filtering).", "_ansible_ignore_errors": true}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-2", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54"}, "event": "runner_item_on_failed"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/22-af8c13fe-c867-44cf-bd15-b4d5524fbc5c.json: -------------------------------------------------------------------------------- 1 | {"uuid": "af8c13fe-c867-44cf-bd15-b4d5524fbc5c", "counter": 22, "stdout": "\u001b[0;31mfailed: [con-1] (item=vdb) => {\"changed\": true, \"cmd\": [\"pvcreate\", \"--test\", \"/dev/vdb\"], \"delta\": \"0:00:00.025672\", \"end\": \"2018-09-13 21:16:53.844119\", \"item\": \"vdb\", \"msg\": \"non-zero return code\", \"rc\": 5, \"start\": \"2018-09-13 21:16:53.818447\", \"stderr\": \" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vdb not found (or ignored by filtering).\", \"stderr_lines\": [\" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\", \" Device /dev/vdb not found (or ignored by filtering).\"], \"stdout\": \"\", \"stdout_lines\": []}\u001b[0m", "start_line": 25, "end_line": 26, "created": "2018-09-13T21:16:53.896276", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "check if disk is free", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=pvcreate --test /dev/vdb, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873413.46-175359760805298/, _ansible_debug=False", "res": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdb not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.844119", "_ansible_item_label": "vdb", "stdout": "", "_ansible_no_log": false, "changed": true, "msg": "non-zero return code", "start": "2018-09-13 21:16:53.818447", "delta": "0:00:00.025672", "cmd": ["pvcreate", "--test", "/dev/vdb"], "item": "vdb", "rc": 5, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdb", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdb not found (or ignored by filtering).", "_ansible_ignore_errors": true}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-1", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54"}, "event": "runner_item_on_failed"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/27-76765c7c-4dae-4dca-9780-186a05c8a939.json: -------------------------------------------------------------------------------- 1 | {"uuid": "76765c7c-4dae-4dca-9780-186a05c8a939", "counter": 27, "stdout": "\u001b[0;31mfailed: [con-2] (item=vdd) => {\"changed\": true, \"cmd\": [\"pvcreate\", \"--test\", \"/dev/vdd\"], \"delta\": \"0:00:00.029428\", \"end\": \"2018-09-13 21:16:54.586866\", \"item\": \"vdd\", \"msg\": \"non-zero return code\", \"rc\": 5, \"start\": \"2018-09-13 21:16:54.557438\", \"stderr\": \" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vdd not found (or ignored by filtering).\", \"stderr_lines\": [\" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\", \" Device /dev/vdd not found (or ignored by filtering).\"], \"stdout\": \"\", \"stdout_lines\": []}\u001b[0m", "start_line": 30, "end_line": 31, "created": "2018-09-13T21:16:54.655073", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "check if disk is free", "task_args": "_ansible_version=2.6.3, warn=True, _ansible_selinux_special_fs=['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'], _ansible_no_log=False, _ansible_module_name=command, _raw_params=pvcreate --test /dev/vdd, _ansible_verbosity=0, _ansible_keep_remote_files=False, _ansible_syslog_facility=LOG_USER, _ansible_socket=None, _ansible_diff=False, _ansible_remote_tmp=~/.ansible/tmp, _ansible_shell_executable=/bin/sh, _ansible_check_mode=False, _ansible_tmpdir=/root/.ansible/tmp/ansible-tmp-1536873414.24-205700890178291/, _ansible_debug=False", "res": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdd not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.586866", "_ansible_item_label": "vdd", "stdout": "", "_ansible_no_log": false, "changed": true, "msg": "non-zero return code", "start": "2018-09-13 21:16:54.557438", "delta": "0:00:00.029428", "cmd": ["pvcreate", "--test", "/dev/vdd"], "item": "vdd", "rc": 5, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdd", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdd not found (or ignored by filtering).", "_ansible_ignore_errors": true}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "host": "con-2", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54"}, "event": "runner_item_on_failed"} -------------------------------------------------------------------------------- /misc/packaging/ars_source.spec: -------------------------------------------------------------------------------- 1 | %if 0%{?rhel} == 7 2 | # Building on CentoOS 7 would result in ".el7.centos" 3 | %define dist .el7 4 | %endif 5 | 6 | Name: ansible-runner-service 7 | Version: 0.9 8 | Release: 3%{?dist} 9 | Summary: RESTful API for ansible/ansible_runner execution 10 | Source0: https://github.com/pcuzner/%{name}/archive/%{name}-%{version}.tar.gz 11 | Group: Applications/System 12 | License: ASL 2.0 13 | 14 | BuildArch: noarch 15 | 16 | 17 | %description 18 | This package provides the Ansible Runner Service source files. Ansible runner service exposes a REST API interface on top of the functionality provided by ansible and ansible_runner. 19 | 20 | The Ansible Runner Service provided in this packages is intended to be used as uwgsi app exposed by Nginx in a Container. 21 | Dependencies, and configuration tasks must be performed in the container. 22 | 23 | Ansible Runner Service listens on https://localhost:5001 by default for playbook or ansible inventory requests. For developers interested in using the API, all the available endpoints are documented at https://localhost:5001/api. 24 | 25 | In addition to the API endpoints, the daemon also provides a /metrics endpoint for prometheus integration. A sample Grafana dashboard is provided within /usr/share/doc/ansible-runner-service 26 | 27 | %prep 28 | %setup -q -n %{name}-%{version} 29 | 30 | %build 31 | # Disable debuginfo packages 32 | %define _enable_debug_package 0 33 | %define debug_package %{nil} 34 | 35 | %{__python} setup.py build 36 | 37 | %install 38 | %{__python} setup.py install -O1 --skip-build --root %{buildroot} --install-scripts /usr/bin 39 | mkdir -p %{buildroot}%{_unitdir} 40 | install -m 0644 ./misc/systemd/ansible-runner-service.service %{buildroot}%{_unitdir} 41 | mkdir -p %{buildroot}%{_sysconfdir}/ansible-runner-service 42 | install -m 0644 ./config.yaml %{buildroot}%{_sysconfdir}/ansible-runner-service 43 | install -m 0644 ./logging.yaml %{buildroot}%{_sysconfdir}/ansible-runner-service 44 | mkdir -p %{buildroot}%{_prefix}/share/ansible-runner-service/artifacts 45 | mkdir -p %{buildroot}%{_prefix}/share/ansible-runner-service/env 46 | mkdir -p %{buildroot}%{_prefix}/share/ansible-runner-service/inventory 47 | mkdir -p %{buildroot}%{_prefix}/share/ansible-runner-service/project 48 | install -m 0644 ./samples/project/runnertest.yml %{buildroot}%{_prefix}/share/ansible-runner-service/project 49 | mkdir -p %{buildroot}%{_docdir}/ansible-runner-service/dashboards 50 | install -m 0644 ./misc/dashboards/ansible-runner-service-metrics.json %{buildroot}%{_docdir}/ansible-runner-service/dashboards 51 | install -m 0644 ./LICENSE.md %{buildroot}%{_docdir}/ansible-runner-service 52 | 53 | %post 54 | 55 | 56 | %postun 57 | 58 | 59 | %files 60 | %{_bindir}/ansible_runner_service 61 | %{python_sitelib}/* 62 | %{_prefix}/share/ansible-runner-service/* 63 | %{_sysconfdir}/ansible-runner-service/* 64 | %{_unitdir}/ansible-runner-service.service 65 | %{_docdir}/ansible-runner-service/* 66 | 67 | %changelog 68 | * Thu Jun 20 2019 Juan Miguel Olmo 0.9-3 69 | - Initial rpm packaging to use Ansible Runner Service in containers 70 | -------------------------------------------------------------------------------- /runner_service/templates/api.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ansible-runner-service API 6 | 7 | 8 | 9 | 10 | 11 |

ansible-runner-service API Documentation

12 |
13 |

The table below shows the available API endpoints with their associated 14 | methods. Clicking on an endpoint row, expands the row to show related methods, 15 | parameters and, where appropriate, provide examples.

16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for route in routes %} 24 | 25 | 61 | 62 | {% endfor %} 63 |
API RouteDescription
26 | {{ route['route'] }} 27 | 28 | {% autoescape False %} 29 | {{ route['description'] | replace('\n', '
') }} 30 | {% endautoescape %} 31 |
32 | 60 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /misc/nginx/generate_certs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Certificates data, password and placement are customizable in this file 4 | source ./certificates_data.custom 5 | 6 | usage="$(basename "$0") [-h] [-s string] [-c string] -- Generate self signed certificates for server and client 7 | 8 | where: 9 | -h show this help text 10 | -s use as CN parameter for the server certificate 11 | -c use as CN parameter for the client certificate" 12 | 13 | # Get user preferences for CN parameter in server/client certificates 14 | SERVER_CN="*" 15 | CLIENT_CN="*" 16 | 17 | while getopts hc:s: option 18 | do 19 | case "${option}" 20 | in 21 | h) echo "$usage" 22 | exit 23 | ;; 24 | s) SERVER_CN=${OPTARG};; 25 | c) CLIENT_CN=${OPTARG};; 26 | \?) echo "illegal option" 27 | echo "$usage" 28 | exit 29 | ;; 30 | esac 31 | done 32 | 33 | CERT_IDENTITY=$CERT_IDENTITY$SERVER_CN 34 | CERT_IDENTITY_CLIENT=$CERT_IDENTITY_CLIENT$CLIENT_CN 35 | 36 | 37 | # create folders 38 | mkdir -p $BASE_PATH/server 39 | mkdir -p $BASE_PATH/client 40 | 41 | 42 | # CA---------------------------------------------------------------------------- 43 | 44 | echo "Create the CA Key and Certificate for signing Client Certs" 45 | openssl genrsa -des3 -out $BASE_PATH/server/ca.key -passout pass:$CERT_PASSWORD 4096 46 | openssl req -new -x509 -sha256 -days 365 -key $BASE_PATH/server/ca.key -out $BASE_PATH/server/ca.crt -passin pass:$CERT_PASSWORD -subj "$CERT_IDENTITY" 47 | 48 | 49 | # Server ----------------------------------------------------------------------- 50 | 51 | echo "Create the Server Key, CSR, and Certificate" 52 | openssl genrsa -des3 -out $BASE_PATH/server/server.key.org -passout pass:$CERT_PASSWORD 4096 53 | # Remove password (avoid server claiming for it each time it starts) 54 | openssl rsa -in $BASE_PATH/server/server.key.org -out $BASE_PATH/server/server.key -passin pass:$CERT_PASSWORD 55 | # Generate server certificate 56 | openssl req -new -sha256 -key $BASE_PATH/server/server.key -out $BASE_PATH/server/server.csr -passin pass:$CERT_PASSWORD -subj "$CERT_IDENTITY" 57 | 58 | echo "Self-sign the certificate with our CA cert" 59 | openssl x509 -req -sha256 -days 365 -in $BASE_PATH/server/server.csr -CA $BASE_PATH/server/ca.crt -CAkey $BASE_PATH/server/ca.key -set_serial 01 -out $BASE_PATH/server/server.crt -passin pass:$CERT_PASSWORD 60 | 61 | 62 | # Client ----------------------------------------------------------------------- 63 | 64 | echo "Create the Client Key and CSR" 65 | openssl genrsa -des3 -out $BASE_PATH/client/client.key.org -passout pass:$CERT_PASSWORD 4096 66 | # Remove password (avoid https client claiming for it in each request) 67 | openssl rsa -in $BASE_PATH/client/client.key.org -out $BASE_PATH/client/client.key -passin pass:$CERT_PASSWORD 68 | # Generate client certificate 69 | openssl req -new -sha256 -key $BASE_PATH/client/client.key -out $BASE_PATH/client/client.csr -passin pass:$CERT_PASSWORD -subj "$CERT_IDENTITY_CLIENT" 70 | 71 | echo "Sign the client certificate with our CA cert" 72 | openssl x509 -req -sha256 -days 365 -in $BASE_PATH/client/client.csr -CA $BASE_PATH/server/ca.crt -CAkey $BASE_PATH/server/ca.key -CAcreateserial -out $BASE_PATH/client/client.crt -passin pass:$CERT_PASSWORD 73 | -------------------------------------------------------------------------------- /tests/test_api_events.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | import json 4 | import logging 5 | import unittest 6 | 7 | sys.path.extend(["../", "./"]) 8 | from common import APITestCase # noqa 9 | 10 | 11 | # turn of normal logging that the ansible_runner_service will generate 12 | nh = logging.NullHandler() 13 | r = logging.getLogger() 14 | r.addHandler(nh) 15 | 16 | 17 | class TestJobEvents(APITestCase): 18 | 19 | def test_list_job_events(self): 20 | """- list events from the sample playbook run""" 21 | response = self.app.get('api/v1/jobs/53b955f2-b79a-11e8-8be9-c85b7671906d/events') # noqa 22 | 23 | self.assertEqual(response.status_code, 24 | 200) 25 | payload = json.loads(response.data) 26 | self.assertEqual(payload['data']['total_events'], 27 | 49) 28 | 29 | def test_list_invalid_job(self): 30 | """- list events for a playbook run that doesn't exist - error 404""" 31 | response = self.app.get("api/v1/jobs/93b955f2-b79a-11e8-8be9-c85b76719093/events") # noqa 32 | 33 | self.assertEqual(response.status_code, 34 | 404) 35 | 36 | def test_fetch_single_event(self): 37 | """- fetch a single event by event uuid""" 38 | response = self.app.get("api/v1/jobs/53b955f2-b79a-11e8-8be9-c85b7671906d/events/49-e084d030-cd3d-4c76-a4d3-03d032c4dc8f") # noqa 39 | 40 | self.assertEqual(response.status_code, 41 | 200) 42 | self.assertEqual(response.headers['Content-Type'], 43 | 'application/json') 44 | self.assertIn("event_data", json.loads(response.data)['data']) 45 | 46 | def test_fetch_invalid_event(self): 47 | """- attempt to fetch an invalid event - error 404""" 48 | response = self.app.get("api/v1/jobs/53b955f2-b79a-11e8-8be9-c85b7671906d/events/49-9384d030-cd3d-4c76-a4d3-03d032c4dc93") # noqa 49 | 50 | self.assertEqual(response.status_code, 51 | 404) 52 | 53 | def test_get_event_with_filter(self): 54 | """- use filter to find matching events in a playbook run""" 55 | response = self.app.get("api/v1/jobs/53b955f2-b79a-11e8-8be9-c85b7671906d/events?task=RESULTS") # noqa 56 | 57 | self.assertEqual(response.status_code, 58 | 200) 59 | self.assertEqual(response.headers['Content-Type'], 60 | 'application/json') 61 | 62 | payload = json.loads(response.data) 63 | self.assertIn("events", payload['data']) 64 | self.assertEqual(payload['data']['total_events'], 65 | 1) 66 | self.assertEqual(payload['data']['events']['49-e084d030-cd3d-4c76-a4d3-03d032c4dc8f']['task'], # noqa 67 | "RESULTS") 68 | 69 | def test_get_event_with_invalid_filter(self): 70 | """- use filter that doesn't match any event""" 71 | response = self.app.get("api/v1/jobs/53b955f2-b79a-11e8-8be9-c85b7671906d/events?task=MISSING") # noqa 72 | 73 | self.assertEqual(response.status_code, 74 | 200) 75 | payload = json.loads(response.data) 76 | self.assertIn("events", payload['data']) 77 | self.assertEqual(payload['data']['total_events'], 78 | 0) 79 | 80 | 81 | if __name__ == "__main__": 82 | 83 | unittest.main(verbosity=2) 84 | -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/25-281e4850-0212-4685-9f1e-d7bbb703f283.json: -------------------------------------------------------------------------------- 1 | {"uuid": "281e4850-0212-4685-9f1e-d7bbb703f283", "counter": 25, "stdout": "\u001b[0;36m...ignoring\u001b[0m", "start_line": 28, "end_line": 29, "created": "2018-09-13T21:16:54.291841", "pid": 26662, "event_data": {"play": "probe hosts for free disks", "event_loop": "{{lsblk_out.stdout_lines}}", "task_args": "_raw_params=pvcreate --test /dev/{{ item }}", "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "ignore_errors": true, "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54", "play_pattern": "osds", "task": "check if disk is free", "remote_addr": "con-3", "res": {"msg": "All items completed", "changed": true, "results": [{"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vda not found (or ignored by filtering)."], "cmd": ["pvcreate", "--test", "/dev/vda"], "end": "2018-09-13 21:16:53.356759", "_ansible_no_log": false, "stdout": "", "failed": true, "_ansible_item_result": true, "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vda", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "item": "vda", "delta": "0:00:00.025456", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vda not found (or ignored by filtering).", "rc": 5, "msg": "non-zero return code", "stdout_lines": [], "start": "2018-09-13 21:16:53.331303", "_ansible_item_label": "vda"}, {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdb not found (or ignored by filtering)."], "cmd": ["pvcreate", "--test", "/dev/vdb"], "end": "2018-09-13 21:16:53.803374", "_ansible_no_log": false, "stdout": "", "failed": true, "_ansible_item_result": true, "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdb", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "item": "vdb", "delta": "0:00:00.024234", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdb not found (or ignored by filtering).", "rc": 5, "msg": "non-zero return code", "stdout_lines": [], "start": "2018-09-13 21:16:53.779140", "_ansible_item_label": "vdb"}, {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdc not found (or ignored by filtering)."], "cmd": ["pvcreate", "--test", "/dev/vdc"], "end": "2018-09-13 21:16:54.223865", "_ansible_no_log": false, "stdout": "", "failed": true, "_ansible_item_result": true, "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdc", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "item": "vdc", "delta": "0:00:00.021241", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdc not found (or ignored by filtering).", "rc": 5, "msg": "non-zero return code", "stdout_lines": [], "start": "2018-09-13 21:16:54.202624", "_ansible_item_label": "vdc"}]}, "host": "con-3"}, "event": "runner_on_failed"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/41-097855c7-fc5e-41e4-8968-4e2107d3fc5f.json: -------------------------------------------------------------------------------- 1 | {"uuid": "097855c7-fc5e-41e4-8968-4e2107d3fc5f", "counter": 41, "stdout": "\u001b[0;32mok: [con-2] => (item={'_ansible_parsed': True, 'stderr_lines': [u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.'], '_ansible_item_result': True, u'end': u'2018-09-13 21:16:55.013852', '_ansible_no_log': False, u'stdout': u' Physical volume \"/dev/vde\" successfully created.', u'cmd': [u'pvcreate', u'--test', u'/dev/vde'], u'rc': 0, 'item': u'vde', u'delta': u'0:00:00.037357', '_ansible_item_label': u'vde', u'stderr': u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.', u'changed': True, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': False, u'_raw_params': u'pvcreate --test /dev/vde', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin': None}}, 'stdout_lines': [u' Physical volume \"/dev/vde\" successfully created.'], u'start': u'2018-09-13 21:16:54.976495', '_ansible_ignore_errors': True, 'failed': False})\u001b[0m", "start_line": 44, "end_line": 45, "created": "2018-09-13T21:16:55.286012", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Update hosts freedisk list", "task_args": "host_disk=[u'vde']", "res": {"changed": false, "_ansible_no_log": false, "_ansible_facts_cacheable": false, "_ansible_item_result": true, "item": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated."], "_ansible_item_result": true, "end": "2018-09-13 21:16:55.013852", "_ansible_no_log": false, "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", "stdout": " Physical volume \"/dev/vde\" successfully created.", "cmd": ["pvcreate", "--test", "/dev/vde"], "rc": 0, "failed": false, "delta": "0:00:00.037357", "item": "vde", "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vde", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [" Physical volume \"/dev/vde\" successfully created."], "start": "2018-09-13 21:16:54.976495", "_ansible_ignore_errors": true, "_ansible_item_label": "vde"}, "ansible_facts": {"host_disk": ["vde"]}, "_ansible_ignore_errors": true, "_ansible_item_label": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated."], "_ansible_item_result": true, "end": "2018-09-13 21:16:55.013852", "_ansible_no_log": false, "stdout": " Physical volume \"/dev/vde\" successfully created.", "cmd": ["pvcreate", "--test", "/dev/vde"], "rc": 0, "item": "vde", "delta": "0:00:00.037357", "_ansible_item_label": "vde", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", "changed": true, "invocation": {"module_args": {"warn": true, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vde", "removes": null, "argv": null, "creates": null, "chdir": null, "stdin": null}}, "stdout_lines": [" Physical volume \"/dev/vde\" successfully created."], "start": "2018-09-13 21:16:54.976495", "_ansible_ignore_errors": true, "failed": false}}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000d", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-2", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:59"}, "event": "runner_item_on_ok"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/46-6845c18a-780c-46b7-820a-6bab972c67e0.json: -------------------------------------------------------------------------------- 1 | {"uuid": "6845c18a-780c-46b7-820a-6bab972c67e0", "counter": 46, "stdout": "\u001b[0;32mok: [con-1] => (item={'_ansible_parsed': True, 'stderr_lines': [u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.'], '_ansible_item_result': True, u'end': u'2018-09-13 21:16:54.743414', '_ansible_no_log': False, u'stdout': u' Physical volume \"/dev/vdd\" successfully created.', u'cmd': [u'pvcreate', u'--test', u'/dev/vdd'], u'rc': 0, 'item': u'vdd', u'delta': u'0:00:00.050889', '_ansible_item_label': u'vdd', u'stderr': u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.', u'changed': True, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': False, u'_raw_params': u'pvcreate --test /dev/vdd', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin': None}}, 'stdout_lines': [u' Physical volume \"/dev/vdd\" successfully created.'], u'start': u'2018-09-13 21:16:54.692525', '_ansible_ignore_errors': True, 'failed': False})\u001b[0m", "start_line": 48, "end_line": 49, "created": "2018-09-13T21:16:55.350601", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Update hosts freedisk list", "task_args": "host_disk=[u'vdd']", "res": {"changed": false, "_ansible_no_log": false, "_ansible_facts_cacheable": false, "_ansible_item_result": true, "item": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.743414", "_ansible_no_log": false, "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", "stdout": " Physical volume \"/dev/vdd\" successfully created.", "cmd": ["pvcreate", "--test", "/dev/vdd"], "rc": 0, "failed": false, "delta": "0:00:00.050889", "item": "vdd", "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdd", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [" Physical volume \"/dev/vdd\" successfully created."], "start": "2018-09-13 21:16:54.692525", "_ansible_ignore_errors": true, "_ansible_item_label": "vdd"}, "ansible_facts": {"host_disk": ["vdd"]}, "_ansible_ignore_errors": true, "_ansible_item_label": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.743414", "_ansible_no_log": false, "stdout": " Physical volume \"/dev/vdd\" successfully created.", "cmd": ["pvcreate", "--test", "/dev/vdd"], "rc": 0, "item": "vdd", "delta": "0:00:00.050889", "_ansible_item_label": "vdd", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", "changed": true, "invocation": {"module_args": {"warn": true, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdd", "removes": null, "argv": null, "creates": null, "chdir": null, "stdin": null}}, "stdout_lines": [" Physical volume \"/dev/vdd\" successfully created."], "start": "2018-09-13 21:16:54.692525", "_ansible_ignore_errors": true, "failed": false}}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000d", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-1", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:59"}, "event": "runner_item_on_ok"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/33-c67654a2-01c8-46dc-ac30-9f24a4c3a536.json: -------------------------------------------------------------------------------- 1 | {"uuid": "c67654a2-01c8-46dc-ac30-9f24a4c3a536", "counter": 33, "stdout": "\u001b[0;36mskipping: [con-2] => (item={'_ansible_parsed': True, 'stderr_lines': [u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.', u' Device /dev/vda not found (or ignored by filtering).'], '_ansible_item_result': True, u'end': u'2018-09-13 21:16:53.316881', '_ansible_no_log': False, u'stdout': u'', 'item': u'vda', u'cmd': [u'pvcreate', u'--test', u'/dev/vda'], u'rc': 5, u'failed': True, u'delta': u'0:00:00.024410', '_ansible_item_label': u'vda', u'stderr': u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vda not found (or ignored by filtering).', u'changed': True, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': False, u'_raw_params': u'pvcreate --test /dev/vda', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin': None}}, 'stdout_lines': [], u'start': u'2018-09-13 21:16:53.292471', u'msg': u'non-zero return code'}) \u001b[0m", "start_line": 37, "end_line": 38, "created": "2018-09-13T21:16:55.205320", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Update hosts freedisk list", "task_args": "host_disk={{host_disk + [item.item]}}", "res": {"_ansible_no_log": false, "skip_reason": "Conditional result was False", "_ansible_item_result": true, "item": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vda not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.316881", "_ansible_no_log": false, "stdout": "", "failed": true, "cmd": ["pvcreate", "--test", "/dev/vda"], "msg": "non-zero return code", "rc": 5, "start": "2018-09-13 21:16:53.292471", "delta": "0:00:00.024410", "item": "vda", "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vda", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vda not found (or ignored by filtering).", "_ansible_item_label": "vda"}, "changed": false, "_ansible_ignore_errors": true, "_ansible_item_label": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vda not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.316881", "_ansible_no_log": false, "stdout": "", "item": "vda", "cmd": ["pvcreate", "--test", "/dev/vda"], "rc": 5, "failed": true, "delta": "0:00:00.024410", "_ansible_item_label": "vda", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vda not found (or ignored by filtering).", "changed": true, "invocation": {"module_args": {"warn": true, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vda", "removes": null, "argv": null, "creates": null, "chdir": null, "stdin": null}}, "stdout_lines": [], "start": "2018-09-13 21:16:53.292471", "msg": "non-zero return code"}}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000d", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-2", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:59"}, "event": "runner_item_on_skipped"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/34-46848398-717c-480c-b7ec-904304ee200e.json: -------------------------------------------------------------------------------- 1 | {"uuid": "46848398-717c-480c-b7ec-904304ee200e", "counter": 34, "stdout": "\u001b[0;36mskipping: [con-2] => (item={'_ansible_parsed': True, 'stderr_lines': [u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.', u' Device /dev/vdb not found (or ignored by filtering).'], '_ansible_item_result': True, u'end': u'2018-09-13 21:16:53.746712', '_ansible_no_log': False, u'stdout': u'', 'item': u'vdb', u'cmd': [u'pvcreate', u'--test', u'/dev/vdb'], u'rc': 5, u'failed': True, u'delta': u'0:00:00.026404', '_ansible_item_label': u'vdb', u'stderr': u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vdb not found (or ignored by filtering).', u'changed': True, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': False, u'_raw_params': u'pvcreate --test /dev/vdb', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin': None}}, 'stdout_lines': [], u'start': u'2018-09-13 21:16:53.720308', u'msg': u'non-zero return code'}) \u001b[0m", "start_line": 38, "end_line": 39, "created": "2018-09-13T21:16:55.208136", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Update hosts freedisk list", "task_args": "host_disk={{host_disk + [item.item]}}", "res": {"_ansible_no_log": false, "skip_reason": "Conditional result was False", "_ansible_item_result": true, "item": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdb not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.746712", "_ansible_no_log": false, "stdout": "", "failed": true, "cmd": ["pvcreate", "--test", "/dev/vdb"], "msg": "non-zero return code", "rc": 5, "start": "2018-09-13 21:16:53.720308", "delta": "0:00:00.026404", "item": "vdb", "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdb", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdb not found (or ignored by filtering).", "_ansible_item_label": "vdb"}, "changed": false, "_ansible_ignore_errors": true, "_ansible_item_label": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdb not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.746712", "_ansible_no_log": false, "stdout": "", "item": "vdb", "cmd": ["pvcreate", "--test", "/dev/vdb"], "rc": 5, "failed": true, "delta": "0:00:00.026404", "_ansible_item_label": "vdb", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdb not found (or ignored by filtering).", "changed": true, "invocation": {"module_args": {"warn": true, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdb", "removes": null, "argv": null, "creates": null, "chdir": null, "stdin": null}}, "stdout_lines": [], "start": "2018-09-13 21:16:53.720308", "msg": "non-zero return code"}}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000d", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-2", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:59"}, "event": "runner_item_on_skipped"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/35-4eedda69-f0a0-4a00-929d-897a1ce4d29d.json: -------------------------------------------------------------------------------- 1 | {"uuid": "4eedda69-f0a0-4a00-929d-897a1ce4d29d", "counter": 35, "stdout": "\u001b[0;36mskipping: [con-2] => (item={'_ansible_parsed': True, 'stderr_lines': [u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.', u' Device /dev/vdc not found (or ignored by filtering).'], '_ansible_item_result': True, u'end': u'2018-09-13 21:16:54.178122', '_ansible_no_log': False, u'stdout': u'', 'item': u'vdc', u'cmd': [u'pvcreate', u'--test', u'/dev/vdc'], u'rc': 5, u'failed': True, u'delta': u'0:00:00.032768', '_ansible_item_label': u'vdc', u'stderr': u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vdc not found (or ignored by filtering).', u'changed': True, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': False, u'_raw_params': u'pvcreate --test /dev/vdc', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin': None}}, 'stdout_lines': [], u'start': u'2018-09-13 21:16:54.145354', u'msg': u'non-zero return code'}) \u001b[0m", "start_line": 39, "end_line": 40, "created": "2018-09-13T21:16:55.274604", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Update hosts freedisk list", "task_args": "host_disk={{host_disk + [item.item]}}", "res": {"_ansible_no_log": false, "skip_reason": "Conditional result was False", "_ansible_item_result": true, "item": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdc not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.178122", "_ansible_no_log": false, "stdout": "", "failed": true, "cmd": ["pvcreate", "--test", "/dev/vdc"], "msg": "non-zero return code", "rc": 5, "start": "2018-09-13 21:16:54.145354", "delta": "0:00:00.032768", "item": "vdc", "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdc", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdc not found (or ignored by filtering).", "_ansible_item_label": "vdc"}, "changed": false, "_ansible_ignore_errors": true, "_ansible_item_label": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdc not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.178122", "_ansible_no_log": false, "stdout": "", "item": "vdc", "cmd": ["pvcreate", "--test", "/dev/vdc"], "rc": 5, "failed": true, "delta": "0:00:00.032768", "_ansible_item_label": "vdc", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdc not found (or ignored by filtering).", "changed": true, "invocation": {"module_args": {"warn": true, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdc", "removes": null, "argv": null, "creates": null, "chdir": null, "stdin": null}}, "stdout_lines": [], "start": "2018-09-13 21:16:54.145354", "msg": "non-zero return code"}}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000d", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-2", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:59"}, "event": "runner_item_on_skipped"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/36-fe767c67-c117-4a33-98db-5d6e22365400.json: -------------------------------------------------------------------------------- 1 | {"uuid": "fe767c67-c117-4a33-98db-5d6e22365400", "counter": 36, "stdout": "\u001b[0;36mskipping: [con-2] => (item={'_ansible_parsed': True, 'stderr_lines': [u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.', u' Device /dev/vdd not found (or ignored by filtering).'], '_ansible_item_result': True, u'end': u'2018-09-13 21:16:54.586866', '_ansible_no_log': False, u'stdout': u'', 'item': u'vdd', u'cmd': [u'pvcreate', u'--test', u'/dev/vdd'], u'rc': 5, u'failed': True, u'delta': u'0:00:00.029428', '_ansible_item_label': u'vdd', u'stderr': u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vdd not found (or ignored by filtering).', u'changed': True, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': False, u'_raw_params': u'pvcreate --test /dev/vdd', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin': None}}, 'stdout_lines': [], u'start': u'2018-09-13 21:16:54.557438', u'msg': u'non-zero return code'}) \u001b[0m", "start_line": 40, "end_line": 41, "created": "2018-09-13T21:16:55.276777", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Update hosts freedisk list", "task_args": "host_disk={{host_disk + [item.item]}}", "res": {"_ansible_no_log": false, "skip_reason": "Conditional result was False", "_ansible_item_result": true, "item": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdd not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.586866", "_ansible_no_log": false, "stdout": "", "failed": true, "cmd": ["pvcreate", "--test", "/dev/vdd"], "msg": "non-zero return code", "rc": 5, "start": "2018-09-13 21:16:54.557438", "delta": "0:00:00.029428", "item": "vdd", "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdd", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdd not found (or ignored by filtering).", "_ansible_item_label": "vdd"}, "changed": false, "_ansible_ignore_errors": true, "_ansible_item_label": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdd not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.586866", "_ansible_no_log": false, "stdout": "", "item": "vdd", "cmd": ["pvcreate", "--test", "/dev/vdd"], "rc": 5, "failed": true, "delta": "0:00:00.029428", "_ansible_item_label": "vdd", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdd not found (or ignored by filtering).", "changed": true, "invocation": {"module_args": {"warn": true, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdd", "removes": null, "argv": null, "creates": null, "chdir": null, "stdin": null}}, "stdout_lines": [], "start": "2018-09-13 21:16:54.557438", "msg": "non-zero return code"}}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000d", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-2", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:59"}, "event": "runner_item_on_skipped"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/37-7ac43b8d-96c7-4e49-ad21-f2399db715c4.json: -------------------------------------------------------------------------------- 1 | {"uuid": "7ac43b8d-96c7-4e49-ad21-f2399db715c4", "counter": 37, "stdout": "\u001b[0;36mskipping: [con-3] => (item={'_ansible_parsed': True, 'stderr_lines': [u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.', u' Device /dev/vda not found (or ignored by filtering).'], '_ansible_item_result': True, u'end': u'2018-09-13 21:16:53.356759', '_ansible_no_log': False, u'stdout': u'', 'item': u'vda', u'cmd': [u'pvcreate', u'--test', u'/dev/vda'], u'rc': 5, u'failed': True, u'delta': u'0:00:00.025456', '_ansible_item_label': u'vda', u'stderr': u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vda not found (or ignored by filtering).', u'changed': True, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': False, u'_raw_params': u'pvcreate --test /dev/vda', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin': None}}, 'stdout_lines': [], u'start': u'2018-09-13 21:16:53.331303', u'msg': u'non-zero return code'}) \u001b[0m", "start_line": 41, "end_line": 42, "created": "2018-09-13T21:16:55.278246", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Update hosts freedisk list", "task_args": "host_disk={{host_disk + [item.item]}}", "res": {"_ansible_no_log": false, "skip_reason": "Conditional result was False", "_ansible_item_result": true, "item": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vda not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.356759", "_ansible_no_log": false, "stdout": "", "failed": true, "cmd": ["pvcreate", "--test", "/dev/vda"], "msg": "non-zero return code", "rc": 5, "start": "2018-09-13 21:16:53.331303", "delta": "0:00:00.025456", "item": "vda", "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vda", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vda not found (or ignored by filtering).", "_ansible_item_label": "vda"}, "changed": false, "_ansible_ignore_errors": true, "_ansible_item_label": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vda not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.356759", "_ansible_no_log": false, "stdout": "", "item": "vda", "cmd": ["pvcreate", "--test", "/dev/vda"], "rc": 5, "failed": true, "delta": "0:00:00.025456", "_ansible_item_label": "vda", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vda not found (or ignored by filtering).", "changed": true, "invocation": {"module_args": {"warn": true, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vda", "removes": null, "argv": null, "creates": null, "chdir": null, "stdin": null}}, "stdout_lines": [], "start": "2018-09-13 21:16:53.331303", "msg": "non-zero return code"}}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000d", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-3", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:59"}, "event": "runner_item_on_skipped"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/38-804e4887-26f2-4a2d-8828-bf27a2f161df.json: -------------------------------------------------------------------------------- 1 | {"uuid": "804e4887-26f2-4a2d-8828-bf27a2f161df", "counter": 38, "stdout": "\u001b[0;36mskipping: [con-3] => (item={'_ansible_parsed': True, 'stderr_lines': [u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.', u' Device /dev/vdb not found (or ignored by filtering).'], '_ansible_item_result': True, u'end': u'2018-09-13 21:16:53.803374', '_ansible_no_log': False, u'stdout': u'', 'item': u'vdb', u'cmd': [u'pvcreate', u'--test', u'/dev/vdb'], u'rc': 5, u'failed': True, u'delta': u'0:00:00.024234', '_ansible_item_label': u'vdb', u'stderr': u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vdb not found (or ignored by filtering).', u'changed': True, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': False, u'_raw_params': u'pvcreate --test /dev/vdb', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin': None}}, 'stdout_lines': [], u'start': u'2018-09-13 21:16:53.779140', u'msg': u'non-zero return code'}) \u001b[0m", "start_line": 42, "end_line": 43, "created": "2018-09-13T21:16:55.279565", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Update hosts freedisk list", "task_args": "host_disk={{host_disk + [item.item]}}", "res": {"_ansible_no_log": false, "skip_reason": "Conditional result was False", "_ansible_item_result": true, "item": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdb not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.803374", "_ansible_no_log": false, "stdout": "", "failed": true, "cmd": ["pvcreate", "--test", "/dev/vdb"], "msg": "non-zero return code", "rc": 5, "start": "2018-09-13 21:16:53.779140", "delta": "0:00:00.024234", "item": "vdb", "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdb", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdb not found (or ignored by filtering).", "_ansible_item_label": "vdb"}, "changed": false, "_ansible_ignore_errors": true, "_ansible_item_label": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdb not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.803374", "_ansible_no_log": false, "stdout": "", "item": "vdb", "cmd": ["pvcreate", "--test", "/dev/vdb"], "rc": 5, "failed": true, "delta": "0:00:00.024234", "_ansible_item_label": "vdb", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdb not found (or ignored by filtering).", "changed": true, "invocation": {"module_args": {"warn": true, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdb", "removes": null, "argv": null, "creates": null, "chdir": null, "stdin": null}}, "stdout_lines": [], "start": "2018-09-13 21:16:53.779140", "msg": "non-zero return code"}}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000d", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-3", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:59"}, "event": "runner_item_on_skipped"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/39-d806fd8d-ead2-4731-a820-1a360b3a2cd2.json: -------------------------------------------------------------------------------- 1 | {"uuid": "d806fd8d-ead2-4731-a820-1a360b3a2cd2", "counter": 39, "stdout": "\u001b[0;36mskipping: [con-3] => (item={'_ansible_parsed': True, 'stderr_lines': [u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.', u' Device /dev/vdc not found (or ignored by filtering).'], '_ansible_item_result': True, u'end': u'2018-09-13 21:16:54.223865', '_ansible_no_log': False, u'stdout': u'', 'item': u'vdc', u'cmd': [u'pvcreate', u'--test', u'/dev/vdc'], u'rc': 5, u'failed': True, u'delta': u'0:00:00.021241', '_ansible_item_label': u'vdc', u'stderr': u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vdc not found (or ignored by filtering).', u'changed': True, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': False, u'_raw_params': u'pvcreate --test /dev/vdc', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin': None}}, 'stdout_lines': [], u'start': u'2018-09-13 21:16:54.202624', u'msg': u'non-zero return code'}) \u001b[0m", "start_line": 43, "end_line": 44, "created": "2018-09-13T21:16:55.281518", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Update hosts freedisk list", "task_args": "host_disk={{host_disk + [item.item]}}", "res": {"_ansible_no_log": false, "skip_reason": "Conditional result was False", "_ansible_item_result": true, "item": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdc not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.223865", "_ansible_no_log": false, "stdout": "", "failed": true, "cmd": ["pvcreate", "--test", "/dev/vdc"], "msg": "non-zero return code", "rc": 5, "start": "2018-09-13 21:16:54.202624", "delta": "0:00:00.021241", "item": "vdc", "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdc", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdc not found (or ignored by filtering).", "_ansible_item_label": "vdc"}, "changed": false, "_ansible_ignore_errors": true, "_ansible_item_label": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdc not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.223865", "_ansible_no_log": false, "stdout": "", "item": "vdc", "cmd": ["pvcreate", "--test", "/dev/vdc"], "rc": 5, "failed": true, "delta": "0:00:00.021241", "_ansible_item_label": "vdc", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdc not found (or ignored by filtering).", "changed": true, "invocation": {"module_args": {"warn": true, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdc", "removes": null, "argv": null, "creates": null, "chdir": null, "stdin": null}}, "stdout_lines": [], "start": "2018-09-13 21:16:54.202624", "msg": "non-zero return code"}}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000d", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-3", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:59"}, "event": "runner_item_on_skipped"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/43-f2700669-4ddc-4189-b610-227af206939a.json: -------------------------------------------------------------------------------- 1 | {"uuid": "f2700669-4ddc-4189-b610-227af206939a", "counter": 43, "stdout": "\u001b[0;36mskipping: [con-1] => (item={'_ansible_parsed': True, 'stderr_lines': [u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.', u' Device /dev/vda not found (or ignored by filtering).'], '_ansible_item_result': True, u'end': u'2018-09-13 21:16:53.405966', '_ansible_no_log': False, u'stdout': u'', 'item': u'vda', u'cmd': [u'pvcreate', u'--test', u'/dev/vda'], u'rc': 5, u'failed': True, u'delta': u'0:00:00.025478', '_ansible_item_label': u'vda', u'stderr': u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vda not found (or ignored by filtering).', u'changed': True, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': False, u'_raw_params': u'pvcreate --test /dev/vda', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin': None}}, 'stdout_lines': [], u'start': u'2018-09-13 21:16:53.380488', u'msg': u'non-zero return code'}) \u001b[0m", "start_line": 45, "end_line": 46, "created": "2018-09-13T21:16:55.302978", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Update hosts freedisk list", "task_args": "host_disk={{host_disk + [item.item]}}", "res": {"_ansible_no_log": false, "skip_reason": "Conditional result was False", "_ansible_item_result": true, "item": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vda not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.405966", "_ansible_no_log": false, "stdout": "", "failed": true, "cmd": ["pvcreate", "--test", "/dev/vda"], "msg": "non-zero return code", "rc": 5, "start": "2018-09-13 21:16:53.380488", "delta": "0:00:00.025478", "item": "vda", "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vda", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vda not found (or ignored by filtering).", "_ansible_item_label": "vda"}, "changed": false, "_ansible_ignore_errors": true, "_ansible_item_label": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vda not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.405966", "_ansible_no_log": false, "stdout": "", "item": "vda", "cmd": ["pvcreate", "--test", "/dev/vda"], "rc": 5, "failed": true, "delta": "0:00:00.025478", "_ansible_item_label": "vda", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vda not found (or ignored by filtering).", "changed": true, "invocation": {"module_args": {"warn": true, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vda", "removes": null, "argv": null, "creates": null, "chdir": null, "stdin": null}}, "stdout_lines": [], "start": "2018-09-13 21:16:53.380488", "msg": "non-zero return code"}}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000d", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-1", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:59"}, "event": "runner_item_on_skipped"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/44-ce7a38e6-e02c-4236-93e1-84629a9bfe06.json: -------------------------------------------------------------------------------- 1 | {"uuid": "ce7a38e6-e02c-4236-93e1-84629a9bfe06", "counter": 44, "stdout": "\u001b[0;36mskipping: [con-1] => (item={'_ansible_parsed': True, 'stderr_lines': [u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.', u' Device /dev/vdb not found (or ignored by filtering).'], '_ansible_item_result': True, u'end': u'2018-09-13 21:16:53.844119', '_ansible_no_log': False, u'stdout': u'', 'item': u'vdb', u'cmd': [u'pvcreate', u'--test', u'/dev/vdb'], u'rc': 5, u'failed': True, u'delta': u'0:00:00.025672', '_ansible_item_label': u'vdb', u'stderr': u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vdb not found (or ignored by filtering).', u'changed': True, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': False, u'_raw_params': u'pvcreate --test /dev/vdb', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin': None}}, 'stdout_lines': [], u'start': u'2018-09-13 21:16:53.818447', u'msg': u'non-zero return code'}) \u001b[0m", "start_line": 46, "end_line": 47, "created": "2018-09-13T21:16:55.310206", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Update hosts freedisk list", "task_args": "host_disk={{host_disk + [item.item]}}", "res": {"_ansible_no_log": false, "skip_reason": "Conditional result was False", "_ansible_item_result": true, "item": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdb not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.844119", "_ansible_no_log": false, "stdout": "", "failed": true, "cmd": ["pvcreate", "--test", "/dev/vdb"], "msg": "non-zero return code", "rc": 5, "start": "2018-09-13 21:16:53.818447", "delta": "0:00:00.025672", "item": "vdb", "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdb", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdb not found (or ignored by filtering).", "_ansible_item_label": "vdb"}, "changed": false, "_ansible_ignore_errors": true, "_ansible_item_label": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdb not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:53.844119", "_ansible_no_log": false, "stdout": "", "item": "vdb", "cmd": ["pvcreate", "--test", "/dev/vdb"], "rc": 5, "failed": true, "delta": "0:00:00.025672", "_ansible_item_label": "vdb", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdb not found (or ignored by filtering).", "changed": true, "invocation": {"module_args": {"warn": true, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdb", "removes": null, "argv": null, "creates": null, "chdir": null, "stdin": null}}, "stdout_lines": [], "start": "2018-09-13 21:16:53.818447", "msg": "non-zero return code"}}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000d", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-1", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:59"}, "event": "runner_item_on_skipped"} -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/45-bcd384f7-a136-4163-994a-8fd5e3895920.json: -------------------------------------------------------------------------------- 1 | {"uuid": "bcd384f7-a136-4163-994a-8fd5e3895920", "counter": 45, "stdout": "\u001b[0;36mskipping: [con-1] => (item={'_ansible_parsed': True, 'stderr_lines': [u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.', u' Device /dev/vdc not found (or ignored by filtering).'], '_ansible_item_result': True, u'end': u'2018-09-13 21:16:54.281095', '_ansible_no_log': False, u'stdout': u'', 'item': u'vdc', u'cmd': [u'pvcreate', u'--test', u'/dev/vdc'], u'rc': 5, u'failed': True, u'delta': u'0:00:00.028400', '_ansible_item_label': u'vdc', u'stderr': u' TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\\n Device /dev/vdc not found (or ignored by filtering).', u'changed': True, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': False, u'_raw_params': u'pvcreate --test /dev/vdc', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin': None}}, 'stdout_lines': [], u'start': u'2018-09-13 21:16:54.252695', u'msg': u'non-zero return code'}) \u001b[0m", "start_line": 47, "end_line": 48, "created": "2018-09-13T21:16:55.318284", "pid": 26662, "event_data": {"play_pattern": "osds", "play": "probe hosts for free disks", "task": "Update hosts freedisk list", "task_args": "host_disk={{host_disk + [item.item]}}", "res": {"_ansible_no_log": false, "skip_reason": "Conditional result was False", "_ansible_item_result": true, "item": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdc not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.281095", "_ansible_no_log": false, "stdout": "", "failed": true, "cmd": ["pvcreate", "--test", "/dev/vdc"], "msg": "non-zero return code", "rc": 5, "start": "2018-09-13 21:16:54.252695", "delta": "0:00:00.028400", "item": "vdc", "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdc", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdc not found (or ignored by filtering).", "_ansible_item_label": "vdc"}, "changed": false, "_ansible_ignore_errors": true, "_ansible_item_label": {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdc not found (or ignored by filtering)."], "_ansible_item_result": true, "end": "2018-09-13 21:16:54.281095", "_ansible_no_log": false, "stdout": "", "item": "vdc", "cmd": ["pvcreate", "--test", "/dev/vdc"], "rc": 5, "failed": true, "delta": "0:00:00.028400", "_ansible_item_label": "vdc", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdc not found (or ignored by filtering).", "changed": true, "invocation": {"module_args": {"warn": true, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdc", "removes": null, "argv": null, "creates": null, "chdir": null, "stdin": null}}, "stdout_lines": [], "start": "2018-09-13 21:16:54.252695", "msg": "non-zero return code"}}, "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000d", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "set_fact", "host": "con-1", "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:59"}, "event": "runner_item_on_skipped"} -------------------------------------------------------------------------------- /runner_service/services/hosts.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from runner_service import AnsibleInventory, configuration 4 | from .utils import APIResponse 5 | from ..utils import ssh_connect_ok, fread 6 | 7 | import logging 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | def add_host(host_name, group_name, ssh_port=None): 12 | r = APIResponse() 13 | r.data = {"hostname": host_name} 14 | inventory = AnsibleInventory(excl=True) 15 | if not inventory.loaded: 16 | r.status, r.msg = "LOCKED", \ 17 | "Unable to lock the inventory file, try later" 18 | return r 19 | 20 | if group_name not in inventory.groups: 21 | # invalid request no such group 22 | r.status, r.msg = "INVALID", "No such group found in the inventory" 23 | inventory.unlock() 24 | return r 25 | 26 | group_members = inventory.group_show(group_name) 27 | if host_name in group_members: 28 | # host already in that group! 29 | r.status, r.msg = "OK", \ 30 | "Host already in the group {}".format(group_name) 31 | inventory.unlock() 32 | return r 33 | 34 | # At this point, the group is valid, and the host requested isn't already 35 | # in it, so proceed 36 | 37 | # TODO is name an IP - if so is it valid? 38 | # TODO if it's a name, does it resolve with DNS? 39 | if configuration.settings.ssh_checks: 40 | ssh_ok, msg = ssh_connect_ok(host_name, port=ssh_port) 41 | if ssh_ok: 42 | logger.info("SSH - {}".format(msg)) 43 | else: 44 | logger.error("SSH - {}".format(msg)) 45 | error_info = msg.split(':', 1) 46 | if error_info[0] == "NOAUTH": 47 | pub_key_file = os.path.join(configuration.settings.playbooks_root_dir, # noqa 48 | "env/ssh_key.pub") 49 | r.data = {"pub_key": fread(pub_key_file)} 50 | 51 | r.status, r.msg = error_info 52 | 53 | inventory.unlock() 54 | return r 55 | else: 56 | logger.warning("Skipped SSH connection test for {}".format(host_name)) 57 | r.msg = 'skipped SSH checks due to ssh_checks disabled by config' 58 | 59 | inventory.host_add(group_name, host_name, ssh_port) 60 | r.status = "OK" 61 | r.msg = "{} added".format(host_name) 62 | 63 | return r 64 | 65 | 66 | def remove_host(host_name, group_name): 67 | r = APIResponse() 68 | inventory = AnsibleInventory(excl=True) 69 | 70 | if not inventory.loaded: 71 | r.status, r.msg = "LOCKED", "Unable to lock the inventory file, " \ 72 | "try later" 73 | return r 74 | 75 | if (group_name not in inventory.groups or 76 | host_name not in inventory.group_show(group_name)): 77 | # invalid request 78 | r.status, r.msg = "INVALID", "No such group found in the inventory" 79 | inventory.unlock() 80 | return r 81 | 82 | # At this point the removal is ok 83 | inventory.host_remove(group_name, host_name) 84 | r.status = "OK" 85 | 86 | return r 87 | 88 | 89 | def get_hosts(): 90 | r = APIResponse() 91 | inventory = AnsibleInventory() 92 | r.status, r.data = "OK", {"hosts": inventory.hosts} 93 | return r 94 | 95 | 96 | def get_host_membership(host_name): 97 | r = APIResponse() 98 | inventory = AnsibleInventory() 99 | hosts_groups = inventory.host_show(host_name) 100 | status_text = "OK" if hosts_groups else "NOTFOUND" 101 | 102 | r.status, r.data = status_text, {"groups": hosts_groups} 103 | return r 104 | -------------------------------------------------------------------------------- /runner_service/metrics.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import glob 4 | import socket 5 | 6 | from .cache import runner_stats, runner_cache 7 | from runner_service import configuration 8 | 9 | 10 | class Metric(object): 11 | """ Metric object used to hold the metric, labels and value """ 12 | 13 | def __init__(self, vhelp, vtype): 14 | self.var_help = vhelp 15 | self.var_type = vtype 16 | self.data = [] 17 | 18 | def add(self, labels, value): 19 | _d = dict(labels=labels, 20 | value=value) 21 | self.data.append(_d) 22 | 23 | 24 | class PrometheusStats(object): 25 | 26 | def __init__(self): 27 | self.metrics = {} 28 | self.hostname = socket.gethostname() 29 | 30 | def fetch(self): 31 | stime = int(time.time()) 32 | 33 | self._get_event_counts() 34 | self._get_playbook_count() 35 | self._get_playbooks_active() 36 | self._get_playbooks_status() 37 | 38 | # insert the get calls here 39 | etime = int(time.time()) 40 | 41 | fetch_stats = Metric("time taken to gather the data", "gauge") 42 | labels = {"hostname": self.hostname} 43 | fetch_stats.add(labels, etime - stime) 44 | self.metrics['runner_service_duration_scrape_secs'] = fetch_stats 45 | 46 | @property 47 | def formatted(self): 48 | s = '' 49 | for m_name in sorted(self.metrics.keys()): 50 | metric = self.metrics[m_name] 51 | s += "#HELP: {} - {}\n".format(m_name, 52 | metric.var_help) 53 | s += "#TYPE: {} - {}\n".format(m_name, 54 | metric.var_type) 55 | 56 | for v in metric.data: 57 | labels = [] 58 | for n in v['labels'].items(): 59 | label_name = '{}='.format(n[0]) 60 | label_value = '"{}"'.format(n[1]) 61 | 62 | labels.append('{}{}'.format(label_name, 63 | label_value)) 64 | 65 | s += "{}{{{}}} {}\n".format(m_name, 66 | ','.join(labels), 67 | v["value"]) 68 | 69 | return s.rstrip() 70 | 71 | def _get_playbook_count(self): 72 | _m = Metric("number of playbooks known to the service", "gauge") 73 | labels = {"hostname": self.hostname} 74 | pb_dir = os.path.join(configuration.settings.playbooks_root_dir, 75 | "project") 76 | playbook_count = len(glob.glob('{}/*.yml'.format(pb_dir))) 77 | _m.add(labels, playbook_count) 78 | self.metrics['runner_service_playbook_count'] = _m 79 | 80 | def _get_playbooks_active(self): 81 | _m = Metric("number of playbook jobs running", "gauge") 82 | labels = {"hostname": self.hostname} 83 | active_count = len(runner_cache.keys()) 84 | _m.add(labels, active_count) 85 | self.metrics['runner_service_playbooks_active'] = _m 86 | 87 | def _get_playbooks_status(self): 88 | _m = Metric("playbook completion states since daemon started", "count") 89 | for status in runner_stats.playbook_status.keys(): 90 | labels = {"hostname": self.hostname, "status": status} 91 | _m.add(labels, runner_stats.playbook_status[status]) 92 | self.metrics['runner_service_playbooks_status'] = _m 93 | 94 | def _get_event_counts(self): 95 | _m = Metric("event/task states for all playbooks executed since " 96 | "daemon started", "count") 97 | for status in runner_stats.event_stats.keys(): 98 | labels = {"hostname": self.hostname, "event_status": status} 99 | _m.add(labels, runner_stats.event_stats[status]) 100 | self.metrics['runner_service_event_status'] = _m 101 | -------------------------------------------------------------------------------- /runner_service/controllers/groups.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from .base import BaseResource 4 | from .utils import log_request 5 | from ..services.groups import (get_groups, 6 | add_group, 7 | remove_group, 8 | get_group_members 9 | ) 10 | 11 | 12 | import logging 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | class ListGroups(BaseResource): 17 | """List all the defined groups in the inventory""" 18 | 19 | @log_request(logger) 20 | def get(self): 21 | """ 22 | GET 23 | Show all groups defined in the 'inventory' 24 | 25 | Example. 26 | 27 | ``` 28 | $ curl -k -i --key ./client.key --cert ./client.crt https://localhost:5001/api/v1/groups -X GET 29 | HTTP/1.0 200 OK 30 | Content-Type: application/json 31 | Content-Length: 108 32 | Server: Werkzeug/0.14.1 Python/3.6.6 33 | Date: Wed, 05 Sep 2018 04:48:35 GMT 34 | 35 | { 36 | "status": "OK", 37 | "msg": "", 38 | "data": { 39 | "groups": [ 40 | "osds" 41 | ] 42 | } 43 | } 44 | 45 | ``` 46 | """ 47 | 48 | response = get_groups() 49 | return response.__dict__, self.state_to_http[response.status] 50 | 51 | 52 | class ManageGroups(BaseResource): 53 | """ 54 | Manage groups within the inventory 55 | """ 56 | 57 | @log_request(logger) 58 | def get(self, group_name): 59 | """ 60 | GET {group_name} 61 | Show members within a given Ansible host group 62 | 63 | Example. 64 | 65 | ``` 66 | $ curl -k -i --key ./client.key --cert ./client.crt https://localhost:5001/api/v1/groups/osds -X GET 67 | HTTP/1.0 200 OK 68 | Content-Type: application/json 69 | Content-Length: 152 70 | Server: Werkzeug/0.14.1 Python/3.6.6 71 | Date: Wed, 05 Sep 2018 05:17:57 GMT 72 | 73 | { 74 | "status": "OK", 75 | "msg": "", 76 | "data": { 77 | "members": [ 78 | "con-1", 79 | "con-2", 80 | "con-3" 81 | ] 82 | } 83 | } 84 | ``` 85 | """ 86 | 87 | response = get_group_members(group_name) 88 | return response.__dict__, self.state_to_http[response.status] 89 | 90 | 91 | @log_request(logger) 92 | def post(self, group_name): 93 | """ 94 | POST {group_name} 95 | Add a new group to the inventory 96 | 97 | Example. 98 | 99 | ``` 100 | $ curl -k -i --key ./client.key --cert ./client.crt https://localhost:5001/api/v1/groups/dummy -X POST 101 | HTTP/1.0 200 OK 102 | Content-Type: application/json 103 | Content-Length: 71 104 | Server: Werkzeug/0.14.1 Python/3.6.6 105 | Date: Wed, 05 Sep 2018 04:54:51 GMT 106 | 107 | { 108 | "status": "OK", 109 | "msg": "Group dummy added", 110 | "data": {} 111 | } 112 | ``` 113 | """ 114 | 115 | response = add_group(group_name) 116 | 117 | return response.__dict__, self.state_to_http[response.status] 118 | 119 | 120 | @log_request(logger) 121 | def delete(self, group_name): 122 | """ 123 | DELETE {group_name} 124 | Remove a group (and all related hosts) from the inventory 125 | 126 | Example. 127 | 128 | ``` 129 | $ curl -k -i --key ./client.key --cert ./client.crt https://localhost:5001/api/v1/groups/dummy -X DELETE 130 | HTTP/1.0 200 OK 131 | Content-Type: application/json 132 | Content-Length: 73 133 | Server: Werkzeug/0.14.1 Python/3.6.6 134 | Date: Wed, 05 Sep 2018 04:56:08 GMT 135 | 136 | { 137 | "status": "OK", 138 | "msg": "Group dummy removed", 139 | "data": {} 140 | } 141 | ``` 142 | """ 143 | 144 | response = remove_group(group_name) 145 | 146 | return response.__dict__, self.state_to_http[response.status] 147 | -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/29-ffc0ff84-5870-4631-9b71-07550e0bd2e1.json: -------------------------------------------------------------------------------- 1 | {"uuid": "ffc0ff84-5870-4631-9b71-07550e0bd2e1", "counter": 29, "stdout": "\u001b[0;36m...ignoring\u001b[0m", "start_line": 32, "end_line": 33, "created": "2018-09-13T21:16:54.800146", "pid": 26662, "event_data": {"play": "probe hosts for free disks", "event_loop": "{{lsblk_out.stdout_lines}}", "task_args": "_raw_params=pvcreate --test /dev/{{ item }}", "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "ignore_errors": true, "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54", "play_pattern": "osds", "task": "check if disk is free", "remote_addr": "con-1", "res": {"msg": "All items completed", "changed": true, "results": [{"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vda not found (or ignored by filtering)."], "cmd": ["pvcreate", "--test", "/dev/vda"], "end": "2018-09-13 21:16:53.405966", "_ansible_no_log": false, "stdout": "", "failed": true, "_ansible_item_result": true, "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vda", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "item": "vda", "delta": "0:00:00.025478", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vda not found (or ignored by filtering).", "rc": 5, "msg": "non-zero return code", "stdout_lines": [], "start": "2018-09-13 21:16:53.380488", "_ansible_item_label": "vda"}, {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdb not found (or ignored by filtering)."], "cmd": ["pvcreate", "--test", "/dev/vdb"], "end": "2018-09-13 21:16:53.844119", "_ansible_no_log": false, "stdout": "", "failed": true, "_ansible_item_result": true, "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdb", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "item": "vdb", "delta": "0:00:00.025672", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdb not found (or ignored by filtering).", "rc": 5, "msg": "non-zero return code", "stdout_lines": [], "start": "2018-09-13 21:16:53.818447", "_ansible_item_label": "vdb"}, {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdc not found (or ignored by filtering)."], "cmd": ["pvcreate", "--test", "/dev/vdc"], "end": "2018-09-13 21:16:54.281095", "_ansible_no_log": false, "stdout": "", "failed": true, "_ansible_item_result": true, "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdc", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "item": "vdc", "delta": "0:00:00.028400", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdc not found (or ignored by filtering).", "rc": 5, "msg": "non-zero return code", "stdout_lines": [], "start": "2018-09-13 21:16:54.252695", "_ansible_item_label": "vdc"}, {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated."], "cmd": ["pvcreate", "--test", "/dev/vdd"], "end": "2018-09-13 21:16:54.743414", "_ansible_no_log": false, "stdout": " Physical volume \"/dev/vdd\" successfully created.", "_ansible_item_result": true, "changed": true, "start": "2018-09-13 21:16:54.692525", "failed": false, "delta": "0:00:00.050889", "item": "vdd", "rc": 0, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdd", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [" Physical volume \"/dev/vdd\" successfully created."], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", "_ansible_ignore_errors": true, "_ansible_item_label": "vdd"}]}, "host": "con-1"}, "event": "runner_on_failed"} -------------------------------------------------------------------------------- /runner_service/controllers/jobs.py: -------------------------------------------------------------------------------- 1 | # from flask import request 2 | from flask_restful import request 3 | # import logging 4 | from .utils import log_request 5 | from .base import BaseResource 6 | 7 | from ..services.jobs import get_events, get_event 8 | from ..services.utils import APIResponse 9 | 10 | import logging 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | class ListEvents(BaseResource): 15 | """Return a list of events within a given playbook run (job) """ 16 | 17 | @log_request(logger) 18 | def get(self, play_uuid=None): 19 | """ 20 | GET {play_uuid}/events 21 | Return a list of the event uuid's for the given job(play_uuid). Filtering is also supported, using the 22 | ?varname=value&varname=value syntax 23 | 24 | Example. 25 | 26 | ``` 27 | $ curl -k -i --key ./client.key --cert ./client.crt https://localhost:5001/api/v1/jobs/9c1714aa-b534-11e8-8c14-aced5c652dd1/events -X GET 28 | HTTP/1.0 200 OK 29 | Content-Type: application/json 30 | Content-Length: 1142 31 | Server: Werkzeug/0.14.1 Python/3.6.5 32 | Date: Mon, 10 Sep 2018 20:04:53 GMT 33 | 34 | { 35 | "status": "OK", 36 | "msg": "", 37 | "data": { 38 | "events": { 39 | "2-0eaf70cd-0d86-4209-a3ca-73c0633afa27": { 40 | "event": "playbook_on_start" 41 | }, 42 | "3-aced5c65-2dd1-7634-7812-00000000000b": { 43 | "event": "playbook_on_play_start" 44 | }, 45 | "4-aced5c65-2dd1-7634-7812-00000000000d": { 46 | "event": "playbook_on_task_start", 47 | "task": "Step 1" 48 | }, 49 | "5-3f6d4b83-df90-401c-9fd7-2b646f00ccfe": { 50 | "event": "runner_on_ok", 51 | "host": "localhost", 52 | "task": "Step 1" 53 | }, 54 | "6-aced5c65-2dd1-7634-7812-00000000000e": { 55 | "event": "playbook_on_task_start", 56 | "task": "Step 2" 57 | }, 58 | "7-ca1c5d3a-218f-487e-97ec-be5751ac5b40": { 59 | "event": "runner_on_ok", 60 | "host": "localhost", 61 | "task": "Step 2" 62 | }, 63 | "8-7c68cc25-9ccc-4b5c-b4b3-fddaf297e7de": { 64 | "event": "playbook_on_stats" 65 | } 66 | }, 67 | "total_events": 7 68 | } 69 | } 70 | 71 | 72 | ``` 73 | """ 74 | # TODO could the to_dict throw an exception? 75 | filter = request.args.to_dict() 76 | 77 | _e = APIResponse() 78 | 79 | if not play_uuid: 80 | _e.status, _e.msg = "INVALID", "playbook uuid missing" 81 | return _e.__dict__, self.state_to_http[_e.status] 82 | 83 | response = get_events(play_uuid, filter) 84 | 85 | return response.__dict__, self.state_to_http[response.status] 86 | 87 | 88 | class GetEvent(BaseResource): 89 | """Return the output of a specific task within a playbook""" 90 | 91 | @log_request(logger) 92 | def get(self, play_uuid, event_uuid): 93 | """ 94 | GET {play_uuid, event_uuid} 95 | Return the json job event data for a given event uuid within a job 96 | 97 | Example. 98 | 99 | ``` 100 | $ curl -k -i --key ./client.key --cert ./client.crt https://localhost:5001/api/v1/jobs/9c1714aa-b534-11e8-8c14-aced5c652dd1/events/2-0eaf70cd-0d86-4209-a3ca-73c0633afa27 -X GET 101 | HTTP/1.0 200 OK 102 | Content-Type: application/json 103 | Content-Length: 480 104 | Server: Werkzeug/0.14.1 Python/3.6.5 105 | Date: Mon, 10 Sep 2018 20:12:03 GMT 106 | 107 | { 108 | "status": "OK", 109 | "msg": "", 110 | "data": { 111 | "uuid": "0eaf70cd-0d86-4209-a3ca-73c0633afa27", 112 | "counter": 2, 113 | "stdout": "", 114 | "start_line": 1, 115 | "end_line": 1, 116 | "created": "2018-09-10T20:03:40.145870", 117 | "pid": 27875, 118 | "event_data": { 119 | "pid": 27875, 120 | "playbook_uuid": "0eaf70cd-0d86-4209-a3ca-73c0633afa27", 121 | "playbook": "test.yml" 122 | }, 123 | "event": "playbook_on_start" 124 | } 125 | } 126 | ``` 127 | """ 128 | 129 | response = get_event(play_uuid, event_uuid) 130 | 131 | return response.__dict__, self.state_to_http[response.status] 132 | -------------------------------------------------------------------------------- /misc/packaging/ansible-runner-service.spec: -------------------------------------------------------------------------------- 1 | %if 0%{?rhel} == 7 2 | # Building on CentoOS 7 would result in ".el7.centos" 3 | %define dist .el7 4 | %endif 5 | 6 | Name: ansible-runner-service 7 | Version: 1.0.7 8 | Release: 1%{?dist} 9 | Summary: RESTful API for ansible/ansible_runner execution 10 | Source0: https://github.com/pcuzner/%{name}/archive/%{name}-%{version}.tar.gz 11 | Group: Applications/System 12 | License: ASL 2.0 13 | 14 | BuildArch: noarch 15 | 16 | BuildRequires: python-setuptools 17 | BuildRequires: python-devel 18 | 19 | Requires: ansible >= 2.8.1 20 | Requires: ansible-runner = 1.3.2 21 | Requires: python-flask >= 1.0.2 22 | Requires: python2-flask-restful >= 0.3.5 23 | Requires: python2-cryptography 24 | Requires: openssl 25 | Requires: pyOpenSSL 26 | Requires: PyYAML 27 | Requires: python-jwt 28 | 29 | %description 30 | This package provides a daemon that exposes a REST API interface on top of the functionality provided by ansible and ansible_runner. 31 | 32 | The daemon (ansible-runner-service) listens on https://localhost:5001 by default for playbook or ansible inventory requests. For developers interested in using the API, all the available endpoints are documented at https://localhost:5001/api. 33 | 34 | In addition to the API endpoints, the daemon also provides a /metrics endpoint for prometheus integration. A sample Grafana dashboard is provided within /usr/share/doc/ansible-runner-service 35 | 36 | %prep 37 | %setup -q -n %{name}-%{version} 38 | 39 | %build 40 | # Disable debuginfo packages 41 | %define _enable_debug_package 0 42 | %define debug_package %{nil} 43 | 44 | %{__python} setup.py build 45 | 46 | %install 47 | %{__python} setup.py install -O1 --skip-build --root %{buildroot} --install-scripts /usr/bin 48 | mkdir -p %{buildroot}%{_unitdir} 49 | install -m 0644 ./misc/systemd/ansible-runner-service.service %{buildroot}%{_unitdir} 50 | mkdir -p %{buildroot}%{_sysconfdir}/ansible-runner-service 51 | install -m 0644 ./config.yaml %{buildroot}%{_sysconfdir}/ansible-runner-service 52 | install -m 0644 ./logging.yaml %{buildroot}%{_sysconfdir}/ansible-runner-service 53 | mkdir -p %{buildroot}%{_prefix}/share/ansible-runner-service/artifacts 54 | mkdir -p %{buildroot}%{_prefix}/share/ansible-runner-service/env 55 | mkdir -p %{buildroot}%{_prefix}/share/ansible-runner-service/inventory 56 | mkdir -p %{buildroot}%{_prefix}/share/ansible-runner-service/project 57 | install -m 0644 ./samples/project/runnertest.yml %{buildroot}%{_prefix}/share/ansible-runner-service/project 58 | mkdir -p %{buildroot}%{_docdir}/ansible-runner-service/dashboards 59 | install -m 0644 ./misc/dashboards/ansible-runner-service-metrics.json %{buildroot}%{_docdir}/ansible-runner-service/dashboards 60 | install -m 0644 ./LICENSE.md %{buildroot}%{_docdir}/ansible-runner-service 61 | 62 | %post 63 | /bin/systemctl --system daemon-reload &> /dev/null || : 64 | /bin/systemctl --system enable --now ansible-runner-service &> /dev/null || : 65 | 66 | %postun 67 | /bin/systemctl --system daemon-reload &> /dev/null || : 68 | 69 | %files 70 | %{_bindir}/ansible_runner_service 71 | %{python_sitelib}/* 72 | %{_prefix}/share/ansible-runner-service/* 73 | %config(noreplace) %{_sysconfdir}/ansible-runner-service/* 74 | %{_unitdir}/ansible-runner-service.service 75 | %{_docdir}/ansible-runner-service/* 76 | 77 | %changelog 78 | * Tue Feb 2 2021 Martin Perina 1.0.7-1 79 | - Fix oVirt logging 80 | - UBI8 Image Rebase 81 | - Fix post script error in Apache 82 | - Add proper logging when decoding JSON has failed 83 | - Add ignore for partial.json.tmp 84 | - Remove finished task from cache instead of the last one 85 | 86 | * Wed Sep 23 2020 Martin Perina 1.0.6-1 87 | - Use permanent selinux label on logs 88 | - Add periodic artifacts removal to wsgi based invocations 89 | 90 | * Tue Jul 28 2020 Martin Necas 1.0.5-1 91 | - Change artifacts_remove_age for weekly cleanup 92 | 93 | * Mon Jul 13 2020 Martin Necas 1.0.4-1 94 | - No change in this RPM 95 | 96 | * Thu Jun 4 2020 Martin Necas 1.0.3-1 97 | - No change in this RPM 98 | 99 | * Tue Apr 28 2020 Martin Necas 1.0.2-1 100 | - Allow playbook parallel execution. 101 | - Add artifacts removal. 102 | - Apply logging configurations. 103 | - Handle connection to IPv6 hosts. 104 | 105 | * Tue Oct 22 2019 Ondra Machacek 1.0.1-1 106 | - Set runner_cache as defaultdict of dict. 107 | - Define ConnectionRefusedError for Python2. 108 | - Add ssh_private_key configuration option. 109 | - Add support to specify host port. 110 | - Add spec files. 111 | 112 | * Tue Aug 6 2019 Paul Cuzner 1.0.0-1 113 | - minor bug fixes 114 | 115 | * Sun Feb 10 2019 Paul Cuzner 0.9-3 116 | - minor updates to improve packaging workflow 117 | 118 | * Mon Dec 17 2018 Paul Cuzner 0.9 119 | - Repackaged for 0.9, including more specific package dependencies 120 | * Mon Sep 24 2018 Paul Cuzner 0.8 121 | - initial rpm packaging 122 | -------------------------------------------------------------------------------- /packaging/gunicorn/ansible-runner-service.spec: -------------------------------------------------------------------------------- 1 | %global srcname ansible-runner-service-dev 2 | 3 | Name: %{srcname} 4 | Version: 1.0.7 5 | Release: 1%{?dist} 6 | Summary: RESTful API for ansible/ansible_runner execution 7 | Source0: https://github.com/ansible/%{name}/archive/%{name}-%{version}.tar.gz 8 | Patch0: wsgi.patch 9 | Patch1: ovirt_log.patch 10 | Group: Applications/System 11 | License: ASL 2.0 12 | 13 | BuildArch: noarch 14 | 15 | BuildRequires: systemd 16 | BuildRequires: python3-devel 17 | BuildRequires: python3-setuptools 18 | 19 | Requires: ansible 20 | Requires: logrotate 21 | Requires: openssl 22 | Requires: openssh 23 | Requires: openssh-clients 24 | Requires: python3 25 | Requires: python3-ansible-runner 26 | Requires: python3-gunicorn 27 | Requires: python3-pyOpenSSL 28 | Requires: python3-netaddr 29 | Requires: python3-notario 30 | Requires: python3-flask 31 | Requires: python3-flask-restful 32 | Requires: python3-psutil 33 | 34 | %global _description %{expand: 35 | This package provides the Ansible Runner Service source files. Ansible runner service exposes a REST API interface on top of the functionality provided by ansible and ansible_runner. 36 | 37 | The Ansible Runner Service provided in this packages is intended to be used as uwgsi app exposed by Nginx in a Container. 38 | Dependencies, and configuration tasks must be performed in the container. 39 | 40 | Ansible Runner Service listens on https://localhost:50001 by default for playbook or ansible inventory requests. For developers interested in using the API, all the available endpoints are documented at https://localhost:50001/api. 41 | 42 | In addition to the API endpoints, the daemon also provides a /metrics endpoint for prometheus integration. A sample Grafana dashboard is provided within /usr/share/doc/ansible-runner-service} 43 | 44 | %description %_description 45 | 46 | %prep 47 | %setup -q -n ansible-runner-service-%{version} 48 | %patch0 -p1 49 | %patch1 -p1 50 | 51 | %build 52 | # Disable debuginfo packages 53 | %define _enable_debug_package 0 54 | %define debug_package %{nil} 55 | 56 | %{__python3} setup.py build 57 | 58 | %install 59 | 60 | %{__python3} setup.py install -O1 --skip-build --root %{buildroot} 61 | 62 | mkdir -p %{buildroot}%{_sysconfdir}/ansible-runner-service 63 | install -m 644 ./config.yaml %{buildroot}%{_sysconfdir}/ansible-runner-service 64 | install -m 644 ./logging.yaml %{buildroot}%{_sysconfdir}/ansible-runner-service 65 | 66 | install -m 644 ./wsgi.py %{buildroot}%{python3_sitelib}/runner_service/ 67 | install -m 644 ./ansible_runner_service.py %{buildroot}%{python3_sitelib}/runner_service 68 | 69 | mkdir -p %{buildroot}%{_unitdir} 70 | cp -r ./packaging/gunicorn/ansible-runner-service.service %{buildroot}%{_unitdir} 71 | 72 | mkdir -p %{buildroot}/var/log/ovirt-engine 73 | touch %{buildroot}/var/log/ovirt-engine/ansible-runner-service.log 74 | 75 | mkdir -p %{buildroot}%{_sysconfdir}/logrotate.d 76 | install -m 644 ./packaging/gunicorn/ansible-runner-service %{buildroot}%{_sysconfdir}/logrotate.d/ansible-runner-service 77 | 78 | %files -n %{srcname} 79 | %{_bindir}/ansible_runner_service 80 | %{python3_sitelib}/* 81 | %config(noreplace) %{_sysconfdir}/ansible-runner-service/config.yaml 82 | %config %{_sysconfdir}/ansible-runner-service/logging.yaml 83 | %{_unitdir}/ansible-runner-service.service 84 | %{_sysconfdir}/logrotate.d/ansible-runner-service 85 | /var/log/ovirt-engine/ansible-runner-service.log 86 | 87 | %license LICENSE.md 88 | 89 | %doc README.md 90 | 91 | %changelog 92 | * Tue Feb 2 2021 Martin Perina 1.0.7-1 93 | - Fix oVirt logging 94 | - UBI8 Image Rebase 95 | - Fix post script error in Apache 96 | - Add proper logging when decoding JSON has failed 97 | - Add ignore for partial.json.tmp 98 | - Remove finished task from cache instead of the last one 99 | 100 | * Thu Oct 01 2020 Martin Necas 1.0.6-2 101 | - Fix wsgi patch 102 | 103 | * Wed Sep 23 2020 Martin Perina 1.0.6-1 104 | - Use permanent selinux label on logs 105 | - Add periodic artifacts removal to wsgi based invocations 106 | 107 | * Tue Jul 28 2020 Martin Necas 1.0.5-1 108 | - Change artifacts_remove_age for weekly cleanup 109 | 110 | * Mon Jul 13 2020 Martin Necas 1.0.4-1 111 | - Fixes log rotation for Apache and Gunicorn based instances 112 | - Adds mocking of SSHClient in hostvars and inventory tests 113 | 114 | * Thu Jun 4 2020 Martin Necas 1.0.3-1 115 | - Add logrotate configuration to purge old log files 116 | - Add psutil to dependencies 117 | 118 | * Tue Apr 28 2020 Martin Necas 1.0.2-1 119 | - Allow playbook parallel execution. 120 | - Add artifacts removal. 121 | - Apply logging configurations. 122 | - Handle connection to IPv6 hosts. 123 | 124 | * Tue Oct 22 2019 Ondra Machacek 1.0.1-1 125 | - Set runner_cache as defaultdict of dict. 126 | - Define ConnectionRefusedError for Python2. 127 | - Add ssh_private_key configuration option. 128 | - Add support to specify host port. 129 | - Add spec files. 130 | 131 | * Mon Sep 2 2019 Ondra Machacek 1.0.0-1 132 | - Release 1.0.0-1. 133 | -------------------------------------------------------------------------------- /tests/data/artifacts/53b955f2-b79a-11e8-8be9-c85b7671906d/job_events/31-59691cae-5a41-48dc-b5bb-90f5e6fba53d.json: -------------------------------------------------------------------------------- 1 | {"uuid": "59691cae-5a41-48dc-b5bb-90f5e6fba53d", "counter": 31, "stdout": "\u001b[0;36m...ignoring\u001b[0m", "start_line": 34, "end_line": 35, "created": "2018-09-13T21:16:55.090338", "pid": 26662, "event_data": {"play": "probe hosts for free disks", "event_loop": "{{lsblk_out.stdout_lines}}", "task_args": "_raw_params=pvcreate --test /dev/{{ item }}", "pid": 26662, "play_uuid": "c85b7671-906d-f1b8-c363-000000000008", "task_uuid": "c85b7671-906d-f1b8-c363-00000000000c", "playbook_uuid": "992a67f3-deb5-4b6d-9ebd-fd680ad79c0e", "playbook": "probe-disks.yml", "task_action": "command", "ignore_errors": true, "task_path": "/home/paul/git/ansible-runner-service/samples/project/probe-disks.yml:54", "play_pattern": "osds", "task": "check if disk is free", "remote_addr": "con-2", "res": {"msg": "All items completed", "changed": true, "results": [{"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vda not found (or ignored by filtering)."], "cmd": ["pvcreate", "--test", "/dev/vda"], "end": "2018-09-13 21:16:53.316881", "_ansible_no_log": false, "stdout": "", "failed": true, "_ansible_item_result": true, "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vda", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "item": "vda", "delta": "0:00:00.024410", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vda not found (or ignored by filtering).", "rc": 5, "msg": "non-zero return code", "stdout_lines": [], "start": "2018-09-13 21:16:53.292471", "_ansible_item_label": "vda"}, {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdb not found (or ignored by filtering)."], "cmd": ["pvcreate", "--test", "/dev/vdb"], "end": "2018-09-13 21:16:53.746712", "_ansible_no_log": false, "stdout": "", "failed": true, "_ansible_item_result": true, "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdb", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "item": "vdb", "delta": "0:00:00.026404", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdb not found (or ignored by filtering).", "rc": 5, "msg": "non-zero return code", "stdout_lines": [], "start": "2018-09-13 21:16:53.720308", "_ansible_item_label": "vdb"}, {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdc not found (or ignored by filtering)."], "cmd": ["pvcreate", "--test", "/dev/vdc"], "end": "2018-09-13 21:16:54.178122", "_ansible_no_log": false, "stdout": "", "failed": true, "_ansible_item_result": true, "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdc", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "item": "vdc", "delta": "0:00:00.032768", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdc not found (or ignored by filtering).", "rc": 5, "msg": "non-zero return code", "stdout_lines": [], "start": "2018-09-13 21:16:54.145354", "_ansible_item_label": "vdc"}, {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", " Device /dev/vdd not found (or ignored by filtering)."], "cmd": ["pvcreate", "--test", "/dev/vdd"], "end": "2018-09-13 21:16:54.586866", "_ansible_no_log": false, "stdout": "", "failed": true, "_ansible_item_result": true, "changed": true, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vdd", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "item": "vdd", "delta": "0:00:00.029428", "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.\n Device /dev/vdd not found (or ignored by filtering).", "rc": 5, "msg": "non-zero return code", "stdout_lines": [], "start": "2018-09-13 21:16:54.557438", "_ansible_item_label": "vdd"}, {"_ansible_parsed": true, "stderr_lines": [" TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated."], "cmd": ["pvcreate", "--test", "/dev/vde"], "end": "2018-09-13 21:16:55.013852", "_ansible_no_log": false, "stdout": " Physical volume \"/dev/vde\" successfully created.", "_ansible_item_result": true, "changed": true, "start": "2018-09-13 21:16:54.976495", "failed": false, "delta": "0:00:00.037357", "item": "vde", "rc": 0, "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "_raw_params": "pvcreate --test /dev/vde", "removes": null, "argv": null, "warn": true, "chdir": null, "stdin": null}}, "stdout_lines": [" Physical volume \"/dev/vde\" successfully created."], "stderr": " TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.", "_ansible_ignore_errors": true, "_ansible_item_label": "vde"}]}, "host": "con-2"}, "event": "runner_on_failed"} -------------------------------------------------------------------------------- /misc/packaging/apache/ansible-runner-service.spec: -------------------------------------------------------------------------------- 1 | %global srcname ansible-runner-service 2 | 3 | Name: %{srcname} 4 | Version: 1.0.7 5 | Release: 1%{?dist} 6 | Summary: RESTful API for ansible/ansible_runner execution 7 | Source0: https://github.com/ansible/%{name}/archive/%{name}-%{version}.tar.gz 8 | Patch0: ovirt_log.patch 9 | Patch1: wsgi.patch 10 | Group: Applications/System 11 | License: ASL 2.0 12 | 13 | BuildArch: noarch 14 | 15 | BuildRequires: systemd 16 | BuildRequires: python3-devel 17 | BuildRequires: python3-setuptools 18 | 19 | Requires: ansible 20 | Requires: logrotate 21 | Requires: openssl 22 | Requires: openssh 23 | Requires: openssh-clients 24 | Requires: policycoreutils-python-utils 25 | Requires: python3 26 | Requires: python3-ansible-runner 27 | Requires: python3-pyOpenSSL 28 | Requires: python3-netaddr 29 | Requires: python3-notario 30 | Requires: python3-flask 31 | Requires: python3-flask-restful 32 | Requires: python3-psutil 33 | 34 | %global _description %{expand: 35 | This package provides the Ansible Runner Service source files. Ansible runner service exposes a REST API interface on top of the functionality provided by ansible and ansible_runner. 36 | 37 | The Ansible Runner Service provided in this packages is intended to be used as uwgsi app exposed by Nginx in a Container. 38 | Dependencies, and configuration tasks must be performed in the container. 39 | 40 | Ansible Runner Service listens on https://localhost:5001 by default for playbook or ansible inventory requests. For developers interested in using the API, all the available endpoints are documented at https://localhost:5001/api. 41 | 42 | In addition to the API endpoints, the daemon also provides a /metrics endpoint for prometheus integration. A sample Grafana dashboard is provided within /usr/share/doc/ansible-runner-service} 43 | 44 | %description %_description 45 | 46 | %prep 47 | %setup -q -n %{name}-%{version} 48 | %patch0 -p1 49 | %patch1 -p1 50 | 51 | %build 52 | # Disable debuginfo packages 53 | %define _enable_debug_package 0 54 | %define debug_package %{nil} 55 | 56 | %{__python3} setup.py build 57 | 58 | %install 59 | 60 | %{__python3} setup.py install -O1 --skip-build --root %{buildroot} 61 | 62 | mkdir -p %{buildroot}%{_sysconfdir}/ansible-runner-service 63 | install -m 644 ./config.yaml %{buildroot}%{_sysconfdir}/ansible-runner-service 64 | install -m 644 ./logging.yaml %{buildroot}%{_sysconfdir}/ansible-runner-service 65 | 66 | mkdir -p %{buildroot}%{_sysconfdir}/logrotate.d 67 | install -m 644 ./misc/packaging/logrotate/ansible-runner-service %{buildroot}%{_sysconfdir}/logrotate.d/ansible-runner-service 68 | 69 | install -m 644 ./ansible_runner_service.py %{buildroot}%{python3_sitelib}/runner_service 70 | 71 | mkdir -p %{buildroot}%{_var}/www/runnner 72 | install -m 644 ./wsgi.py %{buildroot}%{_var}/www/runnner/runner.wsgi 73 | 74 | %post 75 | semanage fcontext -a -t httpd_log_t -s system_u /var/log/ovirt-engine/ansible-runner-service.log 2> /dev/null || semanage fcontext -m -t httpd_log_t -s system_u /var/log/ovirt-engine/ansible-runner-service.log || true 76 | [[ -f /var/log/ovirt-engine/ansible-runner-service.log ]] && restorecon -rF /var/log/ovirt-engine/ansible-runner-service.log || true 77 | 78 | %files -n %{srcname} 79 | %{_bindir}/ansible_runner_service 80 | %{python3_sitelib}/* 81 | %{_sysconfdir}/logrotate.d/ansible-runner-service 82 | %config(noreplace) %{_sysconfdir}/ansible-runner-service/* 83 | %{_var}/www/runnner/runner.wsgi 84 | 85 | %license LICENSE.md 86 | 87 | %doc README.md 88 | 89 | %changelog 90 | * Tue Feb 2 2021 Martin Perina 1.0.7-1 91 | - Fix oVirt logging 92 | - UBI8 Image Rebase 93 | - Fix post script error in Apache 94 | - Add proper logging when decoding JSON has failed 95 | - Add ignore for partial.json.tmp 96 | - Remove finished task from cache instead of the last one 97 | 98 | * Thu Oct 13 2020 Martin Necas 1.0.6-3 99 | - Fix post script error 100 | 101 | * Thu Oct 01 2020 Martin Necas 1.0.6-2 102 | - Fix wsgi patch 103 | 104 | * Wed Sep 23 2020 Martin Perina 1.0.6-1 105 | - Use permanent selinux label on logs 106 | - Add periodic artifacts removal to wsgi based invocations 107 | 108 | * Tue Jul 28 2020 Martin Necas 1.0.5-1 109 | - Change artifacts_remove_age for weekly cleanup 110 | - Fix ansible-runner-service.log permissions 111 | 112 | * Mon Jul 13 2020 Martin Necas 1.0.4-1 113 | - Fixes log rotation for Apache and Gunicorn based instances 114 | - Adds mocking of SSHClient in hostvars and inventory tests 115 | 116 | * Thu Jun 4 2020 Martin Necas 1.0.3-1 117 | - Add logrotate configuration to purge old log files 118 | - Add psutil to dependencies 119 | 120 | * Tue Apr 28 2020 Martin Necas 1.0.2-1 121 | - Allow playbook parallel execution. 122 | - Add artifacts removal. 123 | - Apply logging configurations. 124 | - Handle connection to IPv6 hosts. 125 | 126 | * Tue Oct 22 2019 Ondra Machacek 1.0.1-1 127 | - Set runner_cache as defaultdict of dict. 128 | - Define ConnectionRefusedError for Python2. 129 | - Add ssh_private_key configuration option. 130 | - Add support to specify host port. 131 | - Add spec files. 132 | 133 | * Mon Sep 2 2019 Ondra Machacek 1.0.0-1 134 | - Release 1.0.0-1. 135 | -------------------------------------------------------------------------------- /misc/packaging/nginx/ansible-runner-service-nginx.spec: -------------------------------------------------------------------------------- 1 | %if 0%{?rhel} == 7 2 | # Building on CentoOS 7 would result in ".el7.centos" 3 | %define dist .el7 4 | %endif 5 | 6 | Name: ansible-runner-service 7 | Version: 1.0.7 8 | Release: 1%{?dist} 9 | Summary: RESTful API for ansible/ansible_runner execution 10 | Source0: https://github.com/ansible/%{name}/archive/%{name}-%{version}.tar.gz 11 | Group: Applications/System 12 | License: ASL 2.0 13 | 14 | BuildArch: noarch 15 | 16 | BuildRequires: python-setuptools 17 | BuildRequires: python-devel 18 | 19 | Requires: gcc 20 | Requires: ansible >= 2.6 21 | Requires: ansible-runner >= 1.3.2 22 | Requires: python-flask >= 1.0.2 23 | Requires: python2-flask-restful >= 0.3.5 24 | Requires: python2-cryptography 25 | Requires: python2-psutil 26 | Requires: openssl 27 | Requires: pyOpenSSL 28 | Requires: PyYAML 29 | Requires: nginx 30 | Requires: uwsgi 31 | 32 | %description 33 | This package provides a daemon that exposes a REST API interface on top of the functionality provided by ansible and ansible_runner. 34 | 35 | The daemon (ansible-runner-service) listens on https://localhost:5001 by default for playbook or ansible inventory requests. For developers interested in using the API, all the available endpoints are documented at https://localhost:5001/api. 36 | 37 | In addition to the API endpoints, the daemon also provides a /metrics endpoint for prometheus integration. A sample Grafana dashboard is provided within /usr/share/doc/ansible-runner-service 38 | 39 | %prep 40 | %setup -q -n %{name}-%{version} 41 | 42 | %build 43 | # Disable debuginfo packages 44 | %define _enable_debug_package 0 45 | %define debug_package %{nil} 46 | 47 | %{__python} setup.py build 48 | 49 | %install 50 | # Installation 51 | %{__python} setup.py install -O1 --skip-build --root %{buildroot} --install-scripts /usr/bin 52 | 53 | # Configuration and log files 54 | mkdir -p %{buildroot}%{_sysconfdir}/ansible-runner-service 55 | install -m 0644 ./config.yaml %{buildroot}%{_sysconfdir}/ansible-runner-service 56 | install -m 0644 ./logging.yaml %{buildroot}%{_sysconfdir}/ansible-runner-service 57 | 58 | # Prepare support folders 59 | mkdir -p %{buildroot}%{_prefix}/share/ansible-runner-service/artifacts 60 | mkdir -p %{buildroot}%{_prefix}/share/ansible-runner-service/env 61 | mkdir -p %{buildroot}%{_prefix}/share/ansible-runner-service/inventory 62 | mkdir -p %{buildroot}%{_prefix}/share/ansible-runner-service/project 63 | mkdir -p %{buildroot}%{_prefix}/share/ansible-runner-service/client_cert 64 | 65 | # Copy example playbooks 66 | install -m 0644 ./samples/project/runnertest.yml %{buildroot}%{_prefix}/share/ansible-runner-service/project 67 | 68 | # Prepare metrics dashboard 69 | mkdir -p %{buildroot}%{_docdir}/ansible-runner-service/dashboards 70 | install -m 0644 ./misc/dashboards/ansible-runner-service-metrics.json %{buildroot}%{_docdir}/ansible-runner-service/dashboards 71 | 72 | # Copy license 73 | install -m 0644 ./LICENSE.md %{buildroot}%{_docdir}/ansible-runner-service 74 | 75 | # Copy wsgi file 76 | mkdir -p %{buildroot}%{_sysconfdir}/nginx/conf.d 77 | install -m 0644 ./misc/nginx/ars_site_nginx.conf %{buildroot}%{_sysconfdir}/nginx/conf.d 78 | install -m 0644 ./misc/nginx/uwsgi.ini %{buildroot}%{_sysconfdir}/ansible-runner-service 79 | 80 | %post 81 | # Copy Ansible Runner Service Site config file for Nginx 82 | /bin/systemctl --system daemon-reload &> /dev/null || : 83 | 84 | %postun 85 | /bin/systemctl --system daemon-reload &> /dev/null || : 86 | 87 | %files 88 | %{_bindir}/ansible_runner_service 89 | %{python_sitelib}/* 90 | %{_prefix}/share/ansible-runner-service/* 91 | %config(noreplace) %{_sysconfdir}/ansible-runner-service/* 92 | %{_sysconfdir}/nginx/conf.d/* 93 | %{_docdir}/ansible-runner-service/* 94 | 95 | 96 | %changelog 97 | * Tue Feb 2 2021 Martin Perina 1.0.7-1 98 | - Fix oVirt logging 99 | - UBI8 Image Rebase 100 | - Fix post script error in Apache 101 | - Add proper logging when decoding JSON has failed 102 | - Add ignore for partial.json.tmp 103 | - Remove finished task from cache instead of the last one 104 | 105 | * Wed Sep 23 2020 Martin Perina 1.0.6-1 106 | - Use permanent selinux label on logs 107 | - Add periodic artifacts removal to wsgi based invocations 108 | 109 | * Tue Jul 28 2020 Martin Necas 1.0.5-1 110 | - Change artifacts_remove_age for weekly cleanup 111 | 112 | * Mon Jul 13 2020 Martin Necas 1.0.4-1 113 | - No change in this RPM 114 | 115 | * Thu Jun 4 2020 Martin Necas 1.0.3-1 116 | - Add psutil to dependencies 117 | 118 | * Tue Apr 28 2020 Martin Necas 1.0.2-1 119 | - Allow playbook parallel execution. 120 | - Add artifacts removal. 121 | - Apply logging configurations. 122 | - Handle connection to IPv6 hosts. 123 | 124 | * Tue Oct 22 2019 Ondra Machacek 1.0.1-1 125 | - Set runner_cache as defaultdict of dict. 126 | - Define ConnectionRefusedError for Python2. 127 | - Add ssh_private_key configuration option. 128 | - Add support to specify host port. 129 | - Add spec files. 130 | 131 | * Tue Aug 6 2019 Paul Cuzner 1.0.0-1 132 | - minor bug fixes 133 | 134 | * Fri Mar 29 2019 Juan Migul Olmo 0.9-4 135 | - Provide functionality using Nginx service with TLS mutual authentication 136 | 137 | * Sun Feb 10 2019 Paul Cuzner 0.9-3 138 | - minor updates to improve packaging workflow 139 | 140 | * Mon Dec 17 2018 Paul Cuzner 0.9 141 | - Repackaged for 0.9, including more specific package dependencies 142 | 143 | * Mon Sep 24 2018 Paul Cuzner 0.8 144 | - initial rpm packaging 145 | --------------------------------------------------------------------------------