Skip to content

Commit

Permalink
Add ability to pass options to exec from a command (#23)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kitforbes authored Nov 8, 2020
1 parent 5e7385a commit 52a8851
Show file tree
Hide file tree
Showing 11 changed files with 286 additions and 343 deletions.
46 changes: 16 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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.
3 changes: 2 additions & 1 deletion example.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }),
Expand All @@ -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();
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const path = require('path');

const constants = {
partialsPath: path.join(__dirname, '..', 'partials'),
templatePath: path.join(__dirname, '..', 'templates'),
};

Expand Down
99 changes: 54 additions & 45 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}) {
Expand Down Expand Up @@ -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);
}
}

Expand Down
Loading

0 comments on commit 52a8851

Please sign in to comment.