@agilecustoms/envctl 0.21.1 → 0.22.0
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
|
-
|
|
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
|
|
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.
|
|
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);
|
package/dist/exceptions.js
CHANGED
package/dist/service/EnvCtl.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KnownException
|
|
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.
|
|
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
|
-
|
|
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,19 +135,12 @@ 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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
139
|
+
if (!ephemeral) {
|
|
140
|
+
const newVars = this.terraformAdapter.getNewVars(envTerraform, onDemandVars, cwd);
|
|
141
|
+
if (Object.keys(newVars).length) {
|
|
142
|
+
await this.envApi.setVars(env, newVars);
|
|
143
|
+
}
|
|
148
144
|
}
|
|
149
145
|
}
|
|
150
146
|
console.log('Activating env (to finish creation)');
|
|
@@ -209,6 +205,7 @@ export class EnvCtl {
|
|
|
209
205
|
const { ephemeral, owner, vars } = env;
|
|
210
206
|
const envTerraform = { envName, ephemeral, owner: owner || 'system', args: tfArgs, vars };
|
|
211
207
|
console.log('Destroying env resources');
|
|
208
|
+
await this.terraformAdapter.init(env.key, cwd);
|
|
212
209
|
await this.terraformAdapter.destroy(envTerraform, force, cwd);
|
|
213
210
|
console.log('Unlock env');
|
|
214
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.
|
|
4
|
+
"version": "0.22.0",
|
|
5
5
|
"author": "Alex Chekulaev",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -25,15 +25,17 @@
|
|
|
25
25
|
"test": "vitest run --coverage",
|
|
26
26
|
"build": "tsc",
|
|
27
27
|
"run": "node dist/index.js",
|
|
28
|
-
"run-version": "
|
|
29
|
-
"run-configure": "
|
|
30
|
-
"
|
|
31
|
-
"run-
|
|
32
|
-
"run-
|
|
33
|
-
"run-
|
|
34
|
-
"run-
|
|
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
|
-
"run-ephemeral-create": " tsc --sourceMap true && npm run run -- create-ephemeral --env test
|
|
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",
|
|
38
40
|
"run-ephemeral-init": " tsc --sourceMap true && npm run run -- init --env test --cwd ../tt-gitops",
|
|
39
41
|
"run-ephemeral-deploy": " tsc --sourceMap true && npm run run -- deploy --env test --cwd ../tt-gitops -var-file=versions.tfvars",
|
|
@@ -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"
|