@agilecustoms/envctl 0.9.0 → 0.10.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/commands/configure.js +23 -0
- package/dist/commands/delete.js +13 -2
- package/dist/commands/deploy.js +33 -5
- package/dist/commands/index.js +1 -0
- package/dist/commands/utils.js +21 -11
- package/dist/container.js +3 -2
- package/dist/index.js +2 -1
- package/dist/model/EnvSize.js +11 -0
- package/dist/model/EnvStatus.js +7 -0
- package/dist/model/EnvType.js +5 -0
- package/dist/model/index.js +4 -1
- package/dist/service/ConfigService.js +28 -0
- package/dist/service/EnvCtl.js +21 -18
- package/dist/service/index.js +1 -0
- package/package.json +5 -3
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { configService } from '../container.js';
|
|
4
|
+
import { wrap } from './utils.js';
|
|
5
|
+
export function configure(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('configure')
|
|
8
|
+
.description('Configure user settings on your local machine')
|
|
9
|
+
.action(wrap(handler));
|
|
10
|
+
}
|
|
11
|
+
async function handler() {
|
|
12
|
+
const answers = await inquirer.prompt([
|
|
13
|
+
{
|
|
14
|
+
type: 'input',
|
|
15
|
+
name: 'owner',
|
|
16
|
+
message: 'owner is required to deploy any env\n'
|
|
17
|
+
+ 'this value will be used as default - if not provided via --owner key in "deploy" command\n'
|
|
18
|
+
+ '(prefer GitHub username)\n'
|
|
19
|
+
+ 'owner:',
|
|
20
|
+
},
|
|
21
|
+
]);
|
|
22
|
+
configService.saveConfig({ owner: answers.owner });
|
|
23
|
+
}
|
package/dist/commands/delete.js
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { envCtl } from '../container.js';
|
|
2
|
+
import { configService, envCtl } from '../container.js';
|
|
3
|
+
import { KnownException } from '../exceptions.js';
|
|
3
4
|
import { wrap } from './utils.js';
|
|
4
5
|
export function deleteIt(program) {
|
|
5
6
|
program
|
|
6
7
|
.command('delete')
|
|
7
8
|
.description('Delete a development environment')
|
|
8
|
-
.
|
|
9
|
+
.option('--env <env>', 'Environment name (can be git hash). {project}-{env} give env key used to store env state (s3 key in case of AWS)')
|
|
9
10
|
.option('--project <project>', 'Project code (like tt). Used when multiple projects deployed in same AWS account')
|
|
10
11
|
.action(wrap(handler));
|
|
11
12
|
}
|
|
12
13
|
async function handler(options) {
|
|
13
14
|
let { env, project } = options;
|
|
15
|
+
if (!env) {
|
|
16
|
+
const owner = configService.getOwner();
|
|
17
|
+
if (!owner) {
|
|
18
|
+
throw new KnownException('--env argument is not provided\n'
|
|
19
|
+
+ 'default to owner from local configuration, but it is not configured\n'
|
|
20
|
+
+ 'please call with --env argument or run \'envctl configure\' to configure owner');
|
|
21
|
+
}
|
|
22
|
+
console.log(`Env name not provided, default to owner name ${owner}`);
|
|
23
|
+
env = owner;
|
|
24
|
+
}
|
|
14
25
|
await envCtl.delete(env, project);
|
|
15
26
|
}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { configService, envCtl } from '../container.js';
|
|
4
|
+
import { KnownException } from '../exceptions.js';
|
|
5
|
+
import { EnvType } from '../model/index.js';
|
|
6
|
+
import { EnvSize } from '../model/index.js';
|
|
7
|
+
import { ensureEnumValue, ensureKind, wrap } from './utils.js';
|
|
4
8
|
export function deploy(program) {
|
|
5
9
|
program
|
|
6
10
|
.command('deploy')
|
|
7
11
|
.description('Create new or update existing dev environment')
|
|
8
12
|
.option('--project <project>', 'Project code (like tt). Used when multiple projects deployed in same AWS account')
|
|
9
13
|
.option('--env <env>', 'Environment name (can be git hash). {project}-{env} give env key used to store env state (s3 key in case of AWS)')
|
|
10
|
-
.
|
|
11
|
-
.
|
|
14
|
+
.option('--owner <owner>', 'Environment owner (GH username)')
|
|
15
|
+
.option('--size <size>', 'Environment size: min, small, full')
|
|
12
16
|
.option('--type <type>', 'Environment type: dev, prod', 'dev')
|
|
13
17
|
.option('--kind <kind>', 'Environment kind: complete project (default) or some slice such as tt-core + tt-web')
|
|
14
18
|
.option('--cwd <cwd>', 'Working directory (default: current directory)')
|
|
@@ -18,11 +22,35 @@ export function deploy(program) {
|
|
|
18
22
|
}
|
|
19
23
|
async function handler(tfArgs, options) {
|
|
20
24
|
let { project, env, owner, size, type, kind, cwd } = options;
|
|
25
|
+
if (!owner) {
|
|
26
|
+
owner = configService.getOwner();
|
|
27
|
+
if (!owner) {
|
|
28
|
+
throw new KnownException('when called without --owner option, first call \'envctl configure\'');
|
|
29
|
+
}
|
|
30
|
+
console.log(`Owner not provided, default to configured owner ${owner}`);
|
|
31
|
+
}
|
|
21
32
|
if (!env) {
|
|
22
33
|
console.log(`Env name not provided, default to owner name ${owner}`);
|
|
23
34
|
env = owner;
|
|
24
35
|
}
|
|
25
36
|
kind = ensureKind(kind, cwd);
|
|
26
|
-
|
|
37
|
+
let envSize;
|
|
38
|
+
if (size) {
|
|
39
|
+
envSize = ensureEnumValue(EnvSize, size, 'size');
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const answer = await inquirer.prompt([
|
|
43
|
+
{
|
|
44
|
+
type: 'list',
|
|
45
|
+
name: 'size',
|
|
46
|
+
message: 'Environment size:',
|
|
47
|
+
choices: Object.values(EnvSize),
|
|
48
|
+
default: EnvSize.Small,
|
|
49
|
+
}
|
|
50
|
+
]);
|
|
51
|
+
envSize = answer.size;
|
|
52
|
+
}
|
|
53
|
+
const envType = ensureEnumValue(EnvType, type, 'type');
|
|
54
|
+
const envAttributes = { project, env, owner, size: envSize, type: envType, kind };
|
|
27
55
|
await envCtl.deploy(envAttributes, tfArgs, options.cwd);
|
|
28
56
|
}
|
package/dist/commands/index.js
CHANGED
package/dist/commands/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { ExitPromptError } from '@inquirer/core';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
4
|
import { BusinessException, ExitException, KnownException } from '../exceptions.js';
|
|
5
5
|
export function wrap(callable) {
|
|
6
6
|
return async (...args) => {
|
|
@@ -12,7 +12,9 @@ export function wrap(callable) {
|
|
|
12
12
|
if (error instanceof KnownException || error instanceof BusinessException) {
|
|
13
13
|
console.error(error.message);
|
|
14
14
|
}
|
|
15
|
-
else if (
|
|
15
|
+
else if (error instanceof ExitException || error instanceof ExitPromptError) {
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
16
18
|
console.error('Unknown error:', error);
|
|
17
19
|
}
|
|
18
20
|
process.exit(1);
|
|
@@ -22,16 +24,17 @@ export function wrap(callable) {
|
|
|
22
24
|
}
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
|
-
export async function
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
export async function promptYesNo(message, defaultValue = false) {
|
|
28
|
+
const { answer } = await inquirer.prompt([
|
|
29
|
+
{
|
|
30
|
+
type: 'confirm',
|
|
31
|
+
name: 'answer',
|
|
32
|
+
message,
|
|
33
|
+
default: defaultValue,
|
|
34
|
+
},
|
|
35
|
+
]);
|
|
29
36
|
return answer;
|
|
30
37
|
}
|
|
31
|
-
export async function promptYesNo(question) {
|
|
32
|
-
const answer = await prompt(question);
|
|
33
|
-
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
34
|
-
}
|
|
35
38
|
export function ensureKind(kind, cwd) {
|
|
36
39
|
if (kind) {
|
|
37
40
|
return kind;
|
|
@@ -53,3 +56,10 @@ function resolveCwd(cwd) {
|
|
|
53
56
|
}
|
|
54
57
|
return path.resolve(process.cwd(), cwd);
|
|
55
58
|
}
|
|
59
|
+
export function ensureEnumValue(enumObj, value, name) {
|
|
60
|
+
const values = Object.values(enumObj);
|
|
61
|
+
if (!values.includes(value)) {
|
|
62
|
+
throw new KnownException(`Invalid ${name}: "${value}". Must be one of: ${values.join(', ')}`);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
package/dist/container.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { EnvApiClient, HttpClient, TerraformAdapter } from './client/index.js';
|
|
2
2
|
import { ProcessRunner } from './client/ProcessRunner.js';
|
|
3
|
-
import { EnvCtl } from './service/index.js';
|
|
3
|
+
import { ConfigService, EnvCtl } from './service/index.js';
|
|
4
|
+
const configService = new ConfigService();
|
|
4
5
|
const httpClient = new HttpClient();
|
|
5
6
|
const envApiClient = new EnvApiClient(httpClient);
|
|
6
7
|
const processRunner = new ProcessRunner();
|
|
7
8
|
const terraformAdapter = new TerraformAdapter(processRunner);
|
|
8
9
|
const envCtl = new EnvCtl(envApiClient, terraformAdapter);
|
|
9
|
-
export { envCtl };
|
|
10
|
+
export { configService, envCtl };
|
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 { deleteIt, deploy } from './commands/index.js';
|
|
5
|
+
import { configure, deleteIt, deploy } 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,6 +11,7 @@ program
|
|
|
11
11
|
.name('envctl')
|
|
12
12
|
.description('CLI to manage environments')
|
|
13
13
|
.version(pkg.version);
|
|
14
|
+
configure(program);
|
|
14
15
|
deploy(program);
|
|
15
16
|
deleteIt(program);
|
|
16
17
|
program.parse(process.argv);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export var EnvSize;
|
|
2
|
+
(function (EnvSize) {
|
|
3
|
+
EnvSize["Min"] = "min";
|
|
4
|
+
EnvSize["Small"] = "small";
|
|
5
|
+
EnvSize["Full"] = "full";
|
|
6
|
+
})(EnvSize || (EnvSize = {}));
|
|
7
|
+
export const EnvSizeAvgTime = {
|
|
8
|
+
[EnvSize.Min]: 2,
|
|
9
|
+
[EnvSize.Small]: 5,
|
|
10
|
+
[EnvSize.Full]: 10,
|
|
11
|
+
};
|
package/dist/model/index.js
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
const CONFIG_PATH = path.join(os.homedir(), '.envctl', 'config.json');
|
|
5
|
+
export class ConfigService {
|
|
6
|
+
config;
|
|
7
|
+
constructor() {
|
|
8
|
+
}
|
|
9
|
+
saveConfig(config) {
|
|
10
|
+
const mergedConfig = { ...this.loadConfig(), ...config };
|
|
11
|
+
const data = JSON.stringify(mergedConfig, null, 2);
|
|
12
|
+
fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
|
|
13
|
+
fs.writeFileSync(CONFIG_PATH, data);
|
|
14
|
+
}
|
|
15
|
+
loadConfig() {
|
|
16
|
+
if (this.config)
|
|
17
|
+
return this.config;
|
|
18
|
+
this.config = {};
|
|
19
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
20
|
+
const data = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
21
|
+
this.config = JSON.parse(data);
|
|
22
|
+
}
|
|
23
|
+
return this.config;
|
|
24
|
+
}
|
|
25
|
+
getOwner() {
|
|
26
|
+
return this.loadConfig()?.owner;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/service/EnvCtl.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { promptYesNo } from '../commands/utils.js';
|
|
2
2
|
import { KnownException } from '../exceptions.js';
|
|
3
|
+
import { EnvSizeAvgTime, EnvStatus } from '../model/index.js';
|
|
3
4
|
export class EnvCtl {
|
|
4
5
|
envApi;
|
|
5
6
|
terraformAdapter;
|
|
@@ -36,26 +37,28 @@ export class EnvCtl {
|
|
|
36
37
|
if (env.kind !== envDto.kind) {
|
|
37
38
|
throw new KnownException(`Can not change env kind ${env.kind} -> ${envDto.kind}`);
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
await
|
|
40
|
+
switch (env.status) {
|
|
41
|
+
case EnvStatus.Creating:
|
|
42
|
+
case EnvStatus.Updating: {
|
|
43
|
+
const answerYes = await promptYesNo(`Env status is ${env.status}, likely to be an error from a previous run\n
|
|
44
|
+
Do you want to proceed with deployment? (y/n)`);
|
|
45
|
+
if (answerYes) {
|
|
46
|
+
await this.runDeploy(key, envDto, tfArgs, cwd);
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
43
49
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (answerYes) {
|
|
50
|
+
case EnvStatus.Active: {
|
|
51
|
+
console.log('Env status is ACTIVE\nWill lock for update and run terraform apply (to update resources)');
|
|
52
|
+
await this.envApi.lockForUpdate(key);
|
|
48
53
|
await this.runDeploy(key, envDto, tfArgs, cwd);
|
|
54
|
+
break;
|
|
49
55
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
else if (env.status === 'DELETING') {
|
|
57
|
-
const time = env.size === 'min' ? 2 : 5;
|
|
58
|
-
throw new KnownException(`Env status is DELETING, please wait (~${time} min)`);
|
|
56
|
+
case EnvStatus.Deleting: {
|
|
57
|
+
const time = EnvSizeAvgTime[env.size];
|
|
58
|
+
throw new KnownException(`Env status is DELETING, please wait (~${time} min)`);
|
|
59
|
+
}
|
|
60
|
+
default:
|
|
61
|
+
throw new KnownException(`Unsupported environment status: ${env.status}`);
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
async runDeploy(key, envAttrs, tfArgs, cwd) {
|
|
@@ -75,7 +78,7 @@ export class EnvCtl {
|
|
|
75
78
|
if (env === null) {
|
|
76
79
|
throw new KnownException(`Environment ${key} does not exist`);
|
|
77
80
|
}
|
|
78
|
-
if (env.status ===
|
|
81
|
+
if (env.status === EnvStatus.Creating) {
|
|
79
82
|
const answerYes = await promptYesNo('Environment is still being created.\nDo you want to delete it? (y/n) ');
|
|
80
83
|
if (!answerYes) {
|
|
81
84
|
throw new KnownException('Aborting env deletion');
|
package/dist/service/index.js
CHANGED
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.10.0",
|
|
5
5
|
"author": "Alex Chekulaev",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -26,14 +26,16 @@
|
|
|
26
26
|
"build": "tsc",
|
|
27
27
|
"run": "node dist/index.js",
|
|
28
28
|
"run-version": "tsc --sourceMap true && npm run run -- --version",
|
|
29
|
-
"run-
|
|
30
|
-
"run-
|
|
29
|
+
"run-configure": "tsc --sourceMap true && npm run run -- configure",
|
|
30
|
+
"run-deploy": "tsc --sourceMap true && npm run run -- deploy --size min --cwd ../tt-core",
|
|
31
|
+
"run-delete": "tsc --sourceMap true && npm run run -- delete"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
34
|
"@aws-sdk/client-sts": "^3.716.0",
|
|
34
35
|
"@aws-sdk/credential-providers": "^3.716.0",
|
|
35
36
|
"aws4": "^1.13.2",
|
|
36
37
|
"commander": "^13.0.0",
|
|
38
|
+
"inquirer": "^12.6.0",
|
|
37
39
|
"update-notifier": "^7.3.1"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|