Feature/add auditing monkeypatch (#28)

Feature/add auditing monkeypatch
This commit is contained in:
Ethan Arrowood 2018-09-17 16:26:09 -04:00 коммит произвёл GitHub
Родитель 069486b722
Коммит 798431c0f1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 335 добавлений и 3 удалений

14
.vscode/launch.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,14 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach",
"port": 9229
},
]
}

Просмотреть файл

@ -1,7 +1,7 @@
# platform-chaos
[![Build Status](https://travis-ci.org/azure/platform-chaos.svg?branch=master)](https://travis-ci.org/azure/platform-chaos)
[![Build Status](https://travis-ci.org/Azure/platform-chaos.svg?branch=master)](https://travis-ci.org/Azure/platform-chaos)
A node sdk for building services capable of injecting chaos into PaaS offerings. ⚙️ 🌩
@ -83,6 +83,23 @@ Inflates the `resources` from a `req` objects `body` into a collection of object
const objs = require('azure-chaos-fn/parsers').resourcesToObjects(req)
```
### auditer
A documented implementation of the verbose logging format defined in [Auditing](https://github.com/Azure/platform-chaos/wiki/Auditing). The auditer is implemented by monkeypatching the `context` instance _log_ and _done_ methods. As a developer there is little extra effort you need to do to start using the auditer. At the beginning of your extension file, initialize the auditer by passing in the eventName and resources. Then use `context.log` as usual. Everything you log will be added to an intern audit list. When you call `context.done` at the end of your extension, the internal audit list is appended to the `context.res.body` under the `__audits` property.
Initialize the auditer by using the following method:
```js
const index = require('azure-chaos-fn')
index.auditer(/* Azure Function context */, {
eventName: /* Chaos event name : string */,
resources: /* Target resources : string */
})
```
See a fully implemented example in [this]() chaos event.
> TODO: Add an example project for 'this'
## Related Projects
* [platform-chaos-api](https://github.com/Azure/platform-chaos-api) - An API for introducing chaos into Azure PaaS offerings using configurable extensions.

65
audit.js Normal file
Просмотреть файл

@ -0,0 +1,65 @@
const shimmer = require('shimmer')
const assert = require('assert')
const os = require('os')
const uuidv4 = require('uuid/v4')
const auditSystem = `${os.hostname()}-${os.platform()}`
let auditQueue = []
const audit = (extensionLogArgs, auditOptions) => {
const { eventName, resources } = auditOptions
auditQueue.push({
auditId: uuidv4(),
eventName: eventName,
system: auditSystem,
date: new Date().toISOString(),
resources: resources,
extensionLog: typeof extensionLogArgs === 'string' ? [ extensionLogArgs ] : Array.from(extensionLogArgs)
})
}
module.exports = {
audit: audit,
initialize: (context, opts) => {
assert(opts, 'Options object must be defined')
assert(typeof opts.eventName === 'string', 'Event name must be a string')
assert(typeof opts.resources === 'string', 'Resources must be a string')
const auditOptions = opts
auditQueue = []
shimmer.wrap(context, 'log', function (original) {
return function () {
audit(arguments, auditOptions)
return original.apply(this, arguments)
}
})
shimmer.wrap(context, 'done', function (original) {
return function () {
const audits = auditQueue
if (typeof context.res.body === 'undefined') {
context.rex.body = {
__audits: audits
}
} else if (typeof context.res.body === 'string') {
const body = JSON.parse(context.res.body)
body['__audits'] = audits
context.res.body = JSON.stringify(body)
} else if (typeof context.res.body === 'object') {
context.res.body['__audits'] = audits
}
/*
* If context.res.body is not of type string or object
* do not append the audits list to it and return it
* as usual.
* */
return original.apply(this, arguments)
}
})
}
}

Просмотреть файл

@ -1,4 +1,5 @@
module.exports = {
parsers: require('./parsers'),
validators: require('./validators')
validators: require('./validators'),
auditer: require('./audit')
}

134
package-lock.json сгенерированный
Просмотреть файл

@ -4,6 +4,30 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@sinonjs/commons": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.0.2.tgz",
"integrity": "sha512-WR3dlgqJP4QNrLC4iXN/5/2WaLQQ0VijOOkmflqFGVJ6wLEpbSjo7c0ZeGIdtY8Crk7xBBp87sM6+Mkerz7alw==",
"dev": true,
"requires": {
"type-detect": "4.0.8"
}
},
"@sinonjs/formatio": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz",
"integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==",
"dev": true,
"requires": {
"samsam": "1.3.0"
}
},
"@sinonjs/samsam": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-2.0.0.tgz",
"integrity": "sha512-D7VxhADdZbDJ0HjUTMnSQ5xIGb4H2yWpg8k9Sf1T08zfFiQYlaxM8LZydpR4FQ2E6LZJX8IlabNZ5io4vdChwg==",
"dev": true
},
"acorn": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.2.tgz",
@ -889,6 +913,12 @@
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true
},
"just-extend": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-3.0.0.tgz",
"integrity": "sha512-Fu3T6pKBuxjWT/p4DkqGHFRsysc8OauWr4ZRTY9dIx07Y9O0RkoR5jcv28aeD1vuAwhm3nLkDurwLXoALp4DpQ==",
"dev": true
},
"levn": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
@ -935,6 +965,24 @@
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==",
"dev": true
},
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
"dev": true
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
"dev": true
},
"lolex": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.4.tgz",
"integrity": "sha512-Gh6Vffq/piTeHwunLNFR1jFVaqlwK9GMNUxFcsO1cwHyvbRKHwX8UDkxmrDnbcPdHNmpv7z2kxtkkSx5xkNpMw==",
"dev": true
},
"mimic-fn": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
@ -1033,6 +1081,19 @@
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true
},
"nise": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/nise/-/nise-1.4.4.tgz",
"integrity": "sha512-pxE0c9PzgrUTyhfv5p+5eMIdfU2bLEsq8VQEuE0kxM4zP7SujSar7rk9wpI2F7RyyCEvLyj5O7Is3RER5F36Fg==",
"dev": true,
"requires": {
"@sinonjs/formatio": "2.0.0",
"just-extend": "3.0.0",
"lolex": "2.7.4",
"path-to-regexp": "1.7.0",
"text-encoding": "0.6.4"
}
},
"normalize-package-data": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
@ -1155,6 +1216,23 @@
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"path-to-regexp": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
"integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
"dev": true,
"requires": {
"isarray": "0.0.1"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
"dev": true
}
}
},
"path-type": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
@ -1324,6 +1402,12 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"samsam": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz",
"integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==",
"dev": true
},
"semver": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
@ -1345,12 +1429,45 @@
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
"dev": true
},
"shimmer": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz",
"integrity": "sha512-xTCx2vohXC2EWWDqY/zb4+5Mu28D+HYNSOuFzsyRDRvI/e1ICb69afwaUwfjr+25ZXldbOLyp+iDUZHq8UnTag=="
},
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true
},
"sinon": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-6.2.0.tgz",
"integrity": "sha512-gLFZz5UYvOhYzQ+DBzw/OCkmWaLAHlAyQiE2wxUOmAGVdasP9Yw93E+OwZ0UuhW3ReMu1FKniuNsL6VukvC77w==",
"dev": true,
"requires": {
"@sinonjs/commons": "1.0.2",
"@sinonjs/formatio": "2.0.0",
"@sinonjs/samsam": "2.0.0",
"diff": "3.5.0",
"lodash.get": "4.4.2",
"lolex": "2.7.4",
"nise": "1.4.4",
"supports-color": "5.5.0",
"type-detect": "4.0.8"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"slice-ansi": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz",
@ -1463,6 +1580,12 @@
"string-width": "2.1.1"
}
},
"text-encoding": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz",
"integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=",
"dev": true
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -1493,6 +1616,12 @@
"prelude-ls": "1.1.2"
}
},
"type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
"dev": true
},
"uri-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
@ -1502,6 +1631,11 @@
"punycode": "2.1.1"
}
},
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
},
"validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",

Просмотреть файл

@ -20,6 +20,12 @@
"eslint-plugin-node": "^7.0.1",
"eslint-plugin-promise": "^4.0.0",
"eslint-plugin-standard": "^4.0.0",
"mocha": "^5.2.0"
"lodash.isequal": "^4.5.0",
"mocha": "^5.2.0",
"sinon": "^6.2.0"
},
"dependencies": {
"shimmer": "^1.2.0",
"uuid": "^3.3.2"
}
}

Просмотреть файл

@ -1,5 +1,7 @@
const assert = require('assert')
const index = require('../')
const sinon = require('sinon')
const isEqual = require('lodash.isequal')
/* eslint-env node, mocha */
@ -97,4 +99,97 @@ describe('platform-chaos', () => {
assert.equal(res.headers['Authorization'], expectedAccessToken)
})
it('audits correctly', () => {
function contextLog () {
// in reality this would be `console.log(...arguments)`
// but in order to not cluter test we noop this
return () => null
}
function contextDone () {
return () => null
}
const contextLogSpy = sinon.spy(contextLog)
const contextDoneSpy = sinon.spy(contextDone)
const context = {
log: contextLogSpy,
done: contextDoneSpy,
res: { body: {} }
}
index.auditer.initialize(context, {
eventName: 'testEvent',
resources: 'testResource'
})
const logItem1 = {
'prop1': 'important information',
'anotherProp': 'more important info'
}
const logItem2 = 'Hello, World!'
const logItem3 = ['abc', { a: 12 }]
context.log(logItem1)
context.log(logItem2)
context.log(...logItem3)
context.res.body = {
message: 'I am writing to the body'
}
context.done()
assert(contextLogSpy.called, 'context.log should be called')
assert(contextDoneSpy.called, 'context.done should be called')
const body = context.res.body
assert(typeof body === 'object', 'context.res.body should exist as an object')
assert(body.hasOwnProperty('__audits'), 'body contains __audits property')
assert(isEqual(body.__audits[0].extensionLog[0], logItem1), 'log item 1 is added to audit correctly')
assert(isEqual(body.__audits[1].extensionLog[0], logItem2), 'log item 2 is added to audit correctly')
assert(isEqual(body.__audits[2].extensionLog, logItem3), 'log item 3 is added to audit correctly')
})
it('allows user to audit directly', () => {
function contextLog () {
// in reality this would be `console.log(...arguments)`
// but in order to not cluter test we noop this
return () => null
}
function contextDone () {
return () => null
}
const contextLogSpy = sinon.spy(contextLog)
const contextDoneSpy = sinon.spy(contextDone)
const context = {
log: contextLogSpy,
done: contextDoneSpy,
res: { body: {} }
}
const opts = {
eventName: 'testEvent',
resources: 'testResource'
}
index.auditer.initialize(context, opts)
index.auditer.audit('Hello, World!', opts)
context.done()
assert(contextLogSpy.notCalled, 'context.log should not be called')
assert(contextDoneSpy.called, 'context.done should be called')
const body = context.res.body
assert(typeof body === 'object', 'context.res.body should exist as an object')
assert(body.hasOwnProperty('__audits'), 'body contains __audits property')
assert(body.__audits[0].extensionLog[0] === 'Hello, World!', 'log item 1 is added to audit correctly')
})
})