@agilecustoms/envctl 0.36.2 → 0.37.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/container.js CHANGED
@@ -3,6 +3,7 @@ import { AwsCredsHelper } from './client/AwsCredsHelper.js';
3
3
  import { CliHelper, EnvApiClient, HttpClient, TerraformAdapter } from './client/index.js';
4
4
  import { ProcessRunner } from './client/ProcessRunner.js';
5
5
  import { ConfigService, EnvService } from './service/index.js';
6
+ import { LocalStateService } from './service/LocalStateService.js';
6
7
  import { LogService } from './service/LogService.js';
7
8
  const cliHelper = new CliHelper();
8
9
  const configService = new ConfigService();
@@ -14,6 +15,7 @@ const httpClient = new HttpClient(awsCredsHelper);
14
15
  const envApiClient = new EnvApiClient(httpClient);
15
16
  const processRunner = new ProcessRunner();
16
17
  const terraformAdapter = new TerraformAdapter(processRunner, cliHelper, backends);
17
- const envService = new EnvService(cliHelper, envApiClient, terraformAdapter);
18
+ const localStateService = new LocalStateService();
19
+ const envService = new EnvService(cliHelper, envApiClient, terraformAdapter, localStateService);
18
20
  const logService = new LogService(envApiClient, terraformAdapter, processRunner);
19
21
  export { awsCredsHelper, configService, envService, logService };
@@ -1,13 +1,17 @@
1
+ import { CliHelper, EnvApiClient, TerraformAdapter } from '../client/index.js';
1
2
  import { KnownException } from '../exceptions.js';
