From 65ec3cb09f0ef4cb36a754e5666d7e9936bc6ee8 Mon Sep 17 00:00:00 2001 From: wyvern8 Date: Wed, 14 Mar 2018 19:19:14 +1100 Subject: [PATCH 1/6] feat(proxy): respect https_proxy env var --- README.md | 7 +++++ package-lock.json | 50 ++++++++++++++++++++++++++---------- package.json | 2 ++ src/LaunchDarklyApiClient.js | 11 ++++++++ 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 43aadf2..b7cf11a 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,13 @@ ldutils getFeatureFlags ldutils upsertCustomRole '[{"resources":["proj/*"],"actions":["*"],"effect":"allow"}]' ``` +### proxies +If you are executing ldutils via a proxy, just set the https_proxy env var, eg: +``` +export https_proxy=http://localhost:3128 +``` + + ### commandline modes and parameters The command line modes and parameters map directly to the functions exposed for use in nodejs apps. Each call to `ldutils` takes a 'mode', and associated parameters as below: diff --git a/package-lock.json b/package-lock.json index 1013b25..e961ec2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -338,7 +338,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", - "dev": true, "requires": { "es6-promisify": "5.0.0" } @@ -3021,6 +3020,17 @@ "requires": { "node-fetch": "1.7.3", "whatwg-fetch": "2.0.3" + }, + "dependencies": { + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "0.1.12", + "is-stream": "1.1.0" + } + } } }, "cross-spawn": { @@ -3581,14 +3591,12 @@ "es6-promise": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", - "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", - "dev": true + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" }, "es6-promisify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, "requires": { "es6-promise": "4.2.4" } @@ -5999,7 +6007,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.0.tgz", "integrity": "sha512-uUWcfXHvy/dwfM9bqa6AozvAjS32dZSTUYd/4SEpYKRg6LEcPLshksnQYRudM9AyNvUARMfAg5TLjUDyX/K4vA==", - "dev": true, "requires": { "agent-base": "4.2.0", "debug": "3.1.0" @@ -6009,7 +6016,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -6549,6 +6555,18 @@ "requires": { "node-fetch": "1.7.3", "whatwg-fetch": "2.0.3" + }, + "dependencies": { + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dev": true, + "requires": { + "encoding": "0.1.12", + "is-stream": "1.1.0" + } + } } }, "isomorphic-form-data": { @@ -7772,13 +7790,9 @@ } }, "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "0.1.12", - "is-stream": "1.1.0" - } + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.1.tgz", + "integrity": "sha1-NpynC4L1DIZJYQSmx3bSdPTkotQ=" }, "node-forge": { "version": "0.7.1", @@ -11508,6 +11522,16 @@ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dev": true, + "requires": { + "encoding": "0.1.12", + "is-stream": "1.1.0" + } + }, "supports-color": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", diff --git a/package.json b/package.json index 2abd278..68eae6f 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,11 @@ "bunyan-format": "^0.2.1", "fast-json-patch": "^2.0.6", "globule": "^1.2.0", + "https-proxy-agent": "^2.2.0", "js-yaml": "^3.10.0", "json-format": "^1.0.1", "lodash": "^4.17.5", + "node-fetch": "^2.1.1", "swagger-client": "^3.4.7", "webpack": "^4.0.0" }, diff --git a/src/LaunchDarklyApiClient.js b/src/LaunchDarklyApiClient.js index 7f55089..4d32754 100644 --- a/src/LaunchDarklyApiClient.js +++ b/src/LaunchDarklyApiClient.js @@ -2,6 +2,8 @@ import Swagger from 'swagger-client'; import jsYaml from 'js-yaml'; import { default as fs } from 'fs'; import { default as json } from 'format-json'; +import { default as HttpsProxyAgent } from 'https-proxy-agent'; +import { default as fetch } from 'node-fetch'; /** * @class @@ -20,6 +22,13 @@ export class LaunchDarklyApiClient { static async create(API_TOKEN, log, swaggerYamlString) { log.debug(`creating api client with token: ${API_TOKEN}`); + let proxy = process.env.https_proxy; + let agent = null; + if (proxy) { + agent = new HttpsProxyAgent(proxy); + log.debug(`using proxy from env var 'https_proxy'`); + } + // swagger.yaml from https://launchdarkly.github.io/ld-openapi/swagger.yaml const swaggerYaml = swaggerYamlString || fs.readFileSync(__dirname + `/../swagger.yaml`, 'utf-8').toString(); const swaggerJson = jsYaml.safeLoad(swaggerYaml); @@ -28,6 +37,8 @@ export class LaunchDarklyApiClient { spec: swaggerJson, usePromise: true, requestInterceptor: req => { + req.userFetch = fetch; + req.agent = agent; req.headers.Authorization = API_TOKEN; log.debug(`REQUEST: ${json.plain(req)}`); From e2af5e624eccaa78b0633e8586a0885b6accf1c9 Mon Sep 17 00:00:00 2001 From: wyvern8 Date: Wed, 14 Mar 2018 20:04:19 +1100 Subject: [PATCH 2/6] test(roles): add bulk load folder test, and fix return structure --- src/LaunchDarklyUtilsRoles.js | 10 ++-- test/LaunchDarklyUtilsRoles.spec.js | 58 +++++++++++++++++++ .../custom-roles-bulk-load-file-one.json | 28 +++++++++ .../custom-roles-bulk-load-file-two.json | 53 +++++++++++++++++ 4 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/bulkroles/custom-roles-bulk-load-file-one.json create mode 100644 test/fixtures/bulkroles/custom-roles-bulk-load-file-two.json diff --git a/src/LaunchDarklyUtilsRoles.js b/src/LaunchDarklyUtilsRoles.js index 3bab351..68dc5db 100644 --- a/src/LaunchDarklyUtilsRoles.js +++ b/src/LaunchDarklyUtilsRoles.js @@ -254,13 +254,15 @@ export class LaunchDarklyUtilsRoles { let globMatch = folderPath + '/*.json'; this.log.debug(`Looking for Files with Pattern '${globMatch}'`); let fileArray = globule.find(globMatch); - let results = []; + let promises = []; let that = this; fileArray.forEach(async function(file) { that.log.debug(`Found File '${file}'. Calling 'bulkUpsertCustomRoles'`); - let result = await that.bulkUpsertCustomRoles(file); - results.push(result); + promises.push(that.bulkUpsertCustomRoles(file)); + }); + // resolve all and flatten into single result array + return Promise.all(promises).then(results => { + return Promise.resolve([].concat.apply([], results)); }); - return results; } } diff --git a/test/LaunchDarklyUtilsRoles.spec.js b/test/LaunchDarklyUtilsRoles.spec.js index 2194d12..af87edd 100644 --- a/test/LaunchDarklyUtilsRoles.spec.js +++ b/test/LaunchDarklyUtilsRoles.spec.js @@ -278,4 +278,62 @@ describe('LaunchDarklyUtilsRoles', function() { }); }); }); + + describe('bulkUpsertCustomRoleFolder', function() { + before(done => { + let scope = nock('https://app.launchdarkly.com') + // update + .get('/api/v2/roles/test-role-one') + .replyWithFile(200, __dirname + '/fixtures/custom-roles-get.json', { + 'Content-Type': 'application/json' + }) + .get('/api/v2/roles/test-role-one') + .replyWithFile(200, __dirname + '/fixtures/custom-roles-get.json', { + 'Content-Type': 'application/json' + }) + .patch('/api/v2/roles/test-role-one') + .replyWithFile(200, __dirname + '/fixtures/custom-roles-update.json', { + 'Content-Type': 'application/json' + }) + + .get('/api/v2/roles/test-role-two') + .replyWithFile(200, __dirname + '/fixtures/custom-roles-get.json', { + 'Content-Type': 'application/json' + }) + .get('/api/v2/roles/test-role-two') + .replyWithFile(200, __dirname + '/fixtures/custom-roles-get.json', { + 'Content-Type': 'application/json' + }) + .patch('/api/v2/roles/test-role-two') + .replyWithFile(200, __dirname + '/fixtures/custom-roles-update.json', { + 'Content-Type': 'application/json' + }) + + // create + .get('/api/v2/roles/test-role-three') + .reply( + 404, + { error: 'Not Found' }, + { + 'Content-Type': 'application/json' + } + ) + .post('/api/v2/roles') + .replyWithFile(200, __dirname + '/fixtures/custom-roles-create.json', { + 'Content-Type': 'application/json' + }); + + assert(scope); + done(); + }); + + it('should operate on json bulkload file', async function() { + let expected = JSON.parse( + fs.readFileSync(__dirname + '/fixtures/custom-roles-bulk-load-result.json', 'utf-8') + ); + return ldutils.roles.bulkUpsertCustomRoleFolder(__dirname + '/fixtures/bulkroles').then(actual => { + expect(actual).to.deep.equal(expected); + }); + }); + }); }); diff --git a/test/fixtures/bulkroles/custom-roles-bulk-load-file-one.json b/test/fixtures/bulkroles/custom-roles-bulk-load-file-one.json new file mode 100644 index 0000000..e995dc1 --- /dev/null +++ b/test/fixtures/bulkroles/custom-roles-bulk-load-file-one.json @@ -0,0 +1,28 @@ +[ + { + "name": "Test role one", + "key": "test-role-one", + "description": "Just testing one", + "policy": [ + { + "resources": [ + "proj/*" + ], + "actions": [ + "*" + ], + "effect": "deny" + }, + { + "resources": [ + "proj/*" + ], + "actions": [ + "read" + ], + "effect": "allow" + } + ] + } +] + diff --git a/test/fixtures/bulkroles/custom-roles-bulk-load-file-two.json b/test/fixtures/bulkroles/custom-roles-bulk-load-file-two.json new file mode 100644 index 0000000..9aa26e7 --- /dev/null +++ b/test/fixtures/bulkroles/custom-roles-bulk-load-file-two.json @@ -0,0 +1,53 @@ +[ + { + "name": "Test role two", + "key": "test-role-two", + "description": "Just testing two", + "policy": [ + { + "resources": [ + "proj/*" + ], + "actions": [ + "*" + ], + "effect": "deny" + }, + { + "resources": [ + "proj/*" + ], + "actions": [ + "read" + ], + "effect": "allow" + } + ] + }, + { + "name": "Test role three", + "key": "test-role-three", + "description": "Just testing three", + "policy": [ + { + "resources": [ + "proj/*" + ], + "actions": [ + "*" + ], + "effect": "deny" + }, + { + "resources": [ + "proj/*" + ], + "actions": [ + "read" + ], + "effect": "allow" + } + ] + } +] + From 490adc3a00550b533128e1c0b1e1115e295d2e9c Mon Sep 17 00:00:00 2001 From: wyvern8 Date: Wed, 14 Mar 2018 20:10:03 +1100 Subject: [PATCH 3/6] refactor(proxy): update log to show proxy value --- src/LaunchDarklyApiClient.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarklyApiClient.js b/src/LaunchDarklyApiClient.js index 4d32754..b93b564 100644 --- a/src/LaunchDarklyApiClient.js +++ b/src/LaunchDarklyApiClient.js @@ -26,7 +26,7 @@ export class LaunchDarklyApiClient { let agent = null; if (proxy) { agent = new HttpsProxyAgent(proxy); - log.debug(`using proxy from env var 'https_proxy'`); + log.debug(`using proxy from env var https_proxy=${proxy}`); } // swagger.yaml from https://launchdarkly.github.io/ld-openapi/swagger.yaml From 05f1e70d96b09e2da9254e0ef6dd20932689e933 Mon Sep 17 00:00:00 2001 From: wyvern8 Date: Wed, 14 Mar 2018 20:32:35 +1100 Subject: [PATCH 4/6] feat(scripts): updated diff tools --- package-lock.json | 47 +++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + scripts/jqJsonDiff.sh | 2 +- scripts/jsonDiff.sh | 12 +++++++++++ 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100755 scripts/jsonDiff.sh diff --git a/package-lock.json b/package-lock.json index e961ec2..c57cabf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2334,6 +2334,14 @@ "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", "dev": true }, + "cli-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-0.1.7.tgz", + "integrity": "sha1-rcMgD6RxzCEbDaf1ZrcemLnWc0c=", + "requires": { + "es5-ext": "0.8.2" + } + }, "cli-cursor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", @@ -3369,6 +3377,14 @@ "randombytes": "2.0.6" } }, + "difflib": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", + "integrity": "sha1-teMDYabbAjF21WKJLbhZQKcY9H4=", + "requires": { + "heap": "0.2.6" + } + }, "dir-glob": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", @@ -3449,6 +3465,14 @@ "pify": "2.3.0" } }, + "dreamopt": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/dreamopt/-/dreamopt-0.6.0.tgz", + "integrity": "sha1-2BPM2sjTnYrVJndVFKE92mZNa0s=", + "requires": { + "wordwrap": "0.0.2" + } + }, "dtrace-provider": { "version": "0.8.6", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.6.tgz", @@ -3588,6 +3612,11 @@ "is-arrayish": "0.2.1" } }, + "es5-ext": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.8.2.tgz", + "integrity": "sha1-q6jZ4ZQ6iVrJaDemKjmz9V7NlKs=" + }, "es6-promise": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", @@ -5919,6 +5948,11 @@ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, + "heap": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.6.tgz", + "integrity": "sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=" + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -6791,6 +6825,16 @@ "integrity": "sha512-FD/SedD78LCdSvJaOUQAXseT8oQBb5z6IVYaQaCrVUlu9zOAr1BDdKyVYQaSD/GDsAMrXpKcOyBD4LIl8nfjHw==", "dev": true }, + "json-diff": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/json-diff/-/json-diff-0.5.2.tgz", + "integrity": "sha512-N7oapTQdD4rLMUtA7d1HATCPY/BpHuSNL1mhvIuoS0u5NideDvyR+gB/ntXB7ejFz/LM0XzPLNUJQcC68n5sBw==", + "requires": { + "cli-color": "0.1.7", + "difflib": "0.2.4", + "dreamopt": "0.6.0" + } + }, "json-format": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-format/-/json-format-1.0.1.tgz", @@ -13910,8 +13954,7 @@ "wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" }, "wordwrapjs": { "version": "3.0.0", diff --git a/package.json b/package.json index 68eae6f..53c256b 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "globule": "^1.2.0", "https-proxy-agent": "^2.2.0", "js-yaml": "^3.10.0", + "json-diff": "^0.5.2", "json-format": "^1.0.1", "lodash": "^4.17.5", "node-fetch": "^2.1.1", diff --git a/scripts/jqJsonDiff.sh b/scripts/jqJsonDiff.sh index 93900db..55a0252 100755 --- a/scripts/jqJsonDiff.sh +++ b/scripts/jqJsonDiff.sh @@ -10,4 +10,4 @@ if [ $# -eq 0 ]; then exit 1 fi -diff <(cat "$1" | jq . --sort-keys) <(cat "$2" | jq . --sort-keys) +diff -U5 <(cat "$1" | jq . --sort-keys) <(cat "$2" | jq . --sort-keys) diff --git a/scripts/jsonDiff.sh b/scripts/jsonDiff.sh new file mode 100755 index 0000000..6ab4d04 --- /dev/null +++ b/scripts/jsonDiff.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +display_usage() { + echo "This script must be run with two files to compare." + echo "eg. ./jsonDiff.sh ./before.json ../after.json" +} + +if [ $# -eq 0 ]; then + display_usage + exit 1 +fi +node ../node_modules/json-diff/bin/json-diff.js $1 $2 \ No newline at end of file From 05edd2ffa4eaa5b200fd75d19b7d0eda16438622 Mon Sep 17 00:00:00 2001 From: wyvern8 Date: Wed, 14 Mar 2018 20:38:23 +1100 Subject: [PATCH 5/6] docs(commandline): tidy --- ldutils | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ldutils b/ldutils index 84f6f28..343c419 100755 --- a/ldutils +++ b/ldutils @@ -175,7 +175,7 @@ new LaunchDarklyUtils().create(process.env.LAUNCHDARKLY_API_TOKEN, log).then(fun program .command('getTeamMember ') - .description('get a team members by id') + .description('get a team member by id') .action(function(memberId) { ldUtils.members.getTeamMember(memberId).then(function(response) { console.log(json.plain(response)); @@ -193,7 +193,7 @@ new LaunchDarklyUtils().create(process.env.LAUNCHDARKLY_API_TOKEN, log).then(fun program .command('getTeamMemberCustomRoles ') - .description('get a team member by email address') + .description('get a team members custom roles by email address') .action(function(emailAddress) { ldUtils.members.getTeamMemberCustomRoles(emailAddress).then(function(response) { console.log(json.plain(response)); @@ -201,7 +201,7 @@ new LaunchDarklyUtils().create(process.env.LAUNCHDARKLY_API_TOKEN, log).then(fun }); program.on('--help', function() { - console.log('\n See https://github.com/wyvern8/launchdarkly-nodeutils for details.\n'); + console.log('\n See https://github.com/wyvern8/launchdarkly-nodeutils/API.md for examples.\n'); }); if (process.argv.length < 3) { From 762cbc4a53052455094e7e527dc03895684fabca Mon Sep 17 00:00:00 2001 From: Ewen M Date: Thu, 15 Mar 2018 11:27:08 +1100 Subject: [PATCH 6/6] fix(proxy): Check for http_proxy as well --- src/LaunchDarklyApiClient.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarklyApiClient.js b/src/LaunchDarklyApiClient.js index b93b564..d2c9352 100644 --- a/src/LaunchDarklyApiClient.js +++ b/src/LaunchDarklyApiClient.js @@ -22,7 +22,7 @@ export class LaunchDarklyApiClient { static async create(API_TOKEN, log, swaggerYamlString) { log.debug(`creating api client with token: ${API_TOKEN}`); - let proxy = process.env.https_proxy; + let proxy = process.env.http_proxy || process.env.https_proxy || null; let agent = null; if (proxy) { agent = new HttpsProxyAgent(proxy);