@agilecustoms/envctl 0.15.1 → 0.16.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.
@@ -1,4 +1,4 @@
1
- import { AbortedException, KnownException, ProcessException } from '../exceptions.js';
1
+ import { AbortedException, KnownException, ProcessException, TerraformInitRequired } from '../exceptions.js';
2
2
  const MAX_ATTEMPTS = 2;
3
3
  const RETRYABLE_ERRORS = [
4
4
  'ConcurrentModificationException',
@@ -58,20 +58,20 @@ export class TerraformAdapter {
58
58
  await this.processRunner.run('terraform', ['plan', ...args], cwd);
59
59
  }
60
60
  catch (error) {
61
- if (error instanceof ProcessException) {
62
- if (attemptNo < MAX_ATTEMPTS && RETRYABLE_ERRORS.some(err => error.message.includes(err))) {
63
- console.warn(`Retrying terraform plan due to error: ${error.message}`);
64
- return this.plan(env, tfArgs, cwd, attemptNo + 1);
65
- }
66
- const lockId = this.lockId(error, attemptNo);
67
- if (lockId) {
68
- await this.promptUnlock(lockId, cwd);
69
- console.info('State unlocked, retrying terraform plan');
70
- return this.plan(env, tfArgs, cwd, attemptNo + 1);
71
- }
72
- throw new KnownException(`terraform plan failed with code ${error.code}:\n${error.message}`, { cause: error });
61
+ if (!(error instanceof ProcessException)) {
62
+ throw error;
73
63
  }
74
- throw error;
64
+ if (attemptNo < MAX_ATTEMPTS && RETRYABLE_ERRORS.some(err => error.message.includes(err))) {
65
+ console.warn(`Retrying terraform plan due to error: ${error.message}`);
66
+ return this.plan(env, tfArgs, cwd, attemptNo + 1);
67
+ }
68
+ const lockId = this.lockId(error, attemptNo);
69
+ if (lockId) {
70
+ await this.promptUnlock(lockId, cwd);
71
+ console.info('State unlocked, retrying terraform plan');
72
+ return this.plan(env, tfArgs, cwd, attemptNo + 1);
73
+ }
74
+ throw new KnownException(`terraform plan failed with code ${error.code}:\n${error.message}`, { cause: error });
75
75
  }
76
76
  }
77
77
  async apply(env, tfArgs, cwd, attemptNo = 1) {
@@ -83,20 +83,23 @@ export class TerraformAdapter {
83
83
  this.printTime();
84
84
  }
85
85
  catch (error) {
86
- if (error instanceof ProcessException) {
87
- if (attemptNo < MAX_ATTEMPTS && RETRYABLE_ERRORS.some(err => error.message.includes(err))) {
88
- console.warn(`Retrying terraform apply due to error: ${error.message}`);
89
- return this.apply(env, tfArgs, cwd, attemptNo + 1);
90
- }
91
- const lockId = this.lockId(error, attemptNo);
92
- if (lockId) {
93
- await this.promptUnlock(lockId, cwd);
94
- console.info('State unlocked, retrying terraform apply');
95
- return this.apply(env, tfArgs, cwd, attemptNo + 1);
96
- }
97
- throw new KnownException(`terraform apply failed with code ${error.code}:\n${error.message}`, { cause: error });
86
+ if (!(error instanceof ProcessException)) {
87
+ throw error;
98
88
  }
99
- throw error;
89
+ if (error.message.includes('Backend initialization required')) {
90
+ throw new TerraformInitRequired();
91
+ }
92
+ if (attemptNo < MAX_ATTEMPTS && RETRYABLE_ERRORS.some(err => error.message.includes(err))) {
93
+ console.warn(`Retrying terraform apply due to error: ${error.message}`);
94
+ return this.apply(env, tfArgs, cwd, attemptNo + 1);
95
+ }
96
+ const lockId = this.lockId(error, attemptNo);
97
+ if (lockId) {
98
+ await this.promptUnlock(lockId, cwd);
99
+ console.info('State unlocked, retrying terraform apply');
100
+ return this.apply(env, tfArgs, cwd, attemptNo + 1);
101
+ }
102
+ throw new KnownException(`terraform apply failed with code ${error.code}:\n${error.message}`, { cause: error });
100
103
  }
101
104
  }
102
105
  async destroy(env, tfArgs, cwd, attemptNo = 1) {
@@ -112,16 +115,16 @@ export class TerraformAdapter {
112
115
  await this.processRunner.run('terraform', ['destroy', '-auto-approve', ...args], cwd, scanner);
113
116
  }
114
117
  catch (error) {
115
- if (error instanceof ProcessException) {
116
- const lockId = this.lockId(error, attemptNo);
117
- if (lockId) {
118
- await this.promptUnlock(lockId, cwd);
119
- console.info('State unlocked, retrying terraform destroy');
120
- return this.destroy(env, tfArgs, cwd, attemptNo + 1);
121
- }
122
- throw new KnownException(`terraform destroy failed with code ${error.code}:\n${error.message}`, { cause: error });
118
+ if (!(error instanceof ProcessException)) {
119
+ throw error;
123
120
  }
124
- throw error;
121
+ const lockId = this.lockId(error, attemptNo);
122
+ if (lockId) {
123
+ await this.promptUnlock(lockId, cwd);
124
+ console.info('State unlocked, retrying terraform destroy');
125
+ return this.destroy(env, tfArgs, cwd, attemptNo + 1);
126
+ }
127
+ throw new KnownException(`terraform destroy failed with code ${error.code}:\n${error.message}`, { cause: error });
125
128
  }
126
129
  if (wrongDir) {
127
130
  throw new KnownException('Can not find terraform files. Command needs to be run in a directory with terraform files');
@@ -44,3 +44,9 @@ export class ProcessException extends Error {
44
44
  this.name = 'ProcessException';
45
45
  }
46
46
  }
47
+ export class TerraformInitRequired extends Error {
48
+ constructor() {
49
+ super();
50
+ this.name = 'TerraformInitRequired';
51
+ }
52
+ }
@@ -1,4 +1,4 @@
1
- import { KnownException } from '../exceptions.js';
1
+ import { KnownException, TerraformInitRequired } from '../exceptions.js';
2
2
  import { EnvSizeAvgTime, EnvStatus } from '../model/index.js';
3
3
  export class EnvCtl {
4
4
  cliHelper;
@@ -98,14 +98,13 @@ export class EnvCtl {
98
98
  const { envName, owner, key, env } = await this.tryGetEnv(input);
99
99
  if (env === null) {
100
100
  const envInput = await this.cliHelper.parseEnvInput(input, envName, owner, cwd);
101
- await this.terraformAdapter.init(key, cwd);
102
101
  console.log('Creating env tracking record in DynamoDB');
103
102
  const { size, type, kind } = envInput;
104
103
  const createEnv = { key, ephemeral: false, owner: envInput.owner, size, type, kind };
105
104
  const newEnv = await this.envApi.create(createEnv);
106
105
  return await this.runDeploy(newEnv, envName, tfArgs, cwd);
107
106
  }
108
- this.checkInput(envName, env, input);
107
+ this.checkInput(envName, env, input, cwd);
109
108
  this.checkStatus(env);
110
109
  if (env.status == EnvStatus.Creating || env.status == EnvStatus.Updating) {
111
110
  const answerYes = await this.cliHelper.promptYesNo(`Env status is ${env.status}, likely to be an error from a previous run\n`
@@ -125,7 +124,18 @@ export class EnvCtl {
125
124
  console.log('Deploying resources');
126
125
  const { owner, size, type } = env;
127
126
  const envTf = { owner, size, type, env: envName };
128
- await this.terraformAdapter.apply(envTf, tfArgs, cwd);
127
+ try {
128
+ await this.terraformAdapter.apply(envTf, tfArgs, cwd);
129
+ }
130
+ catch (error) {
131
+ if (error instanceof TerraformInitRequired) {
132
+ await this.terraformAdapter.init(env.key, cwd);
133
+ await this.terraformAdapter.apply(envTf, tfArgs, cwd);
134
+ }
135
+ else {
136
+ throw error;
137
+ }
138
+ }
129
139
  console.log('Activating env (to finish creation)');
130
140
  await this.envApi.activate(env);
131
141
  console.log('Lock env to run db evolution');
@@ -138,7 +148,8 @@ export class EnvCtl {
138
148
  this.checkStatus(env);
139
149
  const kind = this.cliHelper.ensureKind(undefined, cwd);
140
150
  if (env.kind && env.kind !== kind) {
141
- const answerYes = await this.cliHelper.promptYesNo(`Env ${env.key} kind (dir-name): ${env.kind} - you're deleting env deployed from different dir\n`
151
+ const answerYes = await this.cliHelper.promptYesNo(`Env ${env.key} kind (dir-name): ${env.kind}\n`
152
+ + 'You\'re deleting env deployed from different dir\n'
142
153
  + 'Do you want to proceed?');
143
154
  if (!answerYes) {
144
155
  console.log('Aborting deletion');
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.15.1",
4
+ "version": "0.16.1",
5
5
  "author": "Alex Chekulaev",
6
6
  "type": "module",
7
7
  "bin": {
@@ -30,7 +30,7 @@
30
30
  "run-status": "tsc --sourceMap true && npm run run -- status --cwd ../tt-core",
31
31
  "run-plan": "tsc --sourceMap true && npm run run -- plan --size min --cwd ../tt-core",
32
32
  "run-deploy": "tsc --sourceMap true && npm run run -- deploy --size min --cwd ../tt-core",
33
- "run-delete": "tsc --sourceMap true && npm run run -- delete --cwd ../tt-web",
33
+ "run-delete": "tsc --sourceMap true && npm run run -- delete --cwd ../tt-core",
34
34
  "run-destroy": "tsc --sourceMap true && npm run run -- destroy --cwd ../tt-core"
35
35
  },
36
36
  "dependencies": {