2
- import { EnvStatus, } from '../model/index.js';
3
+ import { EnvStatus } from '../model/index.js';
4
+ import { LocalStateService } from './LocalStateService.js';
3
5
  export class EnvService {
4
6
  cliHelper;
5
7
  envApi;
6
8
  terraformAdapter;
7
- constructor(cliHelper, envApi, terraformAdapter) {
9
+ localStateService;
10
+ constructor(cliHelper, envApi, terraformAdapter, localStateService) {
8
11
  this.cliHelper = cliHelper;
9
12
  this.envApi = envApi;
10
13
  this.terraformAdapter = terraformAdapter;
14
+ this.localStateService = localStateService;
11
15
  }
12
16
  async status(key, cwd) {
13
17
  key = this.terraformAdapter.getKey(key, cwd);
@@ -81,15 +85,33 @@ export class EnvService {
81
85
  return await this.envApi.createEphemeral(createEnv);
82
86
  }
83
87
  async lockTerraform(env, cwd) {
88
+ console.log('load local state config');
89
+ const config = this.localStateService.load(cwd);
84
90
  console.log('validate terraform.tfstate file');
85
- env.stateFile = this.terraformAdapter.getTerraformBackend(cwd).validateAndGetStateFileContent();
86
- await this.terraformAdapter.lockProviders(cwd);
87
- env.lockFile = this.terraformAdapter.getLockFile(cwd);
91
+ const stateFileContent = this.terraformAdapter.getTerraformBackend(cwd).validateAndGetStateFileContent();
92
+ const stateHash = this.localStateService.hash(stateFileContent);
93
+ if (config.stateHash !== stateHash) {
94
+ config.stateHash = stateHash;
95
+ env.stateFile = stateFileContent;
96
+ }
97
+ const linuxArm64 = process.platform === 'linux' && process.arch === 'arm64';
98
+ if (!linuxArm64) {
99
+ let lockFileContent = this.terraformAdapter.getLockFile(cwd);
100
+ const lockFileHash = this.localStateService.hash(lockFileContent);
101
+ if (config.lockHash !== lockFileHash) {
102
+ await this.terraformAdapter.lockProviders(cwd);
103
+ lockFileContent = this.terraformAdapter.getLockFile(cwd);
104
+ config.lockHash = this.localStateService.hash(lockFileContent);
105
+ env.lockFile = lockFileContent;
106
+ }
107
+ }
108
+ this.localStateService.save(config, cwd);
88
109
  }
89
110
  async deploy(tfArgs, cwd) {
90
111
  const key = this.terraformAdapter.getTerraformBackend(cwd).getKey();
91
112
  let env = await this.tryGetEnv(key);
92
113
  if (env === null) {
114
+ this.localStateService.init(cwd);
93
115
  const kind = this.cliHelper.getKind(cwd);
94
116
  console.log(`Inferred kind from directory name: ${kind}`);
95
117
  const createEnv = { key, kind };
@@ -102,6 +124,10 @@ export class EnvService {
102
124
  this.checkKind(env, cwd);
103
125
  switch (env.status) {
104
126
  case EnvStatus.Init:
127
+ this.localStateService.init(cwd);
128
+ await this.lockTerraform(env, cwd);
129
+ await this.envApi.lockForUpdate(env);
130
+ break;
105
131
  case EnvStatus.Active:
106
132
  if (env.status === EnvStatus.Active) {
107
133
  console.log('Env status is ACTIVE\nWill lock for update and run terraform apply (to update resources)');
@@ -0,0 +1,58 @@
1
+ import { createHash } from 'node:crypto';
2
+ import * as fs from 'node:fs';
3
+ import path from 'path';
4
+ const CURRENT_VERSION = 1;
5
+ export class LocalStateService {
6
+ config;
7
+ constructor() {
8
+ }
9
+ filePath(cwd) {
10
+ cwd = cwd || process.cwd();
11
+ return path.join(cwd, '.terraform', 'envctl.json');
12
+ }
13
+ hash(data) {
14
+ return createHash('sha256').update(data).digest('hex');
15
+ }
16
+ createEmptyConfig(filePath) {
17
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
18
+ this.config = { version: CURRENT_VERSION };
19
+ fs.writeFileSync(filePath, JSON.stringify(this.config, null, 2));
20
+ return this.config;
21
+ }
22
+ init(cwd) {
23
+ const filePath = this.filePath(cwd);
24
+ if (!fs.existsSync(filePath)) {
25
+ this.createEmptyConfig(filePath);
26
+ return;
27
+ }
28
+ const data = fs.readFileSync(filePath, 'utf-8');
29
+ const config = JSON.parse(data);
30
+ if (config.version == CURRENT_VERSION) {
31
+ this.config = config;
32
+ return;
33
+ }
34
+ console.log('local state file version mismatch, re-initializing');
35
+ this.createEmptyConfig(filePath);
36
+ }
37
+ load(cwd) {
38
+ if (this.config)
39
+ return this.config;
40
+ const filePath = this.filePath(cwd);
41
+ if (!fs.existsSync(filePath)) {
42
+ console.warn('Local state file does not exist, must have been accidentally deleted, re-initializing');
43
+ return this.createEmptyConfig(filePath);
44
+ }
45
+ const data = fs.readFileSync(filePath, 'utf-8');
46
+ this.config = JSON.parse(data);
47
+ return this.config;
48
+ }
49
+ save(config, cwd) {
50
+ if (!this.config) {
51
+ throw new Error('call init or load first');
52
+ }
53
+ const mergedConfig = { ...this.load(cwd), ...config };
54
+ const data = JSON.stringify(mergedConfig, null, 2);
55
+ const filePath = this.filePath(cwd);
56
+ fs.writeFileSync(filePath, data);
57
+ }
58
+ }
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.36.2",
4
+ "version": "0.37.0",
5
5
  "author": "Alex Chekulaev",
6
6
  "type": "module",
7
7
  "engines": {
@@ -32,7 +32,7 @@
32
32
  "run-version": " tsc --sourceMap true && npm run run -- --version",
33
33
  "run-configure": "tsc --sourceMap true && npm run run -- configure",
34
34
  "~": "",
35
- "run-core-init": "cd ../tt-core && terraform init -upgrade -backend-config=key=laxa1986",
35
+ "run-core-init": "cd ../tt-core && terraform init -upgrade -backend-config=key=laxa1986 -reconfigure",
36
36
  "run-core-status": " tsc --sourceMap true && npm run run -- status --cwd ../tt-core",
37
37
  "run-core-plan": " tsc --sourceMap true && npm run run -- plan --cwd ../tt-core",
38
38
  "run-core-deploy": " tsc --sourceMap true && npm run run -- deploy --cwd ../tt-core -var=\"env_size=min\"",