@agilecustoms/envctl 0.21.2 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ import * as readline from 'readline';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { ProcessException } from '../exceptions.js';
6
6
  export class ProcessRunner {
7
- async runScript(script, args, cwd, scanner) {
7
+ async runScript(script, args = [], cwd, scanner) {
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
9
  const scriptPath = path.join(__dirname, `../../scripts/${script}`);
10
10
  await this.run(scriptPath, args, cwd, scanner);
@@ -55,7 +55,9 @@ export class ProcessRunner {
55
55
  return new Promise((resolve, reject) => {
56
56
  child.on('close', (code) => {
57
57
  rl.close();
58
- processLine(buffer);
58
+ if (buffer !== '') {
59
+ processLine(buffer);
60
+ }
59
61
  if (code === 0) {
60
62
  if (errorBuffer) {
61
63
  console.warn('Process completed successfully, but there were errors:\n' + errorBuffer);
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { AbortedException, KnownException, ProcessException, TerraformInitRequired } from '../exceptions.js';
3
+ import { AbortedException, KnownException, ProcessException } from '../exceptions.js';
4
4
  const MAX_ATTEMPTS = 2;
5
5
  const RETRYABLE_ERRORS = [
6
6
  'ConcurrentModificationException',
@@ -21,7 +21,12 @@ export class TerraformAdapter {
21
21
  });
22
22
  console.log(`\nTime EST: ${est}, UTC: ${utc}\n`);
23
23
  }
24
- async init(key, cwd) {
24
+ async init(key, cwd, upgrade = false) {
25
+ let accAlias = '';
26
+ const accAliasScanner = (line) => {
27
+ accAlias = line;
28
+ };
29
+ await this.processRunner.runScript('get-acc-alias.sh', [], cwd, accAliasScanner);
25
30
  console.log('Run terraform init to download providers, this doesn\'t create any resources in AWS even S3 object');
26
31
  let emptyDir = false;
27
32
  const scanner = (line) => {
@@ -29,13 +34,29 @@ export class TerraformAdapter {
29
34
  emptyDir = true;
30
35
  }
31
36
  };
37
+ const bucket = `agilecustoms-${accAlias}-tf-state`;
38
+ const args = [
39
+ 'init',
40
+ '-reconfigure',
41
+ `-backend-config=bucket=${bucket}`,
42
+ `-backend-config=key=${key}`
43
+ ];
44
+ if (upgrade) {
45
+ args.push('-upgrade');
46
+ }
32
47
  this.printTime();
33
48
  try {
34
- await this.processRunner.runScript('terraform-init.sh', [key], cwd, scanner);
49
+ await this.processRunner.run('terraform', args, cwd, scanner);
35
50
  this.printTime();
36
51
  }
37
52
  catch (error) {
38
53
  if (error instanceof ProcessException) {
54
+ if (!upgrade && error.message.includes('Failed to query available provider packages')) {
55
+ args.push('-upgrade');
56
+ await this.processRunner.run('terraform', args, cwd);
57
+ this.printTime();
58
+ return;
59
+ }
39
60
  throw new KnownException(`terraform init failed with code ${error.code}:\n${error.message}`, { cause: error });
40
61
  }
41
62
  throw error;
@@ -174,9 +195,6 @@ export class TerraformAdapter {
174
195
  if (!(error instanceof ProcessException)) {
175
196
  throw error;
176
197
  }
177
- if (error.message.includes('Backend initialization required')) {
178
- throw new TerraformInitRequired();
179
- }
180
198
  if (attemptNo < MAX_ATTEMPTS && RETRYABLE_ERRORS.some(err => error.message.includes(err))) {
181
199
  console.warn(`Retrying terraform apply due to error: ${error.message}`);
182
200
  return this.apply(env, onDemandVars, cwd, attemptNo + 1);
@@ -42,9 +42,3 @@ export class ProcessException extends Error {
42
42
  this.name = 'ProcessException';
43
43
  }
44
44
  }
45
- export class TerraformInitRequired extends Error {
46
- constructor() {
47
- super();
48
- this.name = 'TerraformInitRequired';
49
- }
50
- }
@@ -1,4 +1,4 @@
1
- import { KnownException, TerraformInitRequired } from '../exceptions.js';
1
+ import { KnownException } from '../exceptions.js';
2
2
  import { EnvStatus, } from '../model/index.js';
3
3
  export class EnvCtl {
4
4
  cliHelper;
@@ -65,15 +65,13 @@ export class EnvCtl {
65
65
  }
66
66
  async init(project, envName, cwd) {
67
67
  const key = this.key(project, envName);
68
- await this.terraformAdapter.init(key, cwd);
68
+ await this.terraformAdapter.init(key, cwd, true);
69
69
  }
70
70
  async plan(input, tfArgs, cwd) {
71
71
  const { envName, anOwner, key, env } = await this.tryGetEnv(input);
72
72
  if (env == null) {
73
73
  const owner = this.cliHelper.ensureOwner(anOwner);
74
- await this.terraformAdapter.init(key, cwd);
75
- const envTerraform = { envName, ephemeral: false, owner, args: tfArgs };
76
- await this.terraformAdapter.plan(envTerraform, cwd);
74
+ await this.runPlan(key, envName, owner, tfArgs, undefined, cwd);
77
75
  return;
78
76
  }
79
77
  this.checkInput(env, input, cwd);
@@ -83,7 +81,11 @@ export class EnvCtl {
83
81
  if (env.status !== EnvStatus.Active) {
84
82
  throw new KnownException(`Env ${env.key} status is ${env.status}, can not run plan`);
85
83
  }
86
- const envTerraform = { envName, ephemeral: false, owner, args: tfArgs, vars: env.vars };
84
+ await this.runPlan(key, envName, owner, tfArgs, env.vars, cwd);
85
+ }
86
+ async runPlan(key, envName, owner, args, vars, cwd) {
87
+ const envTerraform = { envName, ephemeral: false, owner, args, vars };
88
+ await this.terraformAdapter.init(key, cwd);
87
89
  await this.terraformAdapter.plan(envTerraform, cwd);
88
90
  }
89
91
  async createEphemeral(project, envName, owner) {
@@ -125,6 +127,7 @@ export class EnvCtl {
125
127
  await this.runDeploy(env, envName, tfArgs, cwd);
126
128
  }
127
129
  async runDeploy(env, envName, tfArgs, cwd) {
130
+ await this.terraformAdapter.init(env.key, cwd);
128
131
  console.log('Deploying resources');
129
132
  const { ephemeral, vars } = env;
130
133
  const envTerraform = { envName, ephemeral, owner: env.owner || 'system', args: tfArgs, vars };
@@ -132,15 +135,6 @@ export class EnvCtl {
132
135
  try {
133
136
  await this.terraformAdapter.apply(envTerraform, onDemandVars, cwd);
134
137
  }
135
- catch (error) {
136
- if (error instanceof TerraformInitRequired) {
137
- await this.terraformAdapter.init(env.key, cwd);
138
- await this.terraformAdapter.apply(envTerraform, onDemandVars, cwd);
139
- }
140
- else {
141
- throw error;
142
- }
143
- }
144
138
  finally {
145
139
  if (!ephemeral) {
146
140
  const newVars = this.terraformAdapter.getNewVars(envTerraform, onDemandVars, cwd);
@@ -184,8 +178,8 @@ export class EnvCtl {
184
178
  }
185
179
  console.log('Deleting env');
186
180
  }
187
- await this.envApi.delete(env);
188
- console.log(`Env ${env.key} is scheduled for deletion`);
181
+ const message = await this.envApi.delete(env);
182
+ console.log(message);
189
183
  }
190
184
  async destroy(project, envName, tfArgs, force, cwd) {
191
185
  const env = await this.get(project, envName);
@@ -211,6 +205,7 @@ export class EnvCtl {
211
205
  const { ephemeral, owner, vars } = env;
212
206
  const envTerraform = { envName, ephemeral, owner: owner || 'system', args: tfArgs, vars };
213
207
  console.log('Destroying env resources');
208
+ await this.terraformAdapter.init(env.key, cwd);
214
209
  await this.terraformAdapter.destroy(envTerraform, force, cwd);
215
210
  console.log('Unlock env');
216
211
  await this.envApi.activate(env);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agilecustoms/envctl",
3
3
  "description": "node.js CLI client for manage environments",
4
- "version": "0.21.2",
4
+ "version": "0.22.1",
5
5
  "author": "Alex Chekulaev",
6
6
  "type": "module",
7
7
  "bin": {
@@ -25,13 +25,15 @@
25
25
  "test": "vitest run --coverage",
26
26
  "build": "tsc",
27
27
  "run": "node dist/index.js",
28
- "run-version": " tsc --sourceMap true && npm run run -- --version",
29
- "run-configure": " tsc --sourceMap true && npm run run -- configure",
30
- "run-status": " tsc --sourceMap true && npm run run -- status --cwd ../tt-core",
31
- "run-plan": " tsc --sourceMap true && npm run run -- plan --cwd ../tt-core",
32
- "run-deploy": " tsc --sourceMap true && npm run run -- deploy --cwd ../tt-core -var=\"env_size=min\"",
33
- "run-delete": " tsc --sourceMap true && npm run run -- delete --cwd ../tt-core",
34
- "run-destroy": " tsc --sourceMap true && npm run run -- destroy --cwd ../tt-core",
28
+ "run-version": " tsc --sourceMap true && npm run run -- --version",
29
+ "run-configure": "tsc --sourceMap true && npm run run -- configure",
30
+ "~": "",
31
+ "run-status": " tsc --sourceMap true && npm run run -- status --cwd ../tt-core",
32
+ "run-init": " tsc --sourceMap true && npm run run -- init --cwd ../tt-core",
33
+ "run-plan": " tsc --sourceMap true && npm run run -- plan --cwd ../tt-core",
34
+ "run-deploy": " tsc --sourceMap true && npm run run -- deploy --cwd ../tt-core -var=\"env_size=min\"",
35
+ "run-delete": " tsc --sourceMap true && npm run run -- delete --cwd ../tt-core",
36
+ "run-destroy": "tsc --sourceMap true && npm run run -- destroy --cwd ../tt-core",
35
37
  "-": "",
36
38
  "run-ephemeral-create": " tsc --sourceMap true && npm run run -- create-ephemeral --env test",
37
39
  "run-ephemeral-status": " tsc --sourceMap true && npm run run -- status --env test",
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # TF state is stored in S3 in format: {company}-{acc-alias}-tf-state/{key}
5
+ # (for non ephemeral environments the {key} typically has form of {project-code}-{env-name} like tt-dev)
6
+ # Retrieve AWS account information
7
+ acc_id=$(aws sts get-caller-identity --query "Account" --output text)
8
+ aws organizations list-tags-for-resource --resource-id "$acc_id" --query "Tags[?Key=='Alias'].Value" --output text
@@ -1,18 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- KEY="${1:?}"
5
-
6
- # TF state is stored in S3 in format: {company}-{acc-alias}-tf-state/{key}
7
- # (for non ephemeral environments the {key} typically has form of {project-code}-{env-name} like tt-dev)
8
- # Retrieve AWS account information
9
- acc_id=$(aws sts get-caller-identity --query "Account" --output text)
10
- acc_alias=$(aws organizations list-tags-for-resource --resource-id "$acc_id" --query "Tags[?Key=='Alias'].Value" --output text)
11
- state_prefix="agilecustoms-$acc_alias" # like "agilecustoms-tt-dev"
12
-
13
- # -upgrade - get latest version of providers (mainly hashicorp/aws)
14
- # -reconfigure - discard local state, use (or create) remote
15
- # added to allow deploy multiple envs from local machine (on CI no local state survive between runs)
16
- terraform init -upgrade -reconfigure \
17
- -backend-config="bucket=$state_prefix-tf-state" \
18
- -backend-config="key=$KEY"