An Ansible playbook automation server.
A node.js server which exposes a simple web API which triggers playbook runs when a request is received. This is especially useful if you are unable to run Ansible playbooks directly from within your continuous integration environment or if you simply wish to trigger playbook runs based on other events within your system.
Features:
- Trigger playbook runs from different sources, including from CI systems such as Drone.
- Run multiple playbooks in parallel, all in separate processes
- Fast, friendly web interface with accompanying REST API
- Highly asynchronous, scalable back-end
- Full console log capture and storage
- Sends notifications of job status through HipChat
Pre-requisite: Ansible 1.5+
Installation instructions: https://docs.ansible.com/intro_installation.html.
To ensure you have the latest version it is recommended that you install it
using pip
, the Python package manager.
Pre-requisite: Node.js 0.11.2+
Installation instructions: https://nodejs.org/.
Ensure the installed version is at least 0.11.2. Ansijet will not work with earlier versions.
(For Ubuntu users I recommend the Chris Lea PPA).
Pre-requisite: MongoDB
Installation instructions:
Ansijet stores its data in MongoDB. The default configuration expects to be able
to connect to a MongoDB server running on 127.0.0.1
(i.e. localhost
).
Setup your Ansible playbooks
Place your Ansible playbooks somewhere, e.g. /playbooks
.
Ansijet expects your playbooks folder to have a certain structure:
<playbooks folder>/*.yml <- your playbooks
<playbooks folder>/hosts <- Ansible hosts file
Ensure that any roles
needed
by your playbooks can be found by the
ansible-playbook
binary. An easy way to ensure this is to store your roles
within the same folder, i.e. at <playbooks folder>/roles/
. Ditto for
group_vars
and host_vars
folders.
Setup Ansijet
$ git clone https://github.com/hiddentao/ansijet.git ansijet
$ cd ansijet
$ npm install -g gulp bower
$ npm install
$ bower install
$ npm run build
Now create ansijet/src/config/production.js
:
"use strict";
module.exports = function(config) {
/** Path to folder containg Ansible playbooks */
config.playbooks = '/playbooks'
/** Max no. of jobs to execute in parallel. Should match no. of CPU cores. */
config.jobsInParallel = 1;
};
If you look inside ansijet/src/config/base.js
you will see other
configuration settings MongoDB, logging, etc. You may
override these too within the config/production.js
you created.
Run Ansijet
$ cd ansijet
$ NODE_ENV=production ./start-app.js
If you visit https://localhost:3000
you should see the dashboard showing the
Active Jobs (there should be none currently).
Once Ansijet is up and running and you can access the web interface you can view the list of Playbooks that Ansijet has found and assign triggers to them.
A trigger is a mechanism which kicks of a playook run when an incoming URL request is received.
Triggers have two purposes:
- To perform any necessary additional checks when a request is received to ensure that the request is valid
- To supply variables to the Ansible playbook, allowing for playbook execution to be configurable based on the incoming request and the trigger configuration.
All triggers URLs look like /invoke/<trigger id>?token=<secret token>
with
additional query parameters depending on the trigger type.
Note: The <secret token>
is randomly generated by Ansijet when a trigger is created
and acts as an additional security check. If the token in an incoming request is
incorrect Ansijet does not report this to the URL requester - it simply logs
this fact in the back-end.
At present two trigger types are supported:
Trigger: Simple
This exposes a simple URL which triggers a playbook run. It does not perform any checks prior to triggering the playbook run. Neither does it supply any Ansible playbook variables.
Trigger: Drone
This exposes a URL to be called after a successful Drone build. It supplies the following Ansible variables:
ci_expected_branch
<- Git branch to run playbook for, configured by userci_build_commit
<- Git commit id, obtained from incoming requestci_build_branch
<- Git branch built, obtained from incoming request
When a trigger is invoked it runs a playbook, known as a Job. Jobs are
executed in parallel by
Ansijet, with the maximum no. of simultaenous jobs determined by the
jobsInParallel
configuration parameter set during Ansijet installation.
Ansijet is also smart enough to ensure that for each playbook, only one instance
of it is being run at a time.
Each job - i.e. playbook run - takes place in a separate shell process, allowing Ansijet to be scaled up according to your machine's cores. Ansijet also monitors each shell process such that if no output is received for 5 minutes (this time window is configurable) it will kill the shell process and assume the playbook run has failed.
When a job is being processed it shows up as an Active Job on your Ansijet server's homepage. You can click on it to view the current log output, including console log output.
All logs can be viewed by going to the Logs site section. You can then drill down to view the logs pertaining to a particular trigger and/or a particular trigger job.
Ansijet is built using Waigo, which means that all the
URL routes automatically have REST API counterparts. For any given URL, you can
view REST JSON output by simply appending a format=json
query parameter when
making the request. This applies to form submissions too. For more information
on this see the Waigo docs.
Ansijet can be configured to send notifications to a
HipChat room using the
send_room_notification
API.
Simply add the room id and auth token to your configuration file:
module.exports = function(config) {
...
config.notifications.hipChat = {
roomId: <room id>,
authToken: <auth token for room>
};
...
};
When Ansijet first starts up it will send a notification. Subsequent notifications will get sent for every job which gets processed.
Ansijet does not come with any sort of authentication out of the box. Since it's running playbooks which most probably affect your servers you will likely want to protect access to it.
My setup is to have Ansijet placed behind an Nginx front-end server, with SSL and HTTP Basic auth enforced on all incoming requests:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443;
server_name example.com www.example.com;
ssl on;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
ssl_session_timeout 5m;
# Perfect Forward Secrecy
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA256:EECDH+aRSA+RC4:EDH+aRSA:EECDH:RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS;
root /ansijet/frontend/build;
location ~ /\. {
deny all;
}
location ~* ^/(css|fonts|img|js)/.+$ {
gzip_static on;
gzip_vary on;
expires 30d;
add_header Pragma public;
add_header Cache-Control "public";
}
location ~* ^(robots|humans)\.txt$ {
expires 30d;
add_header Pragma public;
add_header Cache-Control "public";
}
# If you want to monitor the status of Ansijet and check that it is running
# you can call the `/ping` URL. This will output `Ansijet up` is Ansijet is
# running
location = /ping {
proxy_pass https://127.0.0.1:3000;
}
# Everything else needs auth
location / {
auth_basic on;
auth_basic_user_file /ansijet/httpd.auth;
proxy_pass https://127.0.0.1:3000;
}
}
Though I am already using Ansijet in a production environment it is very much a work-in-progress. All suggestions and pull requests are welcome!
See CONTRIBUTING.md for guidelines.
MIT - see LICENSE.md