Skip to content

Commit

Permalink
feat(xo-server-hooks): web hooks on XO API calls
Browse files Browse the repository at this point in the history
See #1946
  • Loading branch information
pdonias committed Nov 30, 2018
1 parent 12dd40d commit 1a69ba7
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 13 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,11 +5,13 @@
### Enhancements

- [Users] Display user groups [#3719](https://github.com/vatesfr/xen-orchestra/issues/3719) (PR [#3740](https://github.com/vatesfr/xen-orchestra/pull/3740))
- Web hooks plugin [#1946](https://github.com/vatesfr/xen-orchestra/issues/1946)

### Bug fixes

### Released packages

- xo-server-hooks: 0.1.0
- xo-server v5.32.0
- xo-web v5.32.0

Expand Down
10 changes: 10 additions & 0 deletions packages/xo-server-hooks/.npmignore
@@ -0,0 +1,10 @@
/examples/
example.js
example.js.map
*.example.js
*.example.js.map

/test/
/tests/
*.spec.js
*.spec.js.map
48 changes: 48 additions & 0 deletions packages/xo-server-hooks/README.md
@@ -0,0 +1,48 @@
# xo-server-hooks [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)

## Install

Installation of the [npm package](https://npmjs.org/package/xo-server-hooks):

```
> npm install --global xo-server-hooks
```

## Usage

Like all other xo-server plugins, it can be configured directly via
the web interface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html).

## Development

```
# Install dependencies
> npm install
# Run the tests
> npm test
# Continuously compile
> npm run dev
# Continuously run the tests
> npm run dev-test
# Build for production (automatically called by npm install)
> npm run build
```

## Contributions

Contributions are _very_ welcomed, either on the documentation or on
the code.

You may:

- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
you've encountered;
- fork and create a pull request.

## License

AGPL3 © [Vates SAS](https://vates.fr)
70 changes: 70 additions & 0 deletions packages/xo-server-hooks/package.json
@@ -0,0 +1,70 @@
{
"name": "xo-server-hooks",
"version": "0.1.0",
"license": "ISC",
"description": "",
"keywords": [
"hooks",
"orchestra",
"plugin",
"xen",
"xen-orchestra",
"xo-server"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-hooks",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Pierre Donias",
"email": "pierre.donias@gmail.com"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"engines": {
"node": ">=4"
},
"dependencies": {
"babel-runtime": "^6.20.0",
"http-request-plus": "^0.5.0",
"lodash": "^4.17.10"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-env": "^1.5.2",
"babel-preset-stage-3": "^6.16.0",
"cross-env": "^5.1.3",
"rimraf": "^2.5.4"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
},
"babel": {
"plugins": [
"transform-runtime"
],
"presets": [
[
"env",
{
"targets": {
"node": 4
}
}
],
"stage-3"
]
}
}
82 changes: 82 additions & 0 deletions packages/xo-server-hooks/src/index.js
@@ -0,0 +1,82 @@
import request from 'http-request-plus'
import { groupBy } from 'lodash'

const makeRequest = (url, method, params) =>
request(url, {
body: JSON.stringify({ method, params }),
headers: { 'content-type': 'application/json' },
method: 'POST',
})

class XoServerHooks {
constructor ({ xo }) {
this._xo = xo

// Defined in configure().
this._conf = null
this._key = null
this.handleHook = this.handleHook.bind(this)
}

configure (configuration) {
this._conf = groupBy(configuration, 'method')
}

handleHook (method, params) {
let hooks
if ((hooks = this._conf[method]) === undefined) {
return
}
return Promise.all(hooks.map(({ url }) => makeRequest(url, method, params)))
}

load () {
this._xo.on('call', this.handleHook)
}

unload () {
this._xo.removeListener('call', this.handleHook)
}

test ({ url }) {
return makeRequest(url, 'vm.start', { id: 'foobar' })
}
}

export const configurationSchema = ({ xo: { apiMethods } }) => ({
description: 'Bind XO API calls to HTTP requests.',
type: 'array',
items: {
type: 'object',
title: 'Hook',
properties: {
method: {
description: 'The method to be bound to',
enum: Object.keys(apiMethods),
title: 'Method',
type: 'string',
},
url: {
description: 'The full URL you wish the request to be sent to',
title: 'URL',
type: 'string',
},
},
required: ['method', 'url'],
},
})

export const testSchema = {
type: 'object',
description:
'The test will simulate a hook to vm.start with {"id":"foobar"} as a parameter',
properties: {
url: {
title: 'URL',
type: 'string',
description: 'The URL the test request will be sent to',
},
},
}

export default opts => new XoServerHooks(opts)
36 changes: 23 additions & 13 deletions packages/xo-server/src/index.js
Expand Up @@ -225,24 +225,34 @@ async function registerPlugin(pluginPath, pluginName) {
})()

// Supports both “normal” CommonJS and Babel's ES2015 modules.
const {
default: factory = plugin,
let {
default: instance = plugin,
configurationSchema,
configurationPresets,
testSchema,
} = plugin

// The default export can be either a factory or directly a plugin
// instance.
const instance = isFunction(factory)
? factory({
xo: this,
getDataDir: () => {
const dir = `${this._config.datadir}/${pluginName}`
return ensureDir(dir).then(() => dir)
},
})
: factory
const handleFactory = factory =>
isFunction(factory)
? factory({
xo: this,
getDataDir: () => {
const dir = `${this._config.datadir}/${pluginName}`
return ensureDir(dir).then(() => dir)
},
})
: factory
;[
instance,
configurationSchema,
configurationPresets,
testSchema,
] = await Promise.all([
handleFactory(instance),
handleFactory(configurationSchema),
handleFactory(configurationPresets),
handleFactory(testSchema),
])

await this.registerPlugin(
pluginName,
Expand Down
2 changes: 2 additions & 0 deletions packages/xo-server/src/xo-mixins/api.js
Expand Up @@ -259,6 +259,8 @@ export default class Api {

let result = await method.call(context, resolvedParams)

this._xo.emit('call', name, params)

// If nothing was returned, consider this operation a success
// and return true.
if (result === undefined) {
Expand Down

0 comments on commit 1a69ba7

Please sign in to comment.