From 85cec225111710281f7aec463a3deea50b3db19a Mon Sep 17 00:00:00 2001 From: Balwant Date: Tue, 24 Aug 2021 23:25:39 +1000 Subject: [PATCH 1/5] feat(terraform/environment): add support for .tfcrc Allows user to configure variables using .tfcrc file in current directory --- package-lock.json | 34 ++++++++++++++++++++++ package.json | 1 + scripts/shared/environment.js | 54 +++++++++++++++++++++++++---------- scripts/terraform/cli.js | 1 + scripts/terraform/help.js | 1 + 5 files changed, 76 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index d66a83b..f854f38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -485,6 +485,11 @@ "integrity": "sha1-oMoMvCmltz6Dbuvhy/bF4OTrgvk=", "dev": true }, + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==" + }, "array-ify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", @@ -733,6 +738,17 @@ "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", "dev": true }, + "command-line-args": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.0.tgz", + "integrity": "sha512-4zqtU1hYsSJzcJBOcNZIbW5Fbk9BkjCp1pZVhQKoRaWL5J7N4XphDLwo8aWwdQpTugxwu+jf9u2ZhkXiqp5Z6A==", + "requires": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + } + }, "commitizen": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.2.4.tgz", @@ -1277,6 +1293,14 @@ "merge": "^2.1.0" } }, + "find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "requires": { + "array-back": "^3.0.1" + } + }, "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -1956,6 +1980,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, "lodash.capitalize": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", @@ -5221,6 +5250,11 @@ "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", "dev": true }, + "typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==" + }, "uglify-js": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.1.tgz", diff --git a/package.json b/package.json index 7282b6b..ddd65a6 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ } }, "dependencies": { + "command-line-args": "^5.2.0", "follow-redirects": "^1.14.1" } } diff --git a/scripts/shared/environment.js b/scripts/shared/environment.js index 14656ce..c622dfb 100644 --- a/scripts/shared/environment.js +++ b/scripts/shared/environment.js @@ -1,24 +1,48 @@ +const { existsSync, readFileSync } = require('fs'); +const path = require('path'); + +const currentEnv = { + TFCRC_PATH: undefined, + config: undefined +} const Environment = (render) => { - const env = { - // args: process.argv, - paths: { - pwd: process.env.PWD, - home: process.env.HOME, - }, - secrets: { - TFC_TOKEN: process.env.TFC_TOKEN, - TFC_WORKSPACE: process.env.TFC_WORKSPACE, - }, - action: process.argv[3] || '', - resource: process.argv[2] || '', - args: process.argv.slice(4) + if (!currentEnv.config) { + const TFCRC_PATH = path.join(process.env.PWD, '.tfcrc'); + const TFC_CONFIG = {}; + if (existsSync(TFCRC_PATH)) { + currentEnv.TFCRC_PATH = TFCRC_PATH; + + readFileSync(TFCRC_PATH, { encoding: 'utf-8' }) + .split('\n').forEach(line => { + const [key, value] = line.split('='); + if(key && value) { + TFC_CONFIG[key] = value; + } + }); + } + + currentEnv.config = { + // args: process.argv, + paths: { + pwd: process.env.PWD, + home: process.env.HOME, + }, + secrets: { + TFC_TOKEN: process.env.TFC_TOKEN || TFC_CONFIG.TFC_TOKEN, + TFC_WORKSPACE: process.env.TFC_WORKSPACE || TFC_CONFIG.TFC_WORKSPACE || TFC_CONFIG.WORKSPACE_ID, + }, + action: process.argv[3] || '', + resource: process.argv[2] || '', + args: process.argv.slice(4) + } } + if (render) { console.log('Envrionment'); - console.log(env); + console.log(currentEnv.config); } - return env; + return currentEnv.config; } module.exports = Environment; \ No newline at end of file diff --git a/scripts/terraform/cli.js b/scripts/terraform/cli.js index 0b71261..54bfe57 100755 --- a/scripts/terraform/cli.js +++ b/scripts/terraform/cli.js @@ -60,6 +60,7 @@ if ( console.log(JSON.stringify(value, null, ' ')); } }).catch(err => { + // console.log(err); Help(err); }); } diff --git a/scripts/terraform/help.js b/scripts/terraform/help.js index 3e3721b..9077189 100644 --- a/scripts/terraform/help.js +++ b/scripts/terraform/help.js @@ -1,6 +1,7 @@ function Help(errorMessage) { if (errorMessage) { console.error(`Error: ${errorMessage}`); + // console.trace() } console.log(` From 4e65479b2ac0f784ec1dd711787c0b9714d45ade Mon Sep 17 00:00:00 2001 From: Balwant Date: Sat, 18 Sep 2021 15:57:23 +1000 Subject: [PATCH 2/5] feat(terraform/workspace): add support for missing workspace or organization --- scripts/terraform/resources/workspace.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/terraform/resources/workspace.js b/scripts/terraform/resources/workspace.js index 007a4be..d0508bf 100755 --- a/scripts/terraform/resources/workspace.js +++ b/scripts/terraform/resources/workspace.js @@ -19,8 +19,16 @@ const WORKSPACE = { list: async (args, returnOnly) => { if (!args[0]) { - throw 'Organisation Missing' + let responseOrgs = await Request('app.terraform.io', 'GET', `/api/v2/organizations/`, { + 'Authorization': `Bearer ${Environment().secrets.TFC_TOKEN}` + }); + let errorString = 'Organization Missing\nPlease pass organization from list below:\n'; + JSON.parse(responseOrgs).data.forEach(org => { + errorString += ` - ${org.id}\n` + }) + throw errorString; } + const org = args[0]; let response = await Request('app.terraform.io', 'GET', `/api/v2/organizations/${org}/workspaces`, { @@ -29,6 +37,9 @@ const WORKSPACE = { response = JSON.parse(response); if (response.errors) { response.errors.forEach(error => { + if (error.status == 404) { + throw `Organization ${org} not found.` + } console.log(`error: [${error.status}] ${error.title}`); }); throw ""; From 51699eb5651f7cd7b8d4848fd05a12fd36e939bf Mon Sep 17 00:00:00 2001 From: Balwant Date: Sat, 18 Sep 2021 15:58:37 +1000 Subject: [PATCH 3/5] feat(terraform/output): add list sub command for output List subcommand in output lists all available outputs in latest state. It does not displays any information about state but just output id at the moment. --- scripts/terraform/resources/output.js | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/scripts/terraform/resources/output.js b/scripts/terraform/resources/output.js index 6e89885..1901040 100755 --- a/scripts/terraform/resources/output.js +++ b/scripts/terraform/resources/output.js @@ -3,29 +3,44 @@ const Request = require("../../shared/request"); const Help = require("../help"); const OUTPUT = { + list: async (...args) => { + const env = Environment(); + if (!env.secrets.TFC_TOKEN) { + Help('Missing TFC_TOKEN.'); + } + if (!env.secrets.TFC_WORKSPACE) { + Help('Missing TFC_WORKSPACE.'); + } + const state = await Request('app.terraform.io', 'GET', `/api/v2/workspaces/${env.secrets.TFC_WORKSPACE}/current-state-version`, { + 'Authorization': `Bearer ${env.secrets.TFC_TOKEN}` + }); + const stateJSON = JSON.parse(state); + stateJSON.data.relationships.outputs.data.forEach((output, i) => { + console.log(i, output.id); + }); + }, get: async (...args) => { const env = Environment(); - if (env.secrets.TFC_TOKEN) { + if (!env.secrets.TFC_TOKEN) { Help('Missing TFC_TOKEN.'); } - if (env.secrets.TFC_WORKSPACE) { + if (!env.secrets.TFC_WORKSPACE) { Help('Missing TFC_WORKSPACE.'); } const state = await Request('app.terraform.io', 'GET', `/api/v2/workspaces/${env.secrets.TFC_WORKSPACE}/current-state-version`, { 'Authorization': `Bearer ${env.secrets.TFC_TOKEN}` }); - console.log(state); - const output_id = JSON.parse(state).data.relationships.outputs.data[0].id; - + let outputs = await Request('app.terraform.io', 'GET', `/api/v2/state-version-outputs/${output_id}`, { 'Authorization': `Bearer ${env.secrets.TFC_TOKEN}` }); outputs = JSON.parse(outputs).data.attributes.value; - console.log(outputs.byString(env.args[0] || '')); + // console.log(outputs.byString(env.args[0] || '')); + console.log(JSON.stringify(outputs, null, ' ')); }, } From 029e39e649a997385bdca8f3dff8a38694659280 Mon Sep 17 00:00:00 2001 From: Balwant Date: Sat, 18 Sep 2021 16:01:16 +1000 Subject: [PATCH 4/5] feat(terraform/state): add subcommand to get latest state in a workspace --- scripts/terraform/resources/state.js | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/scripts/terraform/resources/state.js b/scripts/terraform/resources/state.js index 70877a2..43a3cd1 100755 --- a/scripts/terraform/resources/state.js +++ b/scripts/terraform/resources/state.js @@ -1,6 +1,28 @@ +const Environment = require("../../shared/environment"); +const Request = require("../../shared/request"); +const Help = require("../help"); +const URL = require('url'); +const { writeFileSync, existsSync } = require("fs"); + const OUTPUT = { - get: () => { - throw "not implimented"; + get: async (...args) => { + const env = Environment(); + if (!env.secrets.TFC_TOKEN) { + Help('Missing TFC_TOKEN.'); + } + if (!env.secrets.TFC_WORKSPACE) { + Help('Missing TFC_WORKSPACE.'); + } + const response = await Request('app.terraform.io', 'GET', `/api/v2/workspaces/${env.secrets.TFC_WORKSPACE}/current-state-version`, { + 'Authorization': `Bearer ${env.secrets.TFC_TOKEN}` + }); + + const stateURL = URL.parse(JSON.parse(response).data.attributes['hosted-state-download-url']); + const stateResponse = await Request(stateURL.hostname, 'GET', stateURL.path, { + 'Authorization': `Bearer ${env.secrets.TFC_TOKEN}` + }); + + console.log(stateResponse) }, set: () => { From 36a8ab1750db98c37a8029586b7116a84697ab41 Mon Sep 17 00:00:00 2001 From: Balwant Date: Sat, 18 Sep 2021 16:02:18 +1000 Subject: [PATCH 5/5] docs(readme): add docs workspace, state and output commands --- README.md | 50 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 994e2ee..74b3df9 100644 --- a/README.md +++ b/README.md @@ -22,20 +22,50 @@ Terraform cloud scripts ### Usage: ```shell -terraform-cloud [arguments] +terraform-cloud [arguments] ``` OR ```shell -tfc [arguments] +tfc [arguments] ``` -### Available Allowed Resources: +### Available Commands and Subcommands: - `workspace` + - `get` - Prints the provided workspace as output + + Example: + ```shell + $ tfc workspace get orgnization/workspace + ``` + - `list` - List all workspaces in Organization. + + Note: In case of missing organization name, Throws an error and outputs all available organizations in the account. + + Example: + ```shell + $ tfc workspace list + + Error: Organization Missing + Please pass organization from list below: + - OrgA + - OrgB + + + Terraform Cloud - Devops Scripts + + Github: https://github.com/phenixcoder/devops-scripts + + Usage: + terraform-cloud [arguments] + OR + tfc [arguments] + + ``` + OR + ```shell + $ tfc workspace list orgnization + ``` - `output` + - `get` - Get the first output from latest state and prints as output + - `list` - Lista all output in the state. - `state` - -### Available Allowed Actions: - - `get` - - `set` - - `list` - ---- \ No newline at end of file + - `get` - Get the latest state and prints as output \ No newline at end of file