@beesolve/aws-accounts 1.0.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/cli.js ADDED
@@ -0,0 +1,201 @@
1
+ import { parseArgs } from "node:util";
2
+ import { createInterface } from "node:readline/promises";
3
+ import { S3Client } from "@aws-sdk/client-s3";
4
+ import { IAMClient } from "@aws-sdk/client-iam";
5
+ import { LambdaClient } from "@aws-sdk/client-lambda";
6
+ import { STSClient } from "@aws-sdk/client-sts";
7
+ import { SSOAdminClient } from "@aws-sdk/client-sso-admin";
8
+ import {
9
+ buildAwsClientConfig,
10
+ resolveAwsProfile,
11
+ resolveAwsRegion
12
+ } from "./awsClientConfig.js";
13
+ import { consoleLogger } from "./logger.js";
14
+ import { runGraveyardCommand } from "./commands/graveyard.js";
15
+ import { runRegenerateCommand } from "./commands/regenerate.js";
16
+ import {
17
+ runRemoteBootstrap,
18
+ runRemoteScan,
19
+ runRemoteInit,
20
+ runRemotePlan,
21
+ runRemoteApply,
22
+ runRemoteUpgrade
23
+ } from "./commands/remote.js";
24
+ import {
25
+ classifyCliError,
26
+ exitCodeForCliErrorKind,
27
+ toUsageError
28
+ } from "./error.js";
29
+ const commands = [
30
+ "bootstrap",
31
+ "scan",
32
+ "init",
33
+ "regenerate",
34
+ "graveyard",
35
+ "plan",
36
+ "apply",
37
+ "upgrade"
38
+ ];
39
+ function isCommandName(value) {
40
+ return commands.includes(value);
41
+ }
42
+ const contextPath = "aws.context.json";
43
+ async function main() {
44
+ const logger = consoleLogger;
45
+ const args = parseArgs({
46
+ options: {
47
+ profile: { type: "string" },
48
+ region: { type: "string" },
49
+ yes: { type: "boolean", default: false },
50
+ json: { type: "boolean", default: false },
51
+ "ignore-unsupported": { type: "boolean", default: false },
52
+ "allow-destructive": { type: "boolean", default: false },
53
+ refresh: { type: "boolean", default: false },
54
+ help: { type: "boolean", default: false }
55
+ },
56
+ allowPositionals: true
57
+ });
58
+ const commandArg = args.positionals[0];
59
+ if (args.values.help || commandArg == null) {
60
+ printHelp(logger);
61
+ return;
62
+ }
63
+ if (!isCommandName(commandArg)) {
64
+ printHelp(logger);
65
+ throw toUsageError(`Unknown command: "${commandArg}".`);
66
+ }
67
+ const command = commandArg;
68
+ const profile = resolveAwsProfile({ profileArg: args.values.profile });
69
+ const region = resolveAwsRegion({ regionArg: args.values.region });
70
+ const clientConfig = buildAwsClientConfig({
71
+ profile,
72
+ region
73
+ });
74
+ if (command === "regenerate") {
75
+ const overwriteConfirmation2 = buildOverwriteConfirmation({
76
+ yes: args.values.yes ?? false,
77
+ isTty: process.stdin.isTTY
78
+ });
79
+ const result = await runRegenerateCommand({
80
+ logger,
81
+ overwriteConfirmation: overwriteConfirmation2
82
+ });
83
+ logger.log("");
84
+ logger.log("Regenerate complete.");
85
+ for (const file of result.files) {
86
+ logger.log(`${file.path}: ${file.status}`);
87
+ }
88
+ return;
89
+ }
90
+ if (command === "graveyard") {
91
+ await runGraveyardCommand({
92
+ logger,
93
+ cachePath: ".remote-state-cache.json",
94
+ contextPath
95
+ });
96
+ return;
97
+ }
98
+ const overwriteConfirmation = buildOverwriteConfirmation({
99
+ yes: args.values.yes ?? false,
100
+ isTty: process.stdin.isTTY
101
+ });
102
+ const remoteInput = {
103
+ subcommand: command,
104
+ profile,
105
+ region,
106
+ flags: {
107
+ yes: args.values.yes ?? false,
108
+ refresh: args.values.refresh ?? false,
109
+ allowDestructive: args.values["allow-destructive"] ?? false,
110
+ ignoreUnsupported: args.values["ignore-unsupported"] ?? false
111
+ },
112
+ logger,
113
+ overwriteConfirmation,
114
+ stsClient: new STSClient(clientConfig),
115
+ s3Client: new S3Client(clientConfig),
116
+ iamClient: new IAMClient(clientConfig),
117
+ lambdaClient: new LambdaClient(clientConfig),
118
+ ssoAdminClient: new SSOAdminClient(clientConfig)
119
+ };
120
+ if (command === "bootstrap") {
121
+ return runRemoteBootstrap(remoteInput);
122
+ }
123
+ if (command === "scan") {
124
+ return runRemoteScan(remoteInput);
125
+ }
126
+ if (command === "init") {
127
+ return runRemoteInit(remoteInput);
128
+ }
129
+ if (command === "plan") {
130
+ return runRemotePlan(remoteInput);
131
+ }
132
+ if (command === "apply") {
133
+ return runRemoteApply(remoteInput);
134
+ }
135
+ if (command === "upgrade") {
136
+ return runRemoteUpgrade(remoteInput);
137
+ }
138
+ printHelp(logger);
139
+ process.exitCode = 1;
140
+ }
141
+ function printHelp(logger) {
142
+ logger.log("@beesolve/aws-accounts");
143
+ logger.log("");
144
+ logger.log("Usage:");
145
+ logger.log(
146
+ " npm run cli -- bootstrap [--profile <name>] [--region <region>] [--yes]"
147
+ );
148
+ logger.log(
149
+ " npm run cli -- scan [--profile <name>] [--region <region>]"
150
+ );
151
+ logger.log(
152
+ " npm run cli -- init [--profile <name>] [--region <region>] [--yes]"
153
+ );
154
+ logger.log(" npm run cli -- regenerate [--yes]");
155
+ logger.log(" npm run cli -- graveyard");
156
+ logger.log(
157
+ " npm run cli -- plan [--profile <name>] [--region <region>] [--refresh]"
158
+ );
159
+ logger.log(
160
+ " npm run cli -- apply [--profile <name>] [--region <region>] [--yes] [--allow-destructive] [--ignore-unsupported]"
161
+ );
162
+ logger.log(
163
+ " npm run cli -- upgrade [--profile <name>] [--region <region>]"
164
+ );
165
+ logger.log("");
166
+ logger.log("Environment fallback:");
167
+ logger.log(" AWS_PROFILE, AWS_REGION, AWS_DEFAULT_REGION");
168
+ }
169
+ function buildOverwriteConfirmation(props) {
170
+ return async (overwriteProps) => {
171
+ if (overwriteProps.fileSummaries.length === 0) {
172
+ return true;
173
+ }
174
+ if (props.yes) {
175
+ return true;
176
+ }
177
+ if (props.isTty !== true) {
178
+ throw new Error(
179
+ "Refusing to overwrite config files in non-interactive mode without --yes."
180
+ );
181
+ }
182
+ const readlineInterface = createInterface({
183
+ input: process.stdin,
184
+ output: process.stdout
185
+ });
186
+ try {
187
+ const answer = await readlineInterface.question(
188
+ "Proceed with writing config files? [y/N] "
189
+ );
190
+ const normalized = answer.trim().toLowerCase();
191
+ return normalized === "y" || normalized === "yes";
192
+ } finally {
193
+ readlineInterface.close();
194
+ }
195
+ };
196
+ }
197
+ main().catch((error) => {
198
+ const classified = classifyCliError(error);
199
+ consoleLogger.error(`CLI ${classified.kind} error: ${classified.message}`);
200
+ process.exitCode = exitCodeForCliErrorKind(classified.kind);
201
+ });
@@ -0,0 +1,46 @@
1
+ import { readAwsContextFromFile } from "../awsConfig.js";
2
+ import { readStateCache } from "../remoteStateCache.js";
3
+ async function runGraveyardCommand(props) {
4
+ const [cache, context] = await Promise.all([
5
+ readStateCache(props.cachePath),
6
+ readAwsContextFromFile(props.contextPath)
7
+ ]);
8
+ if (cache == null) {
9
+ throw new Error(
10
+ `No remote state cache found at "${props.cachePath}". Run a scan or apply command first to populate the cache.`
11
+ );
12
+ }
13
+ const state = cache.state;
14
+ const graveyardOuId = context.organization.graveyardOuId;
15
+ const graveyardAccounts = state.organization.accounts.filter((account) => account.parentId === graveyardOuId).sort((left, right) => left.name.localeCompare(right.name)).map((account) => ({
16
+ id: account.id,
17
+ name: account.name,
18
+ email: account.email,
19
+ status: account.status
20
+ }));
21
+ props.logger.log(`Graveyard OU: ${graveyardOuId}`);
22
+ props.logger.log(`Accounts in Graveyard: ${graveyardAccounts.length}`);
23
+ if (graveyardAccounts.length === 0) {
24
+ props.logger.log("No accounts currently parked in Graveyard.");
25
+ return {
26
+ graveyardOuId,
27
+ accounts: graveyardAccounts
28
+ };
29
+ }
30
+ props.logger.log("");
31
+ for (const account of graveyardAccounts) {
32
+ props.logger.log(
33
+ `- ${account.name} (${account.id}) [${account.status}] <${account.email}>`
34
+ );
35
+ props.logger.log(
36
+ ` aws organizations close-account --account-id ${account.id}`
37
+ );
38
+ }
39
+ return {
40
+ graveyardOuId,
41
+ accounts: graveyardAccounts
42
+ };
43
+ }
44
+ export {
45
+ runGraveyardCommand
46
+ };
@@ -0,0 +1,17 @@
1
+ import { regenerateAwsConfigTypes } from "../awsConfig.js";
2
+ async function runRegenerateCommand(props) {
3
+ const result = await regenerateAwsConfigTypes({
4
+ configPath: props.configPath ?? "aws.config.ts",
5
+ typesPath: props.typesPath ?? "aws.config.types.ts",
6
+ logger: props.logger,
7
+ overwriteConfirmation: props.overwriteConfirmation
8
+ });
9
+ return {
10
+ typesPath: result.typesPath,
11
+ changed: result.changed,
12
+ files: result.files
13
+ };
14
+ }
15
+ export {
16
+ runRegenerateCommand
17
+ };