├── .gitignore ├── roles └── test │ └── hello.yml ├── settings.py ├── README.md ├── helpers.py └── ansibleapi.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | venv/ 3 | -------------------------------------------------------------------------------- /roles/test/hello.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Hello World 3 | hosts: all 4 | gather_facts: no 5 | tasks: 6 | - name: Wait for 10 seconds 7 | pause: 8 | seconds: 10 9 | - name: Hello World 10 | command : echo "hello" 11 | register: hello 12 | - debug: msg="{{ hello.stdout }}" 13 | 14 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | """Settings for the Ansible API""" 2 | 3 | # API Role Listing Setup 4 | ROLES_DIR = "roles" 5 | 6 | # This list is to prevent the api from pulling data from the roles you do not 7 | # want to appear. If you want to ignore an entire directory, enter the full 8 | # name. Anything that starts with a . will automatically be ignored. 9 | IGNORED_FILES = [".md", ".j2", "Vagrantfile", "meta", ".cfg", "handlers", 10 | "defaults", "vars", "LICENCE", "files", "templates", "spec", 11 | "Rakefile", "Gemfile"] 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible API 2 | This is a really really simple API for Ansible written in Flask. It's meant to be lightweight and used in a distrubted manner to bring scale to Ansible and the capability to make REST based requests to a host. 3 | 4 | The code is not meant for production use in its current state. Its missing functions to protect your system from misuse. 5 | 6 | If you are looking for a fully fledged API, take a look at the AWX project. 7 | 8 | This Flask app lets you look at your roles via the API, and lets you run playbooks via the API 9 | 10 | To get started 11 | 12 | sudo pip install flask 13 | git clone https://github.com/donnydavis/ansible-api.git 14 | cd ansible-api 15 | python ansibleapi.py 16 | 17 | ## An example role is included to run a test to ensure the system is functioning 18 | 19 | curl --request POST \ 20 | --url http://127.0.0.1:8080/api/run/ \ 21 | --data 'role=test' \ 22 | --data 'play=hello.yml' \ 23 | --data 'host=localhost' 24 | 25 | 26 | The role parameter relates to the directory your role is in 27 | 28 | The play parameter is the Ansible playbook you want to run 29 | 30 | The host parameter is the host you want to run the play against 31 | 32 | ## If you want to view the available roles, it looks like this 33 | 34 | curl --request GET \ 35 | --url http://127.0.0.1:8080/api/roles/ 36 | 37 | The json output is configurable in the settings.py file 38 | 39 | ## Ansible API can also fetch roles from github. Support for arbitary git repos will be in the next release. 40 | 41 | curl --request POST \ 42 | --url http://127.0.0.1:8080/api/roles/github/get \ 43 | --data 'username=donnydavis' \ 44 | --data 'role=ansible-rh-subscription-manager' 45 | 46 | 47 | The instructions for how to operate the POST methods of the API are in the GET methods. 48 | 49 | ## If you are unsure of how to run a POST, then do a GET on the same API and an example will be printed for you 50 | 51 | curl --request GET \ 52 | --url http://127.0.0.1:8080/api/roles/github/get 53 | 54 | 55 | ### Thanks for checking out Ansible API 56 | -------------------------------------------------------------------------------- /helpers.py: -------------------------------------------------------------------------------- 1 | """Helpers for ansible-api""" 2 | 3 | import os 4 | 5 | 6 | # TODO: additional error handling may be required 7 | # TODO: docstring needs work 8 | def list_roles(path, ignored_files): 9 | """Takes a path and list of ignored files and returns a dictionary. 10 | 11 | :param string path: 12 | The filesystem path to look for playbooks. 13 | :param collections.list ignored_files: 14 | The list of strings to filter path contents. 15 | :returns: collections.dict containing path contents and other data. 16 | """ 17 | directory = {'name': os.path.basename(path)} 18 | if os.path.isdir(path): 19 | directory['type'] = "directory" 20 | directory['playbooks'] = [] 21 | paths = [os.path.join(path, x) for x in os.listdir(path) 22 | if not x.startswith('.') 23 | if not x.endswith(tuple(ignored_files))] 24 | for this_path in paths: 25 | contents = list_roles(this_path, ignored_files) 26 | if contents is not None: 27 | directory['playbooks'].append(contents) 28 | if not directory['playbooks']: 29 | return None 30 | else: 31 | directory['type'] = "playbook" 32 | return directory 33 | 34 | 35 | # TODO: additional error handling may be required 36 | # TODO: docstring needs work 37 | def get_funcdocs(app=None): 38 | """Takes an app and returns a list of handler paths and descriptions. 39 | 40 | :param Flask.app app: 41 | The Flaks app containing the handlers. 42 | :returns: collections.dict containing API paths and short descriptions. 43 | :raises: None 44 | """ 45 | func_list = {} 46 | if app: 47 | try: 48 | for rule in app.url_map.iter_rules(): 49 | if rule.endpoint != "static": 50 | # I am not completely sure if this is the best way to get 51 | # the docs, but it seems to work. 52 | func_list[rule.rule] = \ 53 | app.view_functions[rule.endpoint].__doc__ 54 | except KeyError: 55 | pass 56 | return func_list 57 | -------------------------------------------------------------------------------- /ansibleapi.py: -------------------------------------------------------------------------------- 1 | """Simple ansible API using Flask""" 2 | 3 | import os 4 | import subprocess 5 | from flask import Flask, jsonify, request 6 | 7 | from helpers import list_roles, get_funcdocs 8 | from settings import ROLES_DIR, IGNORED_FILES 9 | 10 | 11 | APP = Flask(__name__) 12 | 13 | 14 | @APP.route("/", methods=["GET"]) 15 | def index(): 16 | """Welcome to Ansible API""" 17 | return jsonify({ 18 | "hello": "Welcome to Ansible API, try GET: /api/ for more information" 19 | }) 20 | 21 | 22 | @APP.route("/api/", methods=["GET"]) 23 | def api_index(): 24 | """These are the available APIS.""" 25 | return jsonify(get_funcdocs(app=APP)) 26 | 27 | 28 | @APP.route("/api/roles/", methods=["GET"]) 29 | def get_roles(): 30 | """A list of installed Roles""" 31 | return jsonify(list_roles(ROLES_DIR, IGNORED_FILES)) 32 | 33 | 34 | # This could really stand to have some way to monitor the status of the 35 | # subprocess and handle errors, currently they are masked by the static 36 | # response. Storing them somewhere and then allowing another API call to 37 | # retrieve status may be sufficient. Some sort of callback might be better 38 | # for users, though. Better default response would be good as well. 39 | # TODO: This needs a lot of error handling 40 | @APP.route("/api/roles/github/", methods=["GET", "POST"]) 41 | def get_role(): 42 | """Run an Ansible Playbook""" 43 | if request.method == "POST": 44 | username = request.values.get("username") 45 | role = request.values.get("role") 46 | _ = subprocess.Popen(["/usr/bin/git", "clone", "https://github.com/" + 47 | str(username) + "/" + str(role) + ".git", 48 | os.path.join(ROLES_DIR, role)]) 49 | return jsonify({"RunningPlay": {"name": role}}) 50 | return """Currently only github is supported, try: 51 | curl --request POST 52 | --url http://127.0.0.1:8080/api/run/ 53 | --data 'username=username' 54 | --data 'role=ansible-rh-subscription-manager' 55 | """ 56 | 57 | 58 | # See the comment on get_role() 59 | # TODO: This needs a lot of error handling 60 | @APP.route("/api/run/", methods=["GET", "POST"]) 61 | def run_play(): 62 | """Run an Ansible Playbook""" 63 | if request.method == "POST": 64 | play = request.values.get("play", None) 65 | role = request.values.get("role", None) 66 | host = request.values.get("host", None) 67 | _ = subprocess.Popen(["/usr/bin/ansible-playbook", "--limit=" + 68 | str(host), os.path.join(ROLES_DIR, role, play)]) 69 | return jsonify({"RunningPlay": {"name": play}}) 70 | return """To use this API, try: 71 | curl --request POST 72 | --url http://127.0.0.1:8080/api/run/ 73 | --data 'role=test' 74 | --data 'play=hello.yml' 75 | --data 'host=localhost' 76 | """ 77 | 78 | 79 | if __name__ == "__main__": 80 | APP.run(host="0.0.0.0", port=8080, debug=True) 81 | --------------------------------------------------------------------------------