@agilecustoms/envctl 0.19.2 → 0.20.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.
- package/dist/client/EnvApiClient.js +9 -10
- package/dist/client/HttpClient.js +10 -0
- package/dist/commands/{apiCreateEphemeral.js → createEphemeral.js} +3 -3
- package/dist/commands/index.js +1 -1
- package/dist/index.js +2 -2
- package/dist/model/EnvStatus.js +2 -1
- package/dist/service/EnvCtl.js +31 -22
- package/package.json +7 -7
|
@@ -17,16 +17,15 @@ export class EnvApiClient {
|
|
|
17
17
|
throw error;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
async createEphemeral(env) {
|
|
21
|
+
const res = await this.httpClient.post('/ci/env-ephemeral', env);
|
|
22
|
+
const token = res.token;
|
|
23
|
+
return { ...env, token, ephemeral: true, status: EnvStatus.Init };
|
|
24
|
+
}
|
|
20
25
|
async create(env) {
|
|
21
|
-
const res = await this.httpClient.
|
|
22
|
-
method: 'POST',
|
|
23
|
-
body: JSON.stringify(env),
|
|
24
|
-
headers: {
|
|
25
|
-
'Content-Type': 'application/json'
|
|
26
|
-
}
|
|
27
|
-
});
|
|
26
|
+
const res = await this.httpClient.post('/ci/env', env);
|
|
28
27
|
const token = res.token;
|
|
29
|
-
return { ...env, token, status: EnvStatus.
|
|
28
|
+
return { ...env, token, ephemeral: false, status: EnvStatus.Deploying };
|
|
30
29
|
}
|
|
31
30
|
async activate(env) {
|
|
32
31
|
await this.httpClient.fetch(`/ci/env/${env.key}/activate`, {
|
|
@@ -35,10 +34,10 @@ export class EnvApiClient {
|
|
|
35
34
|
env.status = EnvStatus.Active;
|
|
36
35
|
}
|
|
37
36
|
async lockForUpdate(env) {
|
|
38
|
-
await this.httpClient.fetch(`/ci/env/${env.key}/lock-for-update`, {
|
|
37
|
+
const res = await this.httpClient.fetch(`/ci/env/${env.key}/lock-for-update`, {
|
|
39
38
|
method: 'POST'
|
|
40
39
|
});
|
|
41
|
-
env.status =
|
|
40
|
+
env.status = res.status;
|
|
42
41
|
}
|
|
43
42
|
async delete(env) {
|
|
44
43
|
let result;
|
|
@@ -6,6 +6,16 @@ export class HttpClient {
|
|
|
6
6
|
constructor(awsCredsHelper) {
|
|
7
7
|
this.awsCredsHelper = awsCredsHelper;
|
|
8
8
|
}
|
|
9
|
+
async post(path, body, options = {}) {
|
|
10
|
+
return await this.fetch(path, {
|
|
11
|
+
method: 'POST',
|
|
12
|
+
body: JSON.stringify(body),
|
|
13
|
+
headers: {
|
|
14
|
+
'Content-Type': 'application/json'
|
|
15
|
+
},
|
|
16
|
+
...options
|
|
17
|
+
});
|
|
18
|
+
}
|
|
9
19
|
async fetch(path, options = {}) {
|
|
10
20
|
const awsCreds = await this.awsCredsHelper.getCredentials();
|
|
11
21
|
const requestOptions = {
|
|
@@ -2,13 +2,13 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { envCtl } from '../container.js';
|
|
3
3
|
import { _keys } from './_keys.js';
|
|
4
4
|
import { wrap } from './_utils.js';
|
|
5
|
-
export function
|
|
5
|
+
export function createEphemeral(program) {
|
|
6
6
|
program
|
|
7
|
-
.command('
|
|
7
|
+
.command('create-ephemeral')
|
|
8
8
|
.description('Create bare env w/o resources. Used in CI as first step just to pre-generate token for extension')
|
|
9
9
|
.option('--project <project>', _keys.PROJECT)
|
|
10
10
|
.requiredOption('--env <env>', _keys.ENV)
|
|
11
|
-
.
|
|
11
|
+
.option('--owner <owner>', _keys.OWNER)
|
|
12
12
|
.requiredOption('--size <size>', _keys.SIZE)
|
|
13
13
|
.requiredOption('--type <type>', _keys.TYPE)
|
|
14
14
|
.action(wrap(handler));
|
package/dist/commands/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { apiCreateEphemeral } from './apiCreateEphemeral.js';
|
|
2
1
|
export { configure } from './configure.js';
|
|
2
|
+
export { createEphemeral } from './createEphemeral.js';
|
|
3
3
|
export { deleteIt } from './delete.js';
|
|
4
4
|
export { deploy } from './deploy.js';
|
|
5
5
|
export { destroy } from './destroy.js';
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import updateNotifier from 'update-notifier';
|
|
5
|
-
import {
|
|
5
|
+
import { configure, createEphemeral, deleteIt, deploy, destroy, init, plan, status } from './commands/index.js';
|
|
6
6
|
const require = createRequire(import.meta.url);
|
|
7
7
|
const pkg = require('../package.json');
|
|
8
8
|
updateNotifier({ pkg, updateCheckInterval: 0 }).notify();
|
|
@@ -11,8 +11,8 @@ program
|
|
|
11
11
|
.name('envctl')
|
|
12
12
|
.description('CLI to manage environments')
|
|
13
13
|
.version(pkg.version);
|
|
14
|
-
apiCreateEphemeral(program);
|
|
15
14
|
configure(program);
|
|
15
|
+
createEphemeral(program);
|
|
16
16
|
deleteIt(program);
|
|
17
17
|
deploy(program);
|
|
18
18
|
destroy(program);
|
package/dist/model/EnvStatus.js
CHANGED
package/dist/service/EnvCtl.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { KnownException, TerraformInitRequired } from '../exceptions.js';
|
|
2
|
-
import {
|
|
3
|
-
import { EnvSizeAvgTime, EnvStatus } from '../model/index.js';
|
|
2
|
+
import { EnvSize, EnvSizeAvgTime, EnvStatus, EnvType } from '../model/index.js';
|
|
4
3
|
export class EnvCtl {
|
|
5
4
|
cliHelper;
|
|
6
5
|
envApi;
|
|
@@ -46,7 +45,7 @@ export class EnvCtl {
|
|
|
46
45
|
}
|
|
47
46
|
return { envName, owner, key, env };
|
|
48
47
|
}
|
|
49
|
-
checkInput(
|
|
48
|
+
checkInput(env, input, cwd) {
|
|
50
49
|
if (input.owner && env.owner !== input.owner) {
|
|
51
50
|
throw new KnownException(`Can not change env owner ${env.owner} -> ${input.owner}`);
|
|
52
51
|
}
|
|
@@ -60,13 +59,8 @@ export class EnvCtl {
|
|
|
60
59
|
if ((input.kind || env.kind) && kind !== env.kind) {
|
|
61
60
|
throw new KnownException(`Can not change env kind ${env.kind} -> ${kind}`);
|
|
62
61
|
}
|
|
63
|
-
const { owner, size, type } = env;
|
|
64
|
-
return {
|
|
65
|
-
project: input.project, env: envName,
|
|
66
|
-
owner, size, type, kind,
|
|
67
|
-
};
|
|
68
62
|
}
|
|
69
|
-
checkStatus(env, availableStatuses = [EnvStatus.
|
|
63
|
+
checkStatus(env, availableStatuses = [EnvStatus.Init, EnvStatus.Deploying, EnvStatus.Active, EnvStatus.Updating]) {
|
|
70
64
|
if (availableStatuses.includes(env.status)) {
|
|
71
65
|
return;
|
|
72
66
|
}
|
|
@@ -89,8 +83,9 @@ export class EnvCtl {
|
|
|
89
83
|
await this.terraformAdapter.plan(envTerraform, tfArgs, cwd);
|
|
90
84
|
return;
|
|
91
85
|
}
|
|
92
|
-
|
|
93
|
-
const
|
|
86
|
+
this.checkInput(env, input, cwd);
|
|
87
|
+
const envOwner = env.owner;
|
|
88
|
+
const envTerraform = { ...env, env: envName, owner: envOwner, ephemeral: false };
|
|
94
89
|
this.checkStatus(env);
|
|
95
90
|
await this.promptUnlock(env);
|
|
96
91
|
if (env.status !== EnvStatus.Active) {
|
|
@@ -104,8 +99,8 @@ export class EnvCtl {
|
|
|
104
99
|
if (env !== null) {
|
|
105
100
|
throw new KnownException(`Env ${key} already exists`);
|
|
106
101
|
}
|
|
107
|
-
const createEnv = { key,
|
|
108
|
-
const newEnv = await this.envApi.
|
|
102
|
+
const createEnv = { key, owner, size, type };
|
|
103
|
+
const newEnv = await this.envApi.createEphemeral(createEnv);
|
|
109
104
|
return newEnv.token;
|
|
110
105
|
}
|
|
111
106
|
async deploy(input, tfArgs, cwd) {
|
|
@@ -114,13 +109,16 @@ export class EnvCtl {
|
|
|
114
109
|
const envInput = await this.cliHelper.parseEnvInput(input, envName, owner, cwd);
|
|
115
110
|
console.log('Creating env tracking record in DynamoDB');
|
|
116
111
|
const { size, type, kind } = envInput;
|
|
117
|
-
const createEnv = { key,
|
|
112
|
+
const createEnv = { key, owner: envInput.owner, size, type, kind };
|
|
118
113
|
const newEnv = await this.envApi.create(createEnv);
|
|
119
114
|
return await this.runDeploy(newEnv, envName, tfArgs, cwd);
|
|
120
115
|
}
|
|
121
|
-
this.checkInput(
|
|
116
|
+
this.checkInput(env, input, cwd);
|
|
122
117
|
this.checkStatus(env);
|
|
123
|
-
if (env.status
|
|
118
|
+
if (env.status === EnvStatus.Init) {
|
|
119
|
+
await this.envApi.lockForUpdate(env);
|
|
120
|
+
}
|
|
121
|
+
else if (env.status == EnvStatus.Updating) {
|
|
124
122
|
const answerYes = await this.cliHelper.promptYesNo(`Env status is ${env.status}, likely to be an error from a previous run\n`
|
|
125
123
|
+ 'Do you want to proceed with deployment?');
|
|
126
124
|
if (!answerYes) {
|
|
@@ -137,7 +135,7 @@ export class EnvCtl {
|
|
|
137
135
|
async runDeploy(env, envName, tfArgs, cwd) {
|
|
138
136
|
console.log('Deploying resources');
|
|
139
137
|
const { owner, size, type, ephemeral } = env;
|
|
140
|
-
const envTf = { owner, size, type, ephemeral, env: envName };
|
|
138
|
+
const envTf = { owner: owner || 'system', size, type, ephemeral, env: envName };
|
|
141
139
|
try {
|
|
142
140
|
await this.terraformAdapter.apply(envTf, tfArgs, cwd);
|
|
143
141
|
}
|
|
@@ -156,9 +154,14 @@ export class EnvCtl {
|
|
|
156
154
|
async delete(project, envName, force, cwd) {
|
|
157
155
|
const env = await this.get(project, envName);
|
|
158
156
|
this.checkStatus(env);
|
|
157
|
+
if (env.status === EnvStatus.Init) {
|
|
158
|
+
await this.envApi.delete(env);
|
|
159
|
+
console.log(`Env ${env.key} is deleted`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
159
162
|
if (force) {
|
|
160
163
|
console.log('Force deletion');
|
|
161
|
-
if (env.status
|
|
164
|
+
if (env.status === EnvStatus.Deploying || env.status === EnvStatus.Updating) {
|
|
162
165
|
console.log(`Env status is ${env.status}, will unlock it`);
|
|
163
166
|
await this.envApi.activate(env);
|
|
164
167
|
}
|
|
@@ -186,19 +189,26 @@ export class EnvCtl {
|
|
|
186
189
|
async destroy(project, envName, tfArgs, force, cwd) {
|
|
187
190
|
const env = await this.get(project, envName);
|
|
188
191
|
this.checkStatus(env);
|
|
192
|
+
if (env.status === EnvStatus.Init) {
|
|
193
|
+
console.log(`Env ${env.key} has no resources, nothing to destroy, call 'delete' command instead`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
189
196
|
if (!force) {
|
|
190
197
|
const kind = this.cliHelper.ensureKind(undefined, cwd);
|
|
191
198
|
if (env.kind && env.kind !== kind) {
|
|
192
199
|
throw new KnownException(`Env ${env.key} kind (dir-name): ${env.kind} - make sure you run destroy from the same directory`);
|
|
193
200
|
}
|
|
194
|
-
await this.promptUnlock(env, [EnvStatus.
|
|
201
|
+
await this.promptUnlock(env, [EnvStatus.Deploying]);
|
|
202
|
+
if (env.status === EnvStatus.Deploying) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
195
205
|
}
|
|
196
206
|
if (env.status === EnvStatus.Active) {
|
|
197
207
|
console.log('Lock env to run destroy');
|
|
198
208
|
await this.envApi.lockForUpdate(env);
|
|
199
209
|
}
|
|
200
210
|
const { owner, size, type, ephemeral } = env;
|
|
201
|
-
const tfEnv = { owner, size, type, ephemeral, env: envName };
|
|
211
|
+
const tfEnv = { owner: owner || 'system', size, type, ephemeral, env: envName };
|
|
202
212
|
console.log('Destroying env resources');
|
|
203
213
|
await this.terraformAdapter.destroy(tfEnv, tfArgs, force, cwd);
|
|
204
214
|
console.log('Unlock env');
|
|
@@ -216,13 +226,12 @@ export class EnvCtl {
|
|
|
216
226
|
}
|
|
217
227
|
return env;
|
|
218
228
|
}
|
|
219
|
-
async promptUnlock(env, statuses = [EnvStatus.
|
|
229
|
+
async promptUnlock(env, statuses = [EnvStatus.Deploying, EnvStatus.Updating]) {
|
|
220
230
|
if (statuses.includes(env.status)) {
|
|
221
231
|
const answerYes = await this.cliHelper.promptYesNo(`Env status is ${env.status}, likely to be an error from a previous run\n`
|
|
222
232
|
+ 'Do you want to unlock it?');
|
|
223
233
|
if (answerYes) {
|
|
224
234
|
await this.envApi.activate(env);
|
|
225
|
-
env.status = EnvStatus.Active;
|
|
226
235
|
}
|
|
227
236
|
}
|
|
228
237
|
}
|
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.20.0",
|
|
5
5
|
"author": "Alex Chekulaev",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -33,12 +33,12 @@
|
|
|
33
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
|
-
"run-
|
|
37
|
-
"run-
|
|
38
|
-
"run-
|
|
39
|
-
"run-
|
|
40
|
-
"run-
|
|
41
|
-
"run-
|
|
36
|
+
"run-ephemeral-create": " tsc --sourceMap true && npm run run -- create-ephemeral --env test --size min --type dev",
|
|
37
|
+
"run-ephemeral-status": " tsc --sourceMap true && npm run run -- status --env test",
|
|
38
|
+
"run-ephemeral-init": " tsc --sourceMap true && npm run run -- init --env test --cwd ../tt-gitops",
|
|
39
|
+
"run-ephemeral-deploy": " tsc --sourceMap true && npm run run -- deploy --env test --cwd ../tt-gitops -var-file=versions.tfvars",
|
|
40
|
+
"run-ephemeral-delete": " tsc --sourceMap true && npm run run -- delete --env test --cwd ../tt-gitops --force",
|
|
41
|
+
"run-ephemeral-destroy": "tsc --sourceMap true && npm run run -- destroy --env test --cwd ../tt-gitops --force -var-file=versions.tfvars"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@aws-sdk/client-sts": "^3.716.0",
|