From 52a885166efb82d21566e4b8d8b53acbe928dacb Mon Sep 17 00:00:00 2001 From: Chris Forbes Date: Sun, 8 Nov 2020 14:59:35 +0000 Subject: [PATCH] Add ability to pass options to exec from a command (#23) * Update integration tests * Allow options to be passed down to shelljs * Move partialsPath to constants file * Add test for terraformDiffError exception * Cleanup * Set version to 0.4.0 --- README.md | 46 +-- example.js | 3 +- package-lock.json | 2 +- package.json | 6 +- src/constants.js | 1 + src/index.js | 99 ++--- src/index.test.js | 350 ++++++------------ ...ation.test.js => integration-0.11.test.js} | 0 src/integration-0.13.test.js | 60 +++ src/registerPartials.js | 2 +- src/shExec.test.js | 60 ++- 11 files changed, 286 insertions(+), 343 deletions(-) rename src/{integration.test.js => integration-0.11.test.js} (100%) create mode 100644 src/integration-0.13.test.js diff --git a/README.md b/README.md index 223c424..2f3dc45 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status](https://travis-ci.org/CDA0/terrajs.svg?branch=master)](https://travis-ci.org/CDA0/terrajs) [![npm version](https://badge.fury.io/js/%40cda0%2Fterrajs.svg)](https://badge.fury.io/js/%40cda0%2Fterrajs) -A module to help with creating terraform commands. +A module to help with creating Terraform commands. ## Supported Commands @@ -31,60 +31,46 @@ A module to help with creating terraform commands. ## Usage -Terrajs will run terraform commands from the directory pass in with `terraformDir`. +Terrajs will run Terraform commands from the directory passed in with `terraformDir`. ```js const tf = new Terrajs( { terraformDir: 'path/to/files.tf' } ); const cmdString = tf.init({ backendConfig: { key: 'MY_KEY' } }); ``` -To view the generated terraform command without running: +To view the generated Terraform command without running: ```js const tf = new Terrajs({ execute: false, terraformDir: 'path/to/files.tf' }); tf.init({ backendConfig: { key: 'MY_KEY' } }); ``` -### Variables - -Variables are mapped from JS camelCase convention to Terraform CLI snake_case convention. For example: +If you need to use a Terraform binary that's not on your path as `terraform`, +then you can tell Terrajs where to find it in the constructor. ```js -tf.plan({ - var: { - subscriptionId: '123', - tenantId: 'abc' - } -}); -``` - -...will be mapped to the following terraform shell command: - -```bash -terraform plan -var subscription_id=123 -var tenant_id=abc +const tf = new Terrajs( { command: 'terraform12', terraformDir: 'path/to/files.tf' } ); +const cmdString = tf.init({ backendConfig: { key: 'MY_KEY' } }); ``` -### Lists +### Variables -Passing a list variable requires some additional preparation. For example: +Variables are mapped from JavaScript camelCase convention to Terraform CLI snake_case convention. For example: ```js -const subnetArray = [ 'subnetA', 'subnetB' ] -const subnetString = subnetArray.length - ? `[\\"${subnetArray.join('\\", \\"')}\\"]` - : ''; - tf.plan({ var: { - subnets: subnetString + subscriptionId: '123', + tenantId: 'abc', + zones: ['A', 'B'], } }); ``` -...will be mapped to the following terraform shell command: +...will be mapped to the following Terraform shell command: ```bash -terraform plan -var "subnets=[\"subnetA\", \"subnetB\"]" +terraform plan -var subscription_id=123 -var tenant_id=abc -var 'zones=["A","B"]' ``` ## Test @@ -97,8 +83,8 @@ terraform plan -var "subnets=[\"subnetA\", \"subnetB\"]" ## Contributing -Commands live in the `templates` dir. +Terraform commands live in the `templates` directory. -Each command has a line for each partial. +Each command has a line for each partial, found in the `partials` directory. A partial contains the logic for a command line argument. diff --git a/example.js b/example.js index 168cb63..5023e6c 100644 --- a/example.js +++ b/example.js @@ -17,6 +17,7 @@ const example = async () => { await tf.import({ address: 'a_resource.exmple', id: '/unique/identifier' }), await tf.refresh({ var: { environment: 'SP' }, target: ['a_resource.example'] }), await tf.plan({ var: { environment: 'SP', location: 'westeurope' } }), + await tf.plan({ var: { environments: ['SP', 'XY', 'AB'] } }), await tf.apply({ var: { environment: 'SP', location: 'westeurope' } }), await tf.apply({ var: { environment: 'SP', location: 'westeurope' }, plan: 'terraform.tfplan' }), await tf.output({ json: true }), @@ -28,7 +29,7 @@ const example = async () => { await tf.workspaceNew({ name: 'dev' }), await tf.workspaceSelect({ name: 'dev' }), await tf.workspaceShow(), - ].forEach(command => console.log(command)); + ].forEach((command) => console.log(command)); }; example(); diff --git a/package-lock.json b/package-lock.json index b8a0c50..26022fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@cda0/terrajs", - "version": "0.3.0", + "version": "0.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 087e8f5..c1df7f8 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "@cda0/terrajs", - "version": "0.3.0", - "description": "A node interface to terraform", + "version": "0.4.0", + "description": "A node interface to Terraform", "main": "src/index.js", "scripts": { "test": "mocha ./src/**/*.test.js", "coverage": "nyc npm run test", - "lint": "eslint ./src/**/*.js", + "lint": "eslint ./src/**/*.js ./example.js", "ci": "npm run lint && npm run coverage" }, "repository": "github:cda0/terrajs", diff --git a/src/constants.js b/src/constants.js index d74345a..c1bf5d4 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,6 +1,7 @@ const path = require('path'); const constants = { + partialsPath: path.join(__dirname, '..', 'partials'), templatePath: path.join(__dirname, '..', 'templates'), }; diff --git a/src/index.js b/src/index.js index d10158f..fe0064d 100644 --- a/src/index.js +++ b/src/index.js @@ -5,10 +5,10 @@ const merge = require('deepmerge'); const Handlebars = require('handlebars'); const { templatePath } = require('./constants'); +const defaults = require('./defaults'); const registerPartials = require('./registerPartials'); const registerHelpers = require('./registerHelpers'); const shExec = require('./shExec'); -const defaults = require('./defaults'); class Terrajs { constructor(options = {}) { @@ -40,94 +40,103 @@ class Terrajs { return tfCmd; } - async buildAndExec(action, args) { + async buildAndExec(action, args, execOptions) { const cmd = await this.buildCommand(action, args); - const opts = this.terraformDir ? { cwd: this.terraformDir } : {}; + const opts = { ...execOptions }; + if (this.terraformDir) { + opts.cwd = this.terraformDir; + } return shExec(cmd, opts); } - apply(args = {}) { - return this.execute ? this.buildAndExec('apply', args) : this.buildCommand('apply', args); + async commandWrapper(action, args, execOptions) { + return this.execute + ? this.buildAndExec(action, args, execOptions) + : this.buildCommand(action, args); + } + + async apply(args = {}, execOptions = {}) { + return this.commandWrapper('apply', args, execOptions); } - destroy(args = {}) { - return this.execute ? this.buildAndExec('destroy', args) : this.buildCommand('destroy', args); + async destroy(args = {}, execOptions = {}) { + return this.commandWrapper('destroy', args, execOptions); } - fmt(args = {}) { - return this.execute ? this.buildAndExec('fmt', args) : this.buildCommand('fmt', args); + async fmt(args = {}, execOptions = {}) { + return this.commandWrapper('fmt', args, execOptions); } - get(args = {}) { - return this.execute ? this.buildAndExec('get', args) : this.buildCommand('get', args); + async get(args = {}, execOptions = {}) { + return this.commandWrapper('get', args, execOptions); } - graph(args = {}) { - return this.execute ? this.buildAndExec('graph', args) : this.buildCommand('graph', args); + async graph(args = {}, execOptions = {}) { + return this.commandWrapper('graph', args, execOptions); } - init(args = {}) { - return this.execute ? this.buildAndExec('init', args) : this.buildCommand('init', args); + async init(args = {}, execOptions = {}) { + return this.commandWrapper('init', args, execOptions); } - import(args = {}) { - return this.execute ? this.buildAndExec('import', args) : this.buildCommand('import', args); + async import(args = {}, execOptions = {}) { + return this.commandWrapper('import', args, execOptions); } - output(args = {}) { - return this.execute ? this.buildAndExec('output', args) : this.buildCommand('output', args); + async output(args = {}, execOptions = {}) { + return this.commandWrapper('output', args, execOptions); } - plan(args = {}) { - return this.execute ? this.buildAndExec('plan', args) : this.buildCommand('plan', args); + async plan(args = {}, execOptions = {}) { + return this.commandWrapper('plan', args, execOptions); } - providers(args = {}) { - return this.execute ? this.buildAndExec('providers', args) : this.buildCommand('providers', args); + async providers(args = {}, execOptions = {}) { + return this.commandWrapper('providers', args, execOptions); } - refresh(args = {}) { - return this.execute ? this.buildAndExec('refresh', args) : this.buildCommand('refresh', args); + async refresh(args = {}, execOptions = {}) { + return this.commandWrapper('refresh', args, execOptions); } - show(args = {}) { - return this.execute ? this.buildAndExec('show', args) : this.buildCommand('show', args); + async show(args = {}, execOptions = {}) { + return this.commandWrapper('show', args, execOptions); } - taint(args = {}) { - return this.execute ? this.buildAndExec('taint', args) : this.buildCommand('taint', args); + async taint(args = {}, execOptions = {}) { + return this.commandWrapper('taint', args, execOptions); } - untaint(args = {}) { - return this.execute ? this.buildAndExec('untaint', args) : this.buildCommand('untaint', args); + async untaint(args = {}, execOptions = {}) { + return this.commandWrapper('untaint', args, execOptions); } - validate(args = {}) { - return this.execute ? this.buildAndExec('validate', args) : this.buildCommand('validate', args); + async validate(args = {}, execOptions = {}) { + return this.commandWrapper('validate', args, execOptions); } - version(args = {}) { - return this.execute ? this.buildAndExec('version', args) : this.buildCommand('version', args); + async version(args = {}, execOptions = {}) { + return this.commandWrapper('version', args, execOptions); } - workspaceDelete(args = {}) { - return this.execute ? this.buildAndExec('workspace-delete', args) : this.buildCommand('workspace-delete', args); + async workspaceDelete(args = {}, execOptions = {}) { + return this.commandWrapper('workspace-delete', args, execOptions); } - workspaceList(args = {}) { - return this.execute ? this.buildAndExec('workspace-list', args) : this.buildCommand('workspace-list', args); + async workspaceList(args = {}, execOptions = {}) { + return this.commandWrapper('workspace-list', args, execOptions); } - workspaceNew(args = {}) { - return this.execute ? this.buildAndExec('workspace-new', args) : this.buildCommand('workspace-new', args); + async workspaceNew(args = {}, execOptions = {}) { + return this.commandWrapper('workspace-new', args, execOptions); } - workspaceSelect(args = {}) { - return this.execute ? this.buildAndExec('workspace-select', args) : this.buildCommand('workspace-select', args); + async workspaceSelect(args = {}, execOptions = {}) { + return this.commandWrapper('workspace-select', args, execOptions); } - workspaceShow(args = {}) { - return this.execute ? this.buildAndExec('workspace-show', args) : this.buildCommand('workspace-show', args); + async workspaceShow(args = {}, execOptions = {}) { + return this.commandWrapper('workspace-show', args, execOptions); } } diff --git a/src/index.test.js b/src/index.test.js index 4b734d7..db392f0 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -157,7 +157,7 @@ describe('index', () => { }); }); - describe('apply', () => { + describe('commandWrapper', () => { let Terrajs; let tf; let buildCommand; @@ -176,594 +176,454 @@ describe('index', () => { it('should call build command if execute is not set', async () => { tf.execute = false; - await tf.apply(); - td.verify(buildCommand('apply', {})); + await tf.commandWrapper('any-command', {}); + td.verify(buildCommand('any-command', {})); }); it('should call execute command if execute is set', async () => { - await tf.apply(); - td.verify(buildAndExec('apply', {})); + await tf.commandWrapper('any-command', {}, {}); + td.verify(buildAndExec('any-command', {}, {})); }); }); - describe('destroy', () => { + describe('apply', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); - td.when(shExec('terraform -version'), { ignoreExtraArgs: true }).thenReturn('Terraform v0.12.15'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; - await tf.destroy(); - td.verify(buildCommand('destroy', {})); + it('should call commandWrapper', async () => { + await tf.apply(); + td.verify(commandWrapper('apply', {}, {})); + }); + }); + + describe('destroy', () => { + let Terrajs; + let tf; + let commandWrapper; + + beforeEach(() => { + td.replace('./registerHelpers'); + td.replace('./registerPartials'); + Terrajs = require('./index'); + tf = new Terrajs(); + commandWrapper = td.replace(tf, 'commandWrapper'); }); - it('should call execute command if execute is set', async () => { + afterEach(() => td.reset()); + + it('should call commandWrapper', async () => { await tf.destroy(); - td.verify(buildAndExec('destroy', {})); + td.verify(commandWrapper('destroy', {}, {})); }); }); describe('fmt', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; - await tf.fmt(); - td.verify(buildCommand('fmt', {})); - }); - - it('should call execute command if execute is set', async () => { + it('should call commandWrapper', async () => { await tf.fmt(); - td.verify(buildAndExec('fmt', {})); + td.verify(commandWrapper('fmt', {}, {})); }); }); describe('get', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; + it('should call commandWrapper', async () => { await tf.get(); - td.verify(buildCommand('get', {})); - }); - - it('should call execute command if execute is set', async () => { - await tf.get(); - td.verify(buildAndExec('get', {})); + td.verify(commandWrapper('get', {}, {})); }); }); describe('graph', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; + it('should call commandWrapper', async () => { await tf.graph(); - td.verify(buildCommand('graph', {})); - }); - - it('should call execute command if execute is set', async () => { - await tf.graph(); - td.verify(buildAndExec('graph', {})); + td.verify(commandWrapper('graph', {}, {})); }); }); describe('init', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; + it('should call commandWrapper', async () => { await tf.init(); - td.verify(buildCommand('init', {})); - }); - - it('should call execute command if execute is set', async () => { - await tf.init(); - td.verify(buildAndExec('init', {})); + td.verify(commandWrapper('init', {}, {})); }); }); describe('import', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; + it('should call commandWrapper', async () => { await tf.import(); - td.verify(buildCommand('import', {})); - }); - - it('should call execute command if execute is set', async () => { - await tf.import(); - td.verify(buildAndExec('import', {})); + td.verify(commandWrapper('import', {}, {})); }); }); describe('output', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; + it('should call commandWrapper', async () => { await tf.output(); - td.verify(buildCommand('output', {})); - }); - - it('should call execute command if execute is set', async () => { - await tf.output(); - td.verify(buildAndExec('output', {})); + td.verify(commandWrapper('output', {}, {})); }); }); describe('plan', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; + it('should call commandWrapper', async () => { await tf.plan(); - td.verify(buildCommand('plan', {})); - }); - - it('should call execute command if execute is set', async () => { - await tf.plan(); - td.verify(buildAndExec('plan', {})); + td.verify(commandWrapper('plan', {}, {})); }); }); describe('providers', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; - await tf.providers(); - td.verify(buildCommand('providers', {})); - }); - - it('should call execute command if execute is set', async () => { + it('should call commandWrapper', async () => { await tf.providers(); - td.verify(buildAndExec('providers', {})); + td.verify(commandWrapper('providers', {}, {})); }); }); describe('refresh', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; - await tf.refresh(); - td.verify(buildCommand('refresh', {})); - }); - - it('should call execute command if execute is set', async () => { + it('should call commandWrapper', async () => { await tf.refresh(); - td.verify(buildAndExec('refresh', {})); + td.verify(commandWrapper('refresh', {}, {})); }); }); describe('show', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; - await tf.show(); - td.verify(buildCommand('show', {})); - }); - - it('should call execute command if execute is set', async () => { + it('should call commandWrapper', async () => { await tf.show(); - td.verify(buildAndExec('show', {})); + td.verify(commandWrapper('show', {}, {})); }); }); describe('taint', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; + it('should call commandWrapper', async () => { await tf.taint(); - td.verify(buildCommand('taint', {})); - }); - - it('should call execute command if execute is set', async () => { - await tf.taint(); - td.verify(buildAndExec('taint', {})); + td.verify(commandWrapper('taint', {}, {})); }); }); describe('untaint', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; - await tf.untaint(); - td.verify(buildCommand('untaint', {})); - }); - - it('should call execute command if execute is set', async () => { + it('should call commandWrapper', async () => { await tf.untaint(); - td.verify(buildAndExec('untaint', {})); + td.verify(commandWrapper('untaint', {}, {})); }); }); describe('validate', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; - await tf.validate(); - td.verify(buildCommand('validate', {})); - }); - - it('should call execute command if execute is set', async () => { + it('should call commandWrapper', async () => { await tf.validate(); - td.verify(buildAndExec('validate', {})); + td.verify(commandWrapper('validate', {}, {})); }); }); describe('version', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; + it('should call commandWrapper', async () => { await tf.version(); - td.verify(buildCommand('version', {})); - }); - - it('should call execute command if execute is set', async () => { - await tf.version(); - td.verify(buildAndExec('version', {})); + td.verify(commandWrapper('version', {}, {})); }); }); describe('workspaceDelete', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; - await tf.workspaceDelete(); - td.verify(buildCommand('workspace-delete', {})); - }); - - it('should call execute command if execute is set', async () => { + it('should call commandWrapper', async () => { await tf.workspaceDelete(); - td.verify(buildAndExec('workspace-delete', {})); + td.verify(commandWrapper('workspace-delete', {}, {})); }); }); describe('workspaceList', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; + it('should call commandWrapper', async () => { await tf.workspaceList(); - td.verify(buildCommand('workspace-list', {})); - }); - - it('should call execute command if execute is set', async () => { - await tf.workspaceList(); - td.verify(buildAndExec('workspace-list', {})); + td.verify(commandWrapper('workspace-list', {}, {})); }); }); describe('workspaceNew', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; - await tf.workspaceNew(); - td.verify(buildCommand('workspace-new', {})); - }); - - it('should call execute command if execute is set', async () => { + it('should call commandWrapper', async () => { await tf.workspaceNew(); - td.verify(buildAndExec('workspace-new', {})); + td.verify(commandWrapper('workspace-new', {}, {})); }); }); describe('workspaceSelect', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; + it('should call commandWrapper', async () => { await tf.workspaceSelect(); - td.verify(buildCommand('workspace-select', {})); - }); - - it('should call execute command if execute is set', async () => { - await tf.workspaceSelect(); - td.verify(buildAndExec('workspace-select', {})); + td.verify(commandWrapper('workspace-select', {}, {})); }); }); describe('workspaceShow', () => { let Terrajs; let tf; - let buildCommand; - let buildAndExec; + let commandWrapper; beforeEach(() => { td.replace('./registerHelpers'); td.replace('./registerPartials'); Terrajs = require('./index'); tf = new Terrajs(); - buildCommand = td.replace(tf, 'buildCommand'); - buildAndExec = td.replace(tf, 'buildAndExec'); + commandWrapper = td.replace(tf, 'commandWrapper'); }); afterEach(() => td.reset()); - it('should call build command if execute is not set', async () => { - tf.execute = false; - await tf.workspaceShow(); - td.verify(buildCommand('workspace-show', {})); - }); - - it('should call execute command if execute is set', async () => { + it('should call commandWrapper', async () => { await tf.workspaceShow(); - td.verify(buildAndExec('workspace-show', {})); + td.verify(commandWrapper('workspace-show', {}, {})); }); }); }); diff --git a/src/integration.test.js b/src/integration-0.11.test.js similarity index 100% rename from src/integration.test.js rename to src/integration-0.11.test.js diff --git a/src/integration-0.13.test.js b/src/integration-0.13.test.js new file mode 100644 index 0000000..e78e42e --- /dev/null +++ b/src/integration-0.13.test.js @@ -0,0 +1,60 @@ +/* eslint-disable global-require */ +/* eslint-env mocha */ +const assert = require('assert'); +const td = require('testdouble'); +const Terrajs = require('./index'); + +describe('integration (Terraform 0.13)', () => { + let tf; + + beforeEach(() => { + tf = new Terrajs({ execute: false }); + td.replace(tf, 'getTerraformVersion'); + td.when(tf.getTerraformVersion()).thenReturn('0.13'); + }); + + afterEach(() => td.reset()); + + describe('destroy', () => { + it('should map var flags', async () => { + assert.strictEqual(await tf.destroy({ + var: { + foo: 'foo1', + bar: 'bar1', + }, + }), 'terraform destroy -backup=terraform.backup -lock=true -lock-timeout=0s -no-color -parallelism=10 -refresh=true -state=terraform.tfstate -var foo="foo1" -var bar="bar1"'); + }); + }); + + describe('plan', () => { + it('should map var flags', async () => { + assert.strictEqual(await tf.plan({ + var: { + foo: 'foo1', + bar: 'bar1', + }, + }), 'terraform plan -input=false -lock=true -lock-timeout=0s -no-color -out=terraform -parallelism=10 -refresh=true -state=terraform.tfstate -var foo="foo1" -var bar="bar1"'); + }); + + it('should map var files flag', async () => { + assert.strictEqual(await tf.plan({ + varFile: [ + 'foo', + 'bar', + ], + }), 'terraform plan -input=false -lock=true -lock-timeout=0s -no-color -out=terraform -parallelism=10 -refresh=true -state=terraform.tfstate -var-file=foo -var-file=bar'); + }); + + it('should remove no-color', async () => { + assert.strictEqual(await tf.plan({ + noColor: false, + }), 'terraform plan -input=false -lock=true -lock-timeout=0s -out=terraform -parallelism=10 -refresh=true -state=terraform.tfstate'); + }); + + it('should include detailed-exitcode', async () => { + assert.strictEqual(await tf.plan({ + detailedExitcode: true, + }), 'terraform plan -detailed-exitcode -input=false -lock=true -lock-timeout=0s -no-color -out=terraform -parallelism=10 -refresh=true -state=terraform.tfstate'); + }); + }); +}); diff --git a/src/registerPartials.js b/src/registerPartials.js index ac5d816..3518cb0 100644 --- a/src/registerPartials.js +++ b/src/registerPartials.js @@ -2,7 +2,7 @@ const fs = require('fs'); const path = require('path'); const Handlebars = require('handlebars'); -const partialsPath = path.join(__dirname, '..', 'partials'); +const { partialsPath } = require('./constants'); module.exports = () => { const partialFiles = fs.readdirSync(partialsPath); diff --git a/src/shExec.test.js b/src/shExec.test.js index e1d7cc0..342e6cb 100644 --- a/src/shExec.test.js +++ b/src/shExec.test.js @@ -31,26 +31,52 @@ describe('shExec', () => { }); describe('reject', () => { - beforeEach(() => { - exec = td.function(); - td.when(exec(td.matchers.isA(String), td.matchers.isA(Object))).thenCallback(1, 'stdout', 'stderr'); - td.replace('shelljs', { exec }); - shExec = require('./shExec'); - }); + describe('terraformError', () => { + beforeEach(() => { + exec = td.function(); + td.when(exec(td.matchers.isA(String), td.matchers.isA(Object))).thenCallback(1, 'stdout', 'stderr'); + td.replace('shelljs', { exec }); + shExec = require('./shExec'); + }); - afterEach(() => { - td.reset(); + afterEach(() => { + td.reset(); + }); + + it('should reject with stdout, stderr, and an exit code', async () => { + try { + await shExec('test', { cwd: 'cwd' }); + } catch (e) { + assert(e instanceof Error); + assert.equal(e.code, 1); + assert.equal(e.stdout, 'stdout'); + assert.equal(e.stderr, 'stderr'); + } + }); }); - it('should reject with stdout, stderr, and an exit code', async () => { - try { - await shExec('test', { cwd: 'cwd' }); - } catch (e) { - assert(e instanceof Error); - assert.equal(e.code, 1); - assert.equal(e.stdout, 'stdout'); - assert.equal(e.stderr, 'stderr'); - } + describe('terraformDiffError', () => { + beforeEach(() => { + exec = td.function(); + td.when(exec(td.matchers.isA(String), td.matchers.isA(Object))).thenCallback(2, 'stdout', 'stderr'); + td.replace('shelljs', { exec }); + shExec = require('./shExec'); + }); + + afterEach(() => { + td.reset(); + }); + + it('should reject with stdout, stderr, and an exit code', async () => { + try { + await shExec('test', { cwd: 'cwd' }); + } catch (e) { + assert(e instanceof Error); + assert.equal(e.code, 2); + assert.equal(e.stdout, 'stdout'); + assert.equal(e.stderr, 'stderr'); + } + }); }); }); });