@agilecustoms/envctl 0.19.1 → 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.
@@ -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.fetch('/ci/env', {
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.Creating };
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 = EnvStatus.Updating;
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 apiCreateEphemeral(program) {
5
+ export function createEphemeral(program) {
6
6
  program
7
- .command('api-create-ephemeral')
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
- .requiredOption('--owner <owner>', _keys.OWNER)
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));
@@ -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 { apiCreateEphemeral, configure, deleteIt, deploy, destroy, init, plan, status } from './commands/index.js';
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);
@@ -1,6 +1,7 @@
1
1
  export var EnvStatus;
2
2
  (function (EnvStatus) {
3
- EnvStatus["Creating"] = "CREATING";
3
+ EnvStatus["Init"] = "INIT";
4
+ EnvStatus["Deploying"] = "DEPLOYING";
4
5
  EnvStatus["Active"] = "ACTIVE";
5
6
  EnvStatus["Updating"] = "UPDATING";
6
7
  EnvStatus["Deleting"] = "DELETING";
@@ -1,6 +1,5 @@
1
1
  import { KnownException, TerraformInitRequired } from '../exceptions.js';
2
- import { EnvType, EnvSize } from '../model/index.js';
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(envName, env, input, cwd) {
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,21 +59,16 @@ 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.Active, EnvStatus.Creating, EnvStatus.Updating]) {
63
+ checkStatus(env, availableStatuses = [EnvStatus.Init, EnvStatus.Deploying, EnvStatus.Active, EnvStatus.Updating]) {
70
64
  if (availableStatuses.includes(env.status)) {
71
65
  return;
72
66
  }
73
67
  if (env.status === EnvStatus.Deleting) {
74
68
  const time = EnvSizeAvgTime[env.size];
75
- throw new KnownException(`Env status is DELETING, please wait (~${time} min)`);
69
+ throw new KnownException(`Env ${env.key} status is DELETING, please wait (~${time} min)`);
76
70
  }
77
- throw new KnownException(`Env status is ${env.status}, can not run this command`);
71
+ throw new KnownException(`Env ${env.key} status is ${env.status}, can not run this command`);
78
72
  }
79
73
  async init(project, envName, cwd) {
80
74
  const key = this.key(project, envName);
@@ -89,8 +83,9 @@ export class EnvCtl {
89
83
  await this.terraformAdapter.plan(envTerraform, tfArgs, cwd);
90
84
  return;
91
85
  }
92
- const envInput = this.checkInput(envName, env, input, cwd);
93
- const envTerraform = { ...envInput, ephemeral: false };
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, ephemeral: true, owner, size, type };
108
- const newEnv = await this.envApi.create(createEnv);
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, ephemeral: false, owner: envInput.owner, size, type, kind };
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(envName, env, input, cwd);
116
+ this.checkInput(env, input, cwd);
122
117
  this.checkStatus(env);
123
- if (env.status == EnvStatus.Updating) {
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 !== EnvStatus.Active) {
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
  }
@@ -180,32 +183,38 @@ export class EnvCtl {
180
183
  }
181
184
  console.log('Deleting env');
182
185
  }
183
- const statusMessage = await this.envApi.delete(env);
184
- console.log(statusMessage);
186
+ await this.envApi.delete(env);
187
+ console.log(`Env ${env.key} is scheduled for deletion`);
185
188
  }
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.Creating]);
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');
205
215
  await this.envApi.activate(env);
206
- console.log('Schedule env metadata deletion');
207
- const statusMessage = await this.envApi.delete(env);
208
- console.log(statusMessage);
216
+ console.log(`Schedule env ${env.key} metadata deletion`);
217
+ await this.envApi.delete(env);
209
218
  console.log('Please wait for ~15 sec before you can create env with same name');
210
219
  }
211
220
  async get(project, envName) {
@@ -217,13 +226,12 @@ export class EnvCtl {
217
226
  }
218
227
  return env;
219
228
  }
220
- async promptUnlock(env, statuses = [EnvStatus.Creating, EnvStatus.Updating]) {
229
+ async promptUnlock(env, statuses = [EnvStatus.Deploying, EnvStatus.Updating]) {
221
230
  if (statuses.includes(env.status)) {
222
231
  const answerYes = await this.cliHelper.promptYesNo(`Env status is ${env.status}, likely to be an error from a previous run\n`
223
232
  + 'Do you want to unlock it?');
224
233
  if (answerYes) {
225
234
  await this.envApi.activate(env);
226
- env.status = EnvStatus.Active;
227
235
  }
228
236
  }
229
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.19.1",
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-api-create-ephemeral": "tsc --sourceMap true && npm run run -- api-create-ephemeral --env test --owner laxa1986 --size min --type dev",
37
- "run-api-status": " tsc --sourceMap true && npm run run -- status --env test",
38
- "run-api-init": " tsc --sourceMap true && npm run run -- init --env test --cwd ../tt-gitops",
39
- "run-api-deploy": " tsc --sourceMap true && npm run run -- deploy --env test --cwd ../tt-gitops -var-file=versions.tfvars",
40
- "run-api-delete": " tsc --sourceMap true && npm run run -- delete --env test --cwd ../tt-gitops --force",
41
- "run-api-destroy": "tsc --sourceMap true && npm run run -- destroy --env test --cwd ../tt-gitops --force -var-file=versions.tfvars"
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",