@agilecustoms/envctl 1.25.0 → 1.25.2

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,7 +1,7 @@
1
1
  import { BusinessException, KnownException, NotFoundException } from '../exceptions.js';
2
2
  import { logger } from '../logger.js';
3
3
  import { EnvStatus } from '../model/index.js';
4
- import { toLocalTime } from '../util.js';
4
+ import { format, toDate } from '../util.js';
5
5
  import { HttpClient } from './HttpClient.js';
6
6
  export class EnvApiClient {
7
7
  httpClient;
@@ -12,6 +12,7 @@ export class EnvApiClient {
12
12
  return await fetch(url);
13
13
  }
14
14
  async get(key) {
15
+ logger.info(`Retrieve environment ${key}`);
15
16
  try {
16
17
  return await this.httpClient.fetch(`/ci/env/${key}`);
17
18
  }
@@ -46,7 +47,7 @@ export class EnvApiClient {
46
47
  }
47
48
  throw error;
48
49
  }
49
- logger.debug(`Env ${env.key} created with TTL ${toLocalTime(result.ttl)}`);
50
+ logger.debug(`Environment ${env.key} created, will expire at ${format(toDate(result.ttl))}`);
50
51
  return { ...env, ephemeral: false, status: EnvStatus.Deploying, ttl: result.ttl };
51
52
  }
