Skip to content

Commit

Permalink
Initial GitHub webhook bridge
Browse files Browse the repository at this point in the history
  • Loading branch information
oybed committed Aug 27, 2018
1 parent 01985b4 commit 32522f3
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 0 deletions.
46 changes: 46 additions & 0 deletions ansible-tower-bridges/github-webhook/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
ansible-tower-bridges/github-webhook
====================================

This implementation is used to bridge GitHub webhooks to Ansible Tower. Ansible Tower expects any payload/input parameters to be passed in the `extra_vars` dictionary. Of course GitHub does not do this, and hence this "bridge" to provide that mapping for webhooks request configured on github to be sent to/through this bridge.

Requirements
------------

Although this can be run as a regular/standalone `node.js` app, it is recommended that this gets deployed on an OpenShift Container Platform for ease of maintaining it.


Running in OpenShift
--------------------

For example, use `oc new-app` to deploy the application:

```
> oc new-app openshift/nodejs:8~https://github.com/tool-integrations.git --context-dir=ansible-tower-bridges/github-webhook
```


Configuration
-------------

| Variable | Description | Required | Defaults |
|:---------|:------------|:---------|:---------|
| ANSIBLE_TOWER_URL | The fqdn portion of Ansible Tower (or IP) - do not include 'http://' or 'https://' | yes | |
| ANSIBLE_TOWER_TEMPLATE_ID | The numeric template id for the job template to run | yes | |
| ANSIBLE_TOWER_USERNAME | An Ansible Tower username with the correct permissions to run the above template | yes | |
| ANSIBLE_TOWER_PASSWORD | Password for the above Ansible Tower username | yes | |
| HTTP_PORT | The http port for the application to listen on | no | 8080 |
| HTTPS_PORT | The httpd (SSL) port for the application listen on (choose this or HTTP_PORT above) | no | 8443 |
| HTTPS_SSL_CERTIFICATE | When HTTPS_PORT above is used, specify the certificate here | no | |
| HTTPS_SSL_KEY | When the HTTPS_PORT above is used, specify the certificate key here | no | |


License
-------

Apache License 2.0


Author Information
------------------

Red Hat Community of Practice & staff of the Red Hat Open Innovation Labs.
46 changes: 46 additions & 0 deletions ansible-tower-bridges/github-webhook/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

const http = require('http');
const https = require('https');
const fs = require('fs');

const httpPort = process.env.HTTP_PORT || 8080;
const httpsPort = process.env.HTTPS_PORT || 8443;
const httpsSSLKey = process.env.HTTPS_SSL_KEY || '';
const httpsSSLCert = process.env.HTTPS_SSL_CERTIFICATE || '';

var express = require('express');
var cors = require('cors');
var app = express();
var bodyParser = require('body-parser');
var bridge = require('./lib/bridge');

app.use(cors());
app.use(bodyParser());

app.post('/', function (request, res) {
bridge.processRequest(request.body, function(err, response) {
if (err){
// TODO: make the return status code be more specific per the error
// - i.e.: not use "400 Bad Request" for all of it
res.status(400).send(err);
} else {
res.send(response);
}
});
});

if (httpsSSLCert.trim() && httpsSSLKey.trim()) { // Secure
const options = {
key: fs.readFileSync(httpsSSLKey),
cert: fs.readFileSync(httpsSSLCert)
};

https.createServer(options, app).listen(httpsPort, function () {
console.log('Listening on https://localhost:' + httpsPort);
});
}
else { // non-Secure
http.createServer(app).listen(httpPort, function() {
console.log('Listening on UNSECURE http://localhost:' + httpPort);
});
}
105 changes: 105 additions & 0 deletions ansible-tower-bridges/github-webhook/lib/bridge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@

const request = require('request');
const urlModule = require('url');

const AnsibleTowerUrl = process.env['ANSIBLE_TOWER_URL'];
const AnsibleTowerTemplateId = process.env['ANSIBLE_TOWER_TEMPLATE_ID'];
const AnsibleTowerUsername = process.env['ANSIBLE_TOWER_USERNAME'] || '';
const AnsibleTowerPassword = process.env['ANSIBLE_TOWER_PASSWORD'] || '';

process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"


function getTemplateLaunchUri(){
return '/api/v1/job_templates/' + AnsibleTowerTemplateId + '/launch/';
}

function getRequestUrl(server, uri){
return 'https://' + server + uri;
}

function getAuth(){
return 'Basic ' + new Buffer(AnsibleTowerUsername + ':' + AnsibleTowerPassword).toString('base64');
}


function generateReturnJson(message, url) {
var returnjson = {
message: message,
url: ''
};

if (url) {
returnjson['url'] = url;
}

return returnjson;
}


function sendResponse(cb, successMessage, errorMessage, url) {
if (errorMessage) {
console.log('ERROR: ' + errorMessage);
returnjson = generateReturnJson(errorMessage, url);
cb(returnjson, null);
} else if (successMessage) {
console.log('SUCCESS: ' + successMessage);
returnjson = generateReturnJson(successMessage, url);
cb(null, returnjson);
} else {
returnjson = generateReturnJson('Unknown Error');
cb(returnjson, null);
}
}


function processRequest(params, cb) {
var uri = getTemplateLaunchUri();
var request_url = getRequestUrl(AnsibleTowerUrl, uri);
var auth = getAuth();

var config_body = {
"extra_vars" : {}
};

// Wrap the params in the `extra_vars` dictionary
config_body['extra_vars'] = params;

var options = {
url: request_url,
method: 'POST',
headers: {
'Content-type': 'application/json',
'Authorization': auth
},
json: config_body
};

// options_string = JSON.stringify(options, null, 4);
// console.log('calling options: ' + options_string);

request(options, function (error, response) {
//console.log('*********RESPONSE**********************');
//console.log(response);
//console.log('**********END RESPONSE*****************');
if (!error && response.statusCode >= 200 && response.statusCode < 300) {
sendResponse(
cb,
'Successful Launch with Ansible Tower job ID: ' + response.body.job,
null,
'https://' + AnsibleTowerUrl + '/#/jobs/' + response.body.job);

}
else {
var errorMsg = 'Failed to launch Ansible Tower job. Please check your input and try again.';
if (error) {
errorMsg += ' (' + error + ')';
}

sendResponse(cb, null, errorMsg, null);
}
})
}


exports.processRequest = processRequest;
18 changes: 18 additions & 0 deletions ansible-tower-bridges/github-webhook/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "github-webhook-bridge",
"version": "0.1.0",
"description": "GitHub Webhook Bridge for Ansible Tower",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"author": "Øystein Bedin",
"license": "ISC",
"dependencies": {
"body-parser": "^1.15.2",
"cors": "^2.7.1",
"express": "^4.14.0",
"request": "^2.85.0",
"url": "^0.11.0"
}
}

0 comments on commit 32522f3

Please sign in to comment.