├── app ├── __init__.py ├── api │ ├── __init__.py │ ├── v1 │ │ ├── __init__.py │ │ ├── index.py │ │ └── base.py │ └── v2 │ │ ├── __init__.py │ │ ├── ansible │ │ ├── __init__.py │ │ └── jobs.py │ │ ├── index.py │ │ └── base.py ├── urls.py └── config.py ├── celerytask ├── __init__.py ├── celeryapp.py ├── stats_json.py └── tasks.py ├── run.sh ├── ansible.cfg ├── README.md ├── requirements-v1.txt ├── requirements-v2.txt ├── .gitignore ├── runserver.py └── requirements.yml /app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/api/v1/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/api/v2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /celerytask/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/api/v2/ansible/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export PYTHONOPTIMIZE=1 4 | export ANSIBLE_HOST_KEY_CHECKING=0 5 | 6 | nohup celery worker --app=celerytask.celeryapp.app -l info & 7 | nohup python runserver.py & 8 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | host_key_checking = False 3 | roles_path = /data/ansible/roles 4 | retry_files_enabled = False 5 | ansible_managed = Ansible managed: modified on %Y-%m-%d %H:%M:%S by {uid} on {host} 6 | -------------------------------------------------------------------------------- /app/urls.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: UTF-8 -*- 3 | 4 | 5 | import api.v1.index 6 | import api.v2.index 7 | import api.v2.ansible.jobs 8 | 9 | 10 | urls = api.v1.index.urls \ 11 | + api.v2.index.urls \ 12 | + api.v2.ansible.jobs.urls 13 | -------------------------------------------------------------------------------- /app/api/v1/index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: UTF-8 -*- 3 | 4 | import json 5 | from bson import json_util 6 | from tornado import gen 7 | from base import APIHandler 8 | 9 | 10 | class IndexHandler(APIHandler): 11 | 12 | @gen.coroutine 13 | def get(self, *args, **kwargs): 14 | """ """ 15 | 16 | doc = {"msg": "App rest api server"} 17 | self.write(json.dumps(doc, default=json_util.default)) 18 | 19 | urls = [ 20 | (r"/api/v1", IndexHandler), 21 | ] 22 | -------------------------------------------------------------------------------- /app/api/v2/index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: UTF-8 -*- 3 | 4 | import json 5 | from bson import json_util 6 | from tornado import gen 7 | from base import APIHandler 8 | 9 | 10 | class IndexHandler(APIHandler): 11 | 12 | @gen.coroutine 13 | def get(self, *args, **kwargs): 14 | """ """ 15 | 16 | doc = {"msg": "App rest api server"} 17 | self.write(json.dumps(doc, default=json_util.default)) 18 | 19 | urls = [ 20 | (r"/api/v2", IndexHandler), 21 | ] 22 | -------------------------------------------------------------------------------- /app/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: UTF-8 -*- 3 | 4 | PORT = "8000" 5 | DEBUG = "True" 6 | SECRET = "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=" 7 | 8 | MONGO_HOST = "127.0.0.1" 9 | MONGO_PORT = 27017 10 | MONGO_NAME = "ansible" 11 | MONGO_USER = "ansible" 12 | MONGO_PASSWORD = "ansible" 13 | MONGO_URI = "mongodb://%s:%s@%s:%d/%s" % (MONGO_USER, MONGO_PASSWORD, MONGO_HOST, MONGO_PORT, MONGO_NAME) 14 | 15 | CELERY_BROKER = "sqla+sqlite:///celery_db.sqlite" 16 | CELERY_BACKEND = "db+sqlite:///celery_db.sqlite" 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible Rest API 2 | Tornado project for Ansible Rest API service. 3 | 4 | 5 | ## 安装 6 | 7 | ### 安装依赖包 8 | 9 | 1. 采用ansible api 1.0 10 | 11 | pip install -r requirements-v1.txt 12 | 13 | 2. 采用ansible api 2.0 14 | 15 | pip install -r requirements-v2.txt 16 | 17 | ### 启动Celery 18 | 19 | 1. 采用ansible api 1.0 20 | 21 | cd ansible-api 22 | celery worker --app=celerytask.celeryapp.app -l info -c 1 23 | 24 | 2. 采用ansible api 2.0 25 | 26 | cd ansible-api 27 | export PYTHONOPTIMIZE=1 28 | celery worker --app=celerytask.celeryapp.app -l info -c 1 29 | -------------------------------------------------------------------------------- /requirements-v1.txt: -------------------------------------------------------------------------------- 1 | amqp==1.4.9 2 | ansible==1.9.6 3 | anyjson==0.3.3 4 | backports-abc==0.4 5 | billiard==3.3.0.23 6 | celery==3.1.23 7 | certifi==2016.2.28 8 | cffi==1.8.3 9 | cryptography==1.5.2 10 | enum34==1.1.6 11 | eventlet==0.19.0 12 | gevent==1.1.2 13 | greenlet==0.4.10 14 | idna==2.1 15 | ipaddress==1.0.17 16 | Jinja2==2.8 17 | kombu==3.0.35 18 | MarkupSafe==0.23 19 | motor==0.6.2 20 | paramiko==2.0.2 21 | pika==0.10.0 22 | pyasn1==0.1.9 23 | pycparser==2.14 24 | pycrypto==2.6.1 25 | pymongo==2.8 26 | pytz==2016.6.1 27 | PyYAML==3.12 28 | schema==0.6.2 29 | singledispatch==3.4.0.3 30 | six==1.10.0 31 | tornado==4.4.1 32 | tornado-celery==0.3.5 33 | setuptools==28.6.1 34 | SQLAlchemy==1.1.4 35 | -------------------------------------------------------------------------------- /requirements-v2.txt: -------------------------------------------------------------------------------- 1 | amqp==1.4.9 2 | ansible==2.2.0.0 3 | anyjson==0.3.3 4 | backports-abc==0.4 5 | billiard==3.3.0.23 6 | celery==3.1.23 7 | certifi==2016.2.28 8 | cffi==1.8.3 9 | cryptography==1.5.2 10 | enum34==1.1.6 11 | eventlet==0.19.0 12 | gevent==1.1.2 13 | greenlet==0.4.10 14 | idna==2.1 15 | ipaddress==1.0.17 16 | Jinja2==2.8 17 | kombu==3.0.35 18 | MarkupSafe==0.23 19 | motor==0.6.2 20 | paramiko==2.0.2 21 | pika==0.10.0 22 | pyasn1==0.1.9 23 | pycparser==2.14 24 | pycrypto==2.6.1 25 | pymongo==2.8 26 | pytz==2016.6.1 27 | PyYAML==3.12 28 | schema==0.6.2 29 | singledispatch==3.4.0.3 30 | six==1.10.0 31 | tornado==4.4.1 32 | tornado-celery==0.3.5 33 | setuptools==28.6.1 34 | SQLAlchemy==1.1.4 35 | -------------------------------------------------------------------------------- /celerytask/celeryapp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: UTF-8 -*- 3 | 4 | from celery import Celery 5 | from app import config 6 | 7 | 8 | app = Celery('celerytask', 9 | broker=config.CELERY_BROKER, 10 | backend=config.CELERY_BACKEND, 11 | include=["celerytask.tasks"] 12 | ) 13 | 14 | app.conf.update( 15 | CELERY_TASK_SERIALIZER='json', 16 | CELERY_ACCEPT_CONTENT=['json'], 17 | CELERY_RESULT_SERIALIZER='json', 18 | CELERY_TIMEZONE='Asia/Shanghai', 19 | CELERY_MONGODB_BACKEND_SETTINGS={ 20 | 'database': config.MONGO_NAME, 21 | }, 22 | ) 23 | 24 | 25 | if __name__ == "__main__": 26 | opts = ['-A celerytask.tasks', 'worker', '--loglevel=info'] 27 | app.start(opts) 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask instance folder 57 | instance/ 58 | 59 | # Scrapy stuff: 60 | .scrapy 61 | 62 | # Sphinx documentation 63 | docs/_build/ 64 | 65 | # PyBuilder 66 | target/ 67 | 68 | # IPython Notebook 69 | .ipynb_checkpoints 70 | 71 | # pyenv 72 | .python-version 73 | 74 | # celery beat schedule file 75 | celerybeat-schedule 76 | 77 | # dotenv 78 | .env 79 | 80 | # virtualenv 81 | venv/ 82 | ENV/ 83 | 84 | # Spyder project settings 85 | .spyderproject 86 | 87 | # Rope project settings 88 | .ropeproject 89 | 90 | # JetBrains project settings 91 | .idea/ 92 | 93 | # Mac 94 | .DS_Store 95 | 96 | # Develop database 97 | *.sqlite 98 | 99 | -------------------------------------------------------------------------------- /app/api/v1/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: UTF-8 -*- 3 | 4 | import tornado.web 5 | import tornado.escape 6 | import json 7 | import functools 8 | 9 | 10 | def authenticated(method): 11 | @functools.wraps(method) 12 | def wrapper(self, *args, **kwargs): 13 | if not self.get_api_key(): 14 | self.redirect("/users/login") 15 | return method(self, *args, **kwargs) 16 | return wrapper 17 | 18 | 19 | class APIHandler(tornado.web.RequestHandler): 20 | """ """ 21 | 22 | @property 23 | def db(self): 24 | return self.application.db 25 | 26 | def prepare(self): 27 | # if auth: 28 | # self.is_authenticated() 29 | pass 30 | 31 | def get_current_user(self): 32 | user_json = self.get_secure_cookie("username") 33 | if not user_json: 34 | return None 35 | return tornado.escape.json_decode(user_json) 36 | 37 | def set_default_headers(self): 38 | self.set_header("Content-Type", "application/json; charset=UTF-8") 39 | 40 | def write_error(self, status_code, **kwargs): 41 | if self._status_code == 404: 42 | self.write(json.dumps({"error": "page not found"})) 43 | elif self._status_code == 500: 44 | self.write(json.dumps({"error": "500"})) 45 | else: 46 | self.write(json.dumps({"error": self._status_code})) 47 | 48 | def get_api_key(self): 49 | return self.get_argument("apikey", False) 50 | # if self.get_argument("code", False): 51 | # user = self.db.users.find_one({"code": self.get_argument("code")}) 52 | # return self.get_argument("apikey") 53 | 54 | 55 | class APIError(tornado.web.HTTPError): 56 | """需要向前端输出错误异常时,请直接在 Handler 中使用 raise APIError() 即可""" 57 | def __init__(self, status_code, *args, **kwargs): 58 | super(APIError, self).__init__(status_code, *args, **kwargs) 59 | -------------------------------------------------------------------------------- /app/api/v2/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: UTF-8 -*- 3 | 4 | import tornado.web 5 | import tornado.escape 6 | import json 7 | import functools 8 | 9 | 10 | def authenticated(method): 11 | @functools.wraps(method) 12 | def wrapper(self, *args, **kwargs): 13 | if not self.get_api_key(): 14 | self.redirect("/users/login") 15 | return method(self, *args, **kwargs) 16 | return wrapper 17 | 18 | 19 | class APIHandler(tornado.web.RequestHandler): 20 | """ """ 21 | 22 | @property 23 | def db(self): 24 | return self.application.db 25 | 26 | def prepare(self): 27 | # if auth: 28 | # self.is_authenticated() 29 | pass 30 | 31 | def get_current_user(self): 32 | user_json = self.get_secure_cookie("username") 33 | if not user_json: 34 | return None 35 | return tornado.escape.json_decode(user_json) 36 | 37 | def set_default_headers(self): 38 | self.set_header("Content-Type", "application/json; charset=UTF-8") 39 | 40 | def write_error(self, status_code, **kwargs): 41 | if self._status_code == 404: 42 | self.write(json.dumps({"error": "page not found"})) 43 | elif self._status_code == 500: 44 | self.write(json.dumps({"error": "500"})) 45 | else: 46 | self.write(json.dumps({"error": self._status_code})) 47 | 48 | def get_api_key(self): 49 | return self.get_argument("apikey", False) 50 | # if self.get_argument("code", False): 51 | # user = self.db.users.find_one({"code": self.get_argument("code")}) 52 | # return self.get_argument("apikey") 53 | 54 | 55 | class APIError(tornado.web.HTTPError): 56 | """需要向前端输出错误异常时,请直接在 Handler 中使用 raise APIError() 即可""" 57 | def __init__(self, status_code, *args, **kwargs): 58 | super(APIError, self).__init__(status_code, *args, **kwargs) 59 | -------------------------------------------------------------------------------- /celerytask/stats_json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: UTF-8 -*- 3 | 4 | from __future__ import (absolute_import, division, print_function) 5 | __metaclass__ = type 6 | 7 | import json 8 | 9 | from ansible.plugins.callback import CallbackBase 10 | 11 | 12 | class CallbackModule(CallbackBase): 13 | CALLBACK_VERSION = 2.0 14 | CALLBACK_TYPE = 'stdout' 15 | CALLBACK_NAME = 'stats_json' 16 | 17 | def __init__(self, display=None): 18 | super(CallbackModule, self).__init__(display) 19 | self.results = [] 20 | self.stats = {} 21 | 22 | def _new_play(self, play): 23 | return { 24 | 'play': { 25 | 'name': play.name, 26 | 'id': str(play._uuid) 27 | }, 28 | 'tasks': [] 29 | } 30 | 31 | def _new_task(self, task): 32 | return { 33 | 'task': { 34 | 'name': task.name, 35 | 'id': str(task._uuid) 36 | }, 37 | 'hosts': {} 38 | } 39 | 40 | def v2_playbook_on_play_start(self, play): 41 | self.results.append(self._new_play(play)) 42 | 43 | def v2_playbook_on_task_start(self, task, is_conditional): 44 | self.results[-1]['tasks'].append(self._new_task(task)) 45 | 46 | def v2_runner_on_ok(self, result, **kwargs): 47 | host = result._host 48 | self.results[-1]['tasks'][-1]['hosts'][host.name] = result._result 49 | 50 | def v2_playbook_on_stats(self, stats): 51 | """Display info about playbook statistics""" 52 | 53 | hosts = sorted(stats.processed.keys()) 54 | 55 | summary = {} 56 | for h in hosts: 57 | s = stats.summarize(h) 58 | summary[h] = s 59 | 60 | self.stats = summary 61 | 62 | v2_runner_on_failed = v2_runner_on_ok 63 | v2_runner_on_unreachable = v2_runner_on_ok 64 | v2_runner_on_skipped = v2_runner_on_ok -------------------------------------------------------------------------------- /runserver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: UTF-8 -*- 3 | 4 | import os 5 | import tornado.web 6 | import tornado.httpserver 7 | import tornado.ioloop 8 | import tornado.netutil 9 | import tornado.process 10 | from tornado.options import options, define 11 | import motor 12 | 13 | from app import config, urls 14 | 15 | 16 | define("PORT", default=config.PORT, help="run on the given port", type=int) 17 | define("DEBUG", default=config.DEBUG, type=bool) 18 | define("SECRET", default=config.SECRET, type=str) 19 | define("MONGO_HOST", default=config.MONGO_HOST, help="mongodb database host", type=str) 20 | define("MONGO_PORT", default=config.MONGO_PORT, help="mongodb database port", type=int) 21 | define("MONGO_NAME", default=config.MONGO_NAME, help="mongodb database name", type=str) 22 | define("MONGO_USER", default=config.MONGO_USER, help="mongodb database user name", type=str) 23 | define("MONGO_PASSWORD", default=config.MONGO_PASSWORD, help="mongodb database password", type=str) 24 | 25 | 26 | class Application(tornado.web.Application): 27 | def __init__(self): 28 | handlers = urls.urls 29 | settings = dict( 30 | template_path=os.path.join(os.path.dirname(__file__), "app/templates"), 31 | static_path=os.path.join(os.path.dirname(__file__), "app/static"), 32 | cookie_secret=options.SECRET, 33 | xsrf_cookies=False, 34 | login_url="/users/login", 35 | debug=options.DEBUG, 36 | ) 37 | 38 | tornado.web.Application.__init__(self, handlers, **settings) 39 | 40 | self.db = motor.MotorClient(config.MONGO_URI)[config.MONGO_NAME] 41 | 42 | 43 | def main(): 44 | tornado.options.parse_command_line() 45 | # tornado.web.ErrorHandler = base.ErrorHandler 46 | 47 | if options.DEBUG: 48 | app_server = tornado.httpserver.HTTPServer(Application()) 49 | print ("app App run in dev mode on %s" % options.PORT) 50 | app_server.listen(options.PORT) 51 | else: 52 | sockets = tornado.netutil.bind_sockets(options.PORT) 53 | tornado.process.fork_processes(0) 54 | app_server = tornado.httpserver.HTTPServer(Application()) 55 | app_server.add_socket(sockets) 56 | 57 | tornado.ioloop.IOLoop.instance().start() 58 | 59 | 60 | if __name__ == "__main__": 61 | main() -------------------------------------------------------------------------------- /requirements.yml: -------------------------------------------------------------------------------- 1 | - name: ansible-role-base 2 | src: https://github.com/devops/ansible-role-base.git 3 | scm: git 4 | path: /data/ansible/roles/ansible-role-base 5 | 6 | - name: ansible-role-sshd 7 | src: https://github.com/devops/ansible-role-sshd.git 8 | scm: git 9 | path: /data/ansible/roles/ansible-role-sshd 10 | 11 | - name: ansible-role-mysql 12 | src: https://github.com/devops/ansible-role-mysql.git 13 | scm: git 14 | path: /data/ansible/roles/ansible-role-mysql 15 | 16 | - name: ansible-role-tomcat7 17 | src: https://github.com/devops/ansible-role-tomcat7.git 18 | scm: git 19 | path: /data/ansible/roles/ansible-role-tomcat7 20 | 21 | - name: ansible-role-rabbitmq 22 | src: https://github.com/devops/ansible-role-rabbitmq.git 23 | scm: git 24 | path: /data/ansible/roles/ansible-role-rabbitmq 25 | 26 | - name: ansible-role-haproxy 27 | src: https://github.com/devops/ansible-role-haproxy.git 28 | scm: git 29 | path: /data/ansible/roles/ansible-role-haproxy 30 | 31 | - name: ansible-role-chrony 32 | src: https://github.com/devops/ansible-role-chrony.git 33 | scm: git 34 | path: /data/ansible/roles/ansible-role-chrony 35 | 36 | - name: ansible-role-elasticsearch 37 | src: https://github.com/devops/ansible-role-elasticsearch.git 38 | scm: git 39 | path: /data/ansible/roles/ansible-role-elasticsearch 40 | 41 | - name: ansible-role-influxdb 42 | src: https://github.com/devops/ansible-role-influxdb.git 43 | scm: git 44 | path: /data/ansible/roles/ansible-role-influxdb 45 | 46 | - name: ansible-role-memcached 47 | src: https://github.com/devops/ansible-role-memcached.git 48 | scm: git 49 | path: /data/ansible/roles/ansible-role-memcached 50 | 51 | - name: ansible-role-network 52 | src: https://github.com/devops/ansible-role-network.git 53 | scm: git 54 | path: /data/ansible/roles/ansible-role-network 55 | 56 | - name: ansible-role-repo-lan 57 | src: https://github.com/devops/ansible-role-repo-lan.git 58 | scm: git 59 | path: /data/ansible/roles/ansible-role-repo-lan 60 | 61 | - name: ansible-role-repo-epel 62 | src: https://github.com/devops/ansible-role-repo-epel.git 63 | scm: git 64 | path: /data/ansible/roles/ansible-role-repo-epel 65 | 66 | - name: ansible-role-ntpd 67 | src: https://github.com/devops/ansible-role-ntpd.git 68 | scm: git 69 | path: /data/ansible/roles/ansible-role-ntpd 70 | 71 | - name: ansible-role-ntp 72 | src: https://github.com/devops/ansible-role-ntp.git 73 | scm: git 74 | path: /data/ansible/roles/ansible-role-ntp 75 | 76 | - name: ansible-role-nfs 77 | src: https://github.com/devops/ansible-role-nfs.git 78 | scm: git 79 | path: /data/ansible/roles/ansible-role-nfs 80 | 81 | - name: ansible-role-jboss 82 | src: https://github.com/devops/ansible-role-jboss.git 83 | scm: git 84 | path: /data/ansible/roles/ansible-role-jboss -------------------------------------------------------------------------------- /app/api/v2/ansible/jobs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: UTF-8 -*- 3 | 4 | import json 5 | from bson import json_util 6 | from schema import Schema, SchemaError, Optional 7 | from celerytask import tasks 8 | 9 | from app.api.v1.base import APIHandler, authenticated 10 | 11 | 12 | class AdhocJobsHandler(APIHandler): 13 | 14 | 15 | def post(self): 16 | adhoc_schema = Schema({ 17 | 'host_list': basestring, 18 | 'module_name': basestring, 19 | 'module_args': basestring, 20 | 'pattern': basestring, 21 | Optional('forks'): int, 22 | Optional('play_name'): basestring, 23 | 24 | }) 25 | try: 26 | job_data = adhoc_schema.validate(json.loads(self.request.body)) 27 | except SchemaError as e: 28 | self.write(json.dumps({"error": e.message})) 29 | else: 30 | job = tasks.ansible_adhoc.delay( 31 | host_list=job_data['host_list'], 32 | module_name=job_data['module_name'], 33 | module_args=job_data['module_args'], 34 | pattern=job_data['pattern'] 35 | ) 36 | self.write(json.dumps({"task_id": job.id}, default=json_util.default)) 37 | self.set_status(202) 38 | self.set_header("Location", "/api/v2/ansible/jobs/status/" + job.id) 39 | 40 | 41 | class PlaybookJobsHandler(APIHandler): 42 | 43 | def post(self, *args, **kwargs): 44 | playbook_schema = Schema({ 45 | 'playbook': basestring, 46 | 'host_list': basestring, 47 | 'module_path': basestring 48 | }) 49 | try: 50 | job_data = playbook_schema.validate(json.loads(self.request.body)) 51 | except SchemaError as e: 52 | self.write(json.dumps({"error": e.message})) 53 | else: 54 | job = tasks.ansible_playbook.delay( 55 | playbook=job_data['playbook'], 56 | host_list=job_data['host_list'], 57 | module_path=job_data['module_path'] 58 | ) 59 | self.write(json.dumps({"task_id": job.id}, default=json_util.default)) 60 | self.set_status(202) 61 | self.set_header("Location", "/api/v2/ansible/jobs/status/" + job.id) 62 | 63 | class AnsibleJobStatusHandler(APIHandler): 64 | def get(self, task_id): 65 | task = tasks.ansible_adhoc.AsyncResult(task_id) 66 | response = { 67 | 'task_id': task_id, 68 | 'state': task.state, 69 | 'result': task.info 70 | } 71 | self.write(json.dumps(response)) 72 | 73 | urls = [ 74 | (r"/api/v2/ansible/jobs/adhocs", AdhocJobsHandler), 75 | (r"/api/v2/ansible/jobs/playbooks", PlaybookJobsHandler), 76 | (r"/api/v2/ansible/jobs/status/(.*)", AnsibleJobStatusHandler), 77 | 78 | ] 79 | -------------------------------------------------------------------------------- /celerytask/tasks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: UTF-8 -*- 3 | 4 | from collections import namedtuple 5 | from ansible.parsing.dataloader import DataLoader 6 | from ansible.vars import VariableManager 7 | from ansible.inventory import Inventory 8 | from ansible.playbook.play import Play 9 | from ansible.executor.task_queue_manager import TaskQueueManager 10 | from ansible.executor.playbook_executor import PlaybookExecutor 11 | #from ansible.plugins.callback.json import CallbackModule 12 | from celerytask.stats_json import CallbackModule 13 | 14 | from celerytask.celeryapp import app 15 | 16 | 17 | @app.task() 18 | def ansible_adhoc(host_list, module_name, module_args, pattern, play_name=None, passwords=None, forks=5): 19 | 20 | loader = DataLoader() 21 | variable_manager = VariableManager() 22 | inventory = Inventory(loader=loader, 23 | variable_manager=variable_manager, 24 | host_list=host_list) 25 | variable_manager.set_inventory(inventory) 26 | 27 | Options = namedtuple( 28 | 'Options',[ 29 | 'remote_user', 'forks', 'become_method', 'become_user', 'listhosts', 'listtasks', 'listtags', 30 | 'syntax', 'module_path', 'become', 'check', 'verbosity', 'connection', 'private_key_file', 31 | 'host_key_checking' 32 | ] 33 | ) 34 | options = Options(remote_user='root', forks=10, become_method='sudo', become_user='root', listhosts=False, 35 | listtasks=False, listtags=False, syntax=False, module_path=None, become=True, check=False, 36 | verbosity=False, connection='smart', private_key_file=None, host_key_checking=False) 37 | 38 | # variable_manager.extra_vars={"ansible_ssh_user":"root" , "ansible_ssh_pass":"password"} 39 | passwords = passwords 40 | play_source = {"name": play_name, 41 | "hosts": pattern, 42 | "gather_facts": "no", 43 | "tasks": [{"action": {"module": module_name, "args": module_args}}]} 44 | play = Play().load(play_source, variable_manager=variable_manager, loader=loader) 45 | tqm = None 46 | try: 47 | tqm = TaskQueueManager( 48 | inventory=inventory, 49 | variable_manager=variable_manager, 50 | loader=loader, 51 | options=options, 52 | passwords=passwords, 53 | stdout_callback='json', 54 | ) 55 | result_code = tqm.run(play) 56 | 57 | except Exception as e: 58 | raise Exception(e) 59 | 60 | finally: 61 | if tqm is not None: 62 | tqm.cleanup() 63 | return dict(retcode=result_code, results=tqm._stdout_callback.results) 64 | 65 | @app.task() 66 | def ansible_playbook(playbook, host_list, module_path, passwords=None): 67 | loader = DataLoader() 68 | variable_manager = VariableManager() 69 | inventory = Inventory(loader=loader, 70 | variable_manager=variable_manager, 71 | host_list=host_list) 72 | variable_manager.set_inventory(inventory) 73 | 74 | Options = namedtuple('Options', 75 | ['remote_user', 'forks', 'become_method', 'become_user', 'listhosts', 'listtasks', 'listtags', 76 | 'syntax', 'module_path', 'become', 'check', 'verbosity', 'connection', 'private_key_file', 77 | 'host_key_checking']) 78 | options = Options(remote_user='root', forks=10, become_method='sudo', become_user='root', listhosts=False, 79 | listtasks=False, listtags=False, syntax=False, module_path=None, become=True, check=False, 80 | verbosity=False, connection='smart', private_key_file=None, host_key_checking=False) 81 | 82 | # variable_manager.extra_vars={"ansible_ssh_user":"root" , "ansible_ssh_pass":"password"} 83 | passwords = passwords 84 | try: 85 | pbex = PlaybookExecutor(playbooks=[playbook], 86 | inventory=inventory, 87 | variable_manager=variable_manager, 88 | loader=loader, 89 | passwords=passwords, 90 | options=options) 91 | pbex._tqm._stdout_callback = CallbackModule() 92 | result_code = pbex.run() 93 | return dict(retcode=result_code, plays=pbex._tqm._stdout_callback.results, stats=pbex._tqm._stdout_callback.stats) 94 | except Exception as e: 95 | raise Exception(e) 96 | --------------------------------------------------------------------------------