52
53
  async activate(env) {
package/dist/logger.js CHANGED
@@ -22,7 +22,13 @@ export class Logger {
22
22
  this.log('error', msg);
23
23
  }
24
24
  log(level, msg) {
25
- const date = new Date(this.now()).toISOString();
25
+ const date = new Date(this.now()).toLocaleString('en-US', {
26
+ hour: '2-digit',
27
+ minute: '2-digit',
28
+ second: '2-digit',
29
+ fractionalSecondDigits: 3,
30
+ hour12: false,
31
+ });
26
32
  const detailedMessage = `${date} [${level}] ${msg}`;
27
33
  this.buffer.push(detailedMessage);
28
34
  if (!this.isDebugEnabled() && level === 'debug') {
@@ -3,7 +3,7 @@ import { EnvApiClient } from '../client/index.js';
3
3
  import { KnownException } from '../exceptions.js';
4
4
  import { logger } from '../logger.js';
5
5
  import { EnvStatus } from '../model/index.js';
6
- import { toLocalTime } from '../util.js';
6
+ import { format, toDate } from '../util.js';
7
7
  import { BaseService } from './BaseService.js';
8
8
  import { ApplyMode, getApplyMode } from './TerraformAdapter.js';
9
9
  export class EnvService extends BaseService {
@@ -16,34 +16,22 @@ export class EnvService extends BaseService {
16
16
  }
17
17
  async status(key) {
18
18
  key = this.getKey(key);
19
- logger.info(`Retrieve env ${key}`);
20
19
  const env = await this.envApi.get(key);
21
20
  if (env === null) {
22
- logger.info(`Env ${key} does not exist`);
21
+ logger.info(`Environment ${key} does not exist`);
23
22
  return;
24
23
  }
25
- logger.info(`Env ${key} status: ${env.status}`);
24
+ logger.info(`Environment ${key} status: ${env.status}`);
26
25
  if (env.status !== EnvStatus.Deleting && env.status !== EnvStatus.DeleteError) {
27
- logger.info(`Expires at ${toLocalTime(env.ttl)}`);
26
+ logger.info(`Expires at ${format(toDate(env.ttl))}`);
28
27
  }
29
28
  if (env.dir) {
30
29
  const dir = this.cli.getDir();
31
30
  if (env.dir !== dir) {
32
- logger.warn(`Env ${key} was deployed from ${env.dir}`);
31
+ logger.warn(`Environment ${key} was deployed from ${env.dir}`);
33
32
  }
34
33
  }
35
34
  }
36
- async getEnv(key) {
37
- logger.info(`Check if env ${key} already exists`);
38
- const env = await this.envApi.get(key);
39
- if (env) {
40
- logger.info(`Env ${key} status: ${env.status}`);
41
- }
42
- else {
43
- logger.info(`Env ${key} does not exist`);
44
- }
45
- return env;
46
- }
47
35
  async init(tfArgs) {
48
36
  await this.terraformAdapter.init(tfArgs);
49
37
  await this.terraformAdapter.lockProviders();
@@ -51,7 +39,7 @@ export class EnvService extends BaseService {
51
39
  async plan(tfArgs) {
52
40
  this.terraformAdapter.validateAndGetStateFile();
53
41
  const key = this.terraformAdapter.getKey();
54
- this.terraformAdapter.getLockFile();
42
+ this.terraformAdapter.validateAndGetLockFile();
55
43
  await this.terraformAdapter.plan(key, tfArgs);
56
44
  }
57
45
  async createEphemeral(key) {
@@ -64,35 +52,37 @@ export class EnvService extends BaseService {
64
52
  if (env.dir) {
65
53
  const dir = this.cli.getDir();
66
54
  if (dir !== env.dir) {
67
- throw new KnownException(`Env ${env.key} was deployed from ${env.dir} - make sure you run this command from the same directory`);
55
+ throw new KnownException(`Environment ${env.key} was deployed from ${env.dir} - make sure you run this command from the same directory`);
68
56
  }
69
57
  }
70
58
  }
71
59
  async apply(tfArgs) {
72
60
  const { stateFile, stateUpdated } = this.terraformAdapter.validateAndGetStateFile();
73
61
  const key = this.terraformAdapter.getKey();
74
- const { lockFile, lockUpdated } = this.terraformAdapter.getLockFile();
75
- const applyMode = getApplyMode(tfArgs);
76
- let env = await this.getEnv(key);
62
+ const { lockFile, lockUpdated } = this.terraformAdapter.validateAndGetLockFile();
63
+ let env = await this.envApi.get(key);
77
64
  if (env === null) {
65
+ logger.info(`Environment ${key} does not exist`);
78
66
  const dir = this.cli.getDir();
79
67
  const createEnv = { key, dir, stateFile, lockFile };
80
- logger.info('Creating env metadata');
68
+ logger.info('Creating environment metadata');
81
69
  env = await this.envApi.create(createEnv);
82
70
  await this.runApply(env, tfArgs);
83
71
  return env;
84
72
  }
73
+ logger.info(`Environment ${key} status: ${env.status}`);
85
74
  this.checkDir(env);
86
75
  if (env.status === EnvStatus.Deleting) {
87
- throw new KnownException(`Env ${env.key} status is DELETING, please wait.\n
76
+ throw new KnownException(`Environment ${env.key} status is DELETING, please wait.\n
88
77
  If it is DELETING more than 10 minutes, you can try to run 'envctl delete ${env.key} --force' to remove it`);
89
78
  }
90
79
  if (env.status === EnvStatus.DeleteError) {
91
- throw new KnownException(`Env ${env.key} status is DELETE_ERROR, most likely lack of permissions.\n
80
+ throw new KnownException(`Environment ${env.key} status is DELETE_ERROR, most likely lack of permissions.\n
92
81
  Check logs with 'envctl logs', address the issue and re-run 'envctl delete ${env.key}'`);
93
82
  }
94
83
  const status = env.status;
95
84
  if (status === EnvStatus.Init) {
85
+ const applyMode = getApplyMode(tfArgs);
96
86
  if (applyMode === ApplyMode.Default) {
97
87
  throw new KnownException('Either run apply with planfile or with flag -auto-approve');
98
88
  }
@@ -115,9 +105,8 @@ export class EnvService extends BaseService {
115
105
  }
116
106
  async runApply(env, tfArgs) {
117
107
  this.terraformAdapter.saveHashes();
118
- logger.info('Deploying resources');
119
108
  await this.terraformAdapter.apply(env, tfArgs);
120
- logger.info('Activating env (to finish creation)');
109
+ logger.info('Activate environment');
121
110
  await this.envApi.activate(env);
122
111
  return env;
123
112
  }
@@ -127,30 +116,25 @@ export class EnvService extends BaseService {
127
116
  }
128
117
  async destroy(tfArgs, force) {
129
118
  const key = this.terraformAdapter.getKey();
130
- const env = await this.get(key);
119
+ const env = await this.envApi.get(key);
120
+ if (!env) {
121
+ throw new KnownException(`Environment ${key} is not found`);
122
+ }
123
+ logger.info(`Environment status: ${env.status}`);
131
124
  if (env.status === EnvStatus.Init) {
132
- logger.info(`Env ${env.key} status is INIT - no resources, nothing to destroy, just deleting metadata`);
125
+ logger.info(`Environment ${env.key} status is INIT - no resources, nothing to destroy, just deleting metadata`);
133
126
  await this.envApi.delete(key, force);
134
127
  return;
135
128
  }
136
129
  if (env.status === EnvStatus.Deleting && !force) {
137
- throw new KnownException(`Env ${env.key} status is DELETING, please wait or re-run with --force`);
130
+ throw new KnownException(`Environment ${env.key} status is DELETING, please wait or re-run with --force`);
138
131
  }
139
132
  if (env.status === EnvStatus.Active) {
140
- logger.info('Lock env to run destroy');
133
+ logger.info('Lock environment to run destroy');
141
134
  await this.envApi.lockForUpdate(env);
142
135
  }
143
136
  await this.terraformAdapter.destroy(key, tfArgs, force);
144
137
  await this.envApi.delete(key, force);
145
- logger.info('Please wait for ~30 sec before you can create env with same name');
146
- }
147
- async get(key) {
148
- logger.info(`Retrieve env ${key}`);
149
- const env = await this.envApi.get(key);
150
- if (!env) {
151
- throw new KnownException(`Environment ${key} is not found`);
152
- }
153
- logger.info(`Env status: ${env.status}`);
154
- return env;
138
+ logger.info('Please wait for ~30 sec before you can create environment with same name');
155
139
  }
156
140
  }
@@ -5,6 +5,7 @@ import { confirm } from '@inquirer/prompts';
5
5
  import {} from '../client/Cli.js';
6
6
  import { AbortedException, KnownException, ProcessException, TimeoutException } from '../exceptions.js';
7
7
  import { logger } from '../logger.js';
8
+ import { format } from '../util.js';
8
9
  import { LocalStateService } from './LocalStateService.js';
9
10
  export const MAX_ATTEMPTS = 2;
10
11
  export const RETRYABLE_ERRORS = [
@@ -108,7 +109,7 @@ export class TerraformAdapter {
108
109
  config.stateHash = fileHash;
109
110
  return { stateFile: fileContent, stateUpdated };
110
111
  }
111
- _getLockFile() {
112
+ getLockFile() {
112
113
  const lockPath = path.join(process.cwd(), '.terraform.lock.hcl');
113
114
  if (!fs.existsSync(lockPath)) {
114
115
  throw new KnownException(`Terraform lock file not found at: ${lockPath}`);
@@ -120,8 +121,8 @@ export class TerraformAdapter {
120
121
  throw new KnownException(`Failed to read terraform lock file: ${lockPath}`, { cause });
121
122
  }
122
123
  }
123
- getLockFile() {
124
- const fileContent = this._getLockFile();
124
+ validateAndGetLockFile() {
125
+ const fileContent = this.getLockFile();
125
126
  const config = this.localStateService.load();
126
127
  const fileHash = hash(fileContent);
127
128
  if (fileHash !== config.lockFileHash) {
@@ -141,18 +142,14 @@ export class TerraformAdapter {
141
142
  logger.info('Lock providers');
142
143
  await this.cli.run('terraform', ['providers', 'lock', '-platform=linux_arm64']);
143
144
  }
144
- const fileContent = this._getLockFile();
145
+ const fileContent = this.getLockFile();
145
146
  const config = this.localStateService.load();
146
147
  config.lockFileHash = hash(fileContent);
147
148
  this.localStateService.save(config);
148
149
  }
149
150
  printTime() {
150
151
  const now = new Date();
151
- const utc = now.toISOString();
152
- const edt = now.toLocaleString('en-US', {
153
- timeZone: 'America/New_York'
154
- });
155
- logger.info(`\nTime EDT: ${edt}, UTC: ${utc}\n`);
152
+ logger.info(`\nLocal time: ${format(now)}, UTC: ${now.toISOString()}\n`);
156
153
  }
157
154
  tfArgs(key, args) {
158
155
  const varDefs = this.getVarDefs();
package/dist/util.js CHANGED
@@ -1,5 +1,7 @@
1
- export function toLocalTime(ttl, timeZone) {
2
- const date = new Date(ttl * 1000);
1
+ export function toDate(ttl) {
2
+ return new Date(ttl * 1000);
3
+ }
4
+ export function format(date, timeZone) {
3
5
  return date.toLocaleString('en-US', {
4
6
  timeZone,
5
7
  month: 'short',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agilecustoms/envctl",
3
- "version": "1.25.0",
3
+ "version": "1.25.2",
4
4
  "description": "node.js CLI client for manage environments",
5
5
  "keywords": [
6
6
  "terraform wrapper",