hereya-cli 0.10.0 → 0.12.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/README.md +19 -44
- package/dist/iac/common.d.ts +9 -1
- package/dist/iac/common.js +3 -0
- package/dist/iac/index.js +9 -0
- package/dist/iac/terraform.d.ts +2 -1
- package/dist/iac/terraform.js +90 -34
- package/dist/infrastructure/aws.d.ts +2 -5
- package/dist/infrastructure/aws.js +78 -253
- package/dist/infrastructure/local.d.ts +0 -2
- package/dist/infrastructure/local.js +3 -25
- package/dist/lib/filesystem.d.ts +1 -0
- package/dist/lib/filesystem.js +10 -1
- package/dist/lib/package/index.d.ts +1 -0
- package/dist/lib/package/index.js +26 -8
- package/oclif.manifest.json +1 -51
- package/package.json +1 -1
- package/dist/commands/remote/exec/index.d.ts +0 -13
- package/dist/commands/remote/exec/index.js +0 -90
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ $ npm install -g hereya-cli
|
|
|
20
20
|
$ hereya COMMAND
|
|
21
21
|
running command...
|
|
22
22
|
$ hereya (--version)
|
|
23
|
-
hereya-cli/0.
|
|
23
|
+
hereya-cli/0.12.0 linux-x64 node-v22.12.0
|
|
24
24
|
$ hereya --help [COMMAND]
|
|
25
25
|
USAGE
|
|
26
26
|
$ hereya COMMAND
|
|
@@ -36,7 +36,6 @@ USAGE
|
|
|
36
36
|
* [`hereya env [NAME]`](#hereya-env-name)
|
|
37
37
|
* [`hereya help [COMMAND]`](#hereya-help-command)
|
|
38
38
|
* [`hereya init PROJECT`](#hereya-init-project)
|
|
39
|
-
* [`hereya remote exec [PKGPATH]`](#hereya-remote-exec-pkgpath)
|
|
40
39
|
* [`hereya remove PACKAGE`](#hereya-remove-package)
|
|
41
40
|
* [`hereya run CMD`](#hereya-run-cmd)
|
|
42
41
|
* [`hereya unbootstrap INFRASTRUCTURETYPE`](#hereya-unbootstrap-infrastructuretype)
|
|
@@ -73,7 +72,7 @@ EXAMPLES
|
|
|
73
72
|
$ hereya add cloudy/docker_postgres
|
|
74
73
|
```
|
|
75
74
|
|
|
76
|
-
_See code: [src/commands/add/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
75
|
+
_See code: [src/commands/add/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/add/index.ts)_
|
|
77
76
|
|
|
78
77
|
## `hereya bootstrap INFRASTRUCTURETYPE`
|
|
79
78
|
|
|
@@ -98,7 +97,7 @@ EXAMPLES
|
|
|
98
97
|
$ hereya bootstrap local
|
|
99
98
|
```
|
|
100
99
|
|
|
101
|
-
_See code: [src/commands/bootstrap/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
100
|
+
_See code: [src/commands/bootstrap/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/bootstrap/index.ts)_
|
|
102
101
|
|
|
103
102
|
## `hereya deploy`
|
|
104
103
|
|
|
@@ -119,7 +118,7 @@ EXAMPLES
|
|
|
119
118
|
$ hereya deploy
|
|
120
119
|
```
|
|
121
120
|
|
|
122
|
-
_See code: [src/commands/deploy/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
121
|
+
_See code: [src/commands/deploy/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/deploy/index.ts)_
|
|
123
122
|
|
|
124
123
|
## `hereya down`
|
|
125
124
|
|
|
@@ -141,7 +140,7 @@ EXAMPLES
|
|
|
141
140
|
$ hereya down
|
|
142
141
|
```
|
|
143
142
|
|
|
144
|
-
_See code: [src/commands/down/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
143
|
+
_See code: [src/commands/down/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/down/index.ts)_
|
|
145
144
|
|
|
146
145
|
## `hereya env [NAME]`
|
|
147
146
|
|
|
@@ -172,7 +171,7 @@ EXAMPLES
|
|
|
172
171
|
$ hereya env -w dev -l
|
|
173
172
|
```
|
|
174
173
|
|
|
175
|
-
_See code: [src/commands/env/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
174
|
+
_See code: [src/commands/env/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/env/index.ts)_
|
|
176
175
|
|
|
177
176
|
## `hereya help [COMMAND]`
|
|
178
177
|
|
|
@@ -218,31 +217,7 @@ EXAMPLES
|
|
|
218
217
|
$ hereya init myProject -w=defaultWorkspace --chdir=./myProject
|
|
219
218
|
```
|
|
220
219
|
|
|
221
|
-
_See code: [src/commands/init/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
222
|
-
|
|
223
|
-
## `hereya remote exec [PKGPATH]`
|
|
224
|
-
|
|
225
|
-
remotely provision or destroy a package
|
|
226
|
-
|
|
227
|
-
```
|
|
228
|
-
USAGE
|
|
229
|
-
$ hereya remote exec [PKGPATH] [-o <value>] [-s <value>]
|
|
230
|
-
|
|
231
|
-
ARGUMENTS
|
|
232
|
-
PKGPATH The path to the package to provision or destroy
|
|
233
|
-
|
|
234
|
-
FLAGS
|
|
235
|
-
-o, --output=<value> The path to store the output env in
|
|
236
|
-
-s, --source=<value> The source of the project to provision or destroy the package for
|
|
237
|
-
|
|
238
|
-
DESCRIPTION
|
|
239
|
-
remotely provision or destroy a package
|
|
240
|
-
|
|
241
|
-
EXAMPLES
|
|
242
|
-
$ hereya remote exec
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
_See code: [src/commands/remote/exec/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/remote/exec/index.ts)_
|
|
220
|
+
_See code: [src/commands/init/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/init/index.ts)_
|
|
246
221
|
|
|
247
222
|
## `hereya remove PACKAGE`
|
|
248
223
|
|
|
@@ -265,7 +240,7 @@ EXAMPLES
|
|
|
265
240
|
$ hereya remove cloudy/docker_postgres
|
|
266
241
|
```
|
|
267
242
|
|
|
268
|
-
_See code: [src/commands/remove/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
243
|
+
_See code: [src/commands/remove/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/remove/index.ts)_
|
|
269
244
|
|
|
270
245
|
## `hereya run CMD`
|
|
271
246
|
|
|
@@ -291,7 +266,7 @@ EXAMPLES
|
|
|
291
266
|
$ hereya run -w uat -- node index.js
|
|
292
267
|
```
|
|
293
268
|
|
|
294
|
-
_See code: [src/commands/run/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
269
|
+
_See code: [src/commands/run/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/run/index.ts)_
|
|
295
270
|
|
|
296
271
|
## `hereya unbootstrap INFRASTRUCTURETYPE`
|
|
297
272
|
|
|
@@ -316,7 +291,7 @@ EXAMPLES
|
|
|
316
291
|
$ hereya unbootstrap local
|
|
317
292
|
```
|
|
318
293
|
|
|
319
|
-
_See code: [src/commands/unbootstrap/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
294
|
+
_See code: [src/commands/unbootstrap/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/unbootstrap/index.ts)_
|
|
320
295
|
|
|
321
296
|
## `hereya undeploy`
|
|
322
297
|
|
|
@@ -337,7 +312,7 @@ EXAMPLES
|
|
|
337
312
|
$ hereya undeploy
|
|
338
313
|
```
|
|
339
314
|
|
|
340
|
-
_See code: [src/commands/undeploy/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
315
|
+
_See code: [src/commands/undeploy/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/undeploy/index.ts)_
|
|
341
316
|
|
|
342
317
|
## `hereya up`
|
|
343
318
|
|
|
@@ -359,7 +334,7 @@ EXAMPLES
|
|
|
359
334
|
$ hereya up
|
|
360
335
|
```
|
|
361
336
|
|
|
362
|
-
_See code: [src/commands/up/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
337
|
+
_See code: [src/commands/up/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/up/index.ts)_
|
|
363
338
|
|
|
364
339
|
## `hereya workspace create NAME`
|
|
365
340
|
|
|
@@ -379,7 +354,7 @@ EXAMPLES
|
|
|
379
354
|
$ hereya workspace create dev
|
|
380
355
|
```
|
|
381
356
|
|
|
382
|
-
_See code: [src/commands/workspace/create/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
357
|
+
_See code: [src/commands/workspace/create/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/workspace/create/index.ts)_
|
|
383
358
|
|
|
384
359
|
## `hereya workspace delete NAME`
|
|
385
360
|
|
|
@@ -399,7 +374,7 @@ EXAMPLES
|
|
|
399
374
|
$ hereya workspace delete dev
|
|
400
375
|
```
|
|
401
376
|
|
|
402
|
-
_See code: [src/commands/workspace/delete/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
377
|
+
_See code: [src/commands/workspace/delete/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/workspace/delete/index.ts)_
|
|
403
378
|
|
|
404
379
|
## `hereya workspace env [NAME]`
|
|
405
380
|
|
|
@@ -425,7 +400,7 @@ EXAMPLES
|
|
|
425
400
|
$ hereya workspace env myEnv -w dev
|
|
426
401
|
```
|
|
427
402
|
|
|
428
|
-
_See code: [src/commands/workspace/env/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
403
|
+
_See code: [src/commands/workspace/env/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/workspace/env/index.ts)_
|
|
429
404
|
|
|
430
405
|
## `hereya workspace env set`
|
|
431
406
|
|
|
@@ -449,7 +424,7 @@ EXAMPLES
|
|
|
449
424
|
$ hereya workspace env set -w my-workspace -n myVar -v my-value -i aws -s
|
|
450
425
|
```
|
|
451
426
|
|
|
452
|
-
_See code: [src/commands/workspace/env/set/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
427
|
+
_See code: [src/commands/workspace/env/set/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/workspace/env/set/index.ts)_
|
|
453
428
|
|
|
454
429
|
## `hereya workspace env unset`
|
|
455
430
|
|
|
@@ -470,7 +445,7 @@ EXAMPLES
|
|
|
470
445
|
$ hereya workspace env unset -w my-workspace -n myVar
|
|
471
446
|
```
|
|
472
447
|
|
|
473
|
-
_See code: [src/commands/workspace/env/unset/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
448
|
+
_See code: [src/commands/workspace/env/unset/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/workspace/env/unset/index.ts)_
|
|
474
449
|
|
|
475
450
|
## `hereya workspace install PACKAGE`
|
|
476
451
|
|
|
@@ -496,7 +471,7 @@ EXAMPLES
|
|
|
496
471
|
$ hereya workspace install hereya/aws-cognito
|
|
497
472
|
```
|
|
498
473
|
|
|
499
|
-
_See code: [src/commands/workspace/install/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
474
|
+
_See code: [src/commands/workspace/install/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/workspace/install/index.ts)_
|
|
500
475
|
|
|
501
476
|
## `hereya workspace uninstall PACKAGE`
|
|
502
477
|
|
|
@@ -522,5 +497,5 @@ EXAMPLES
|
|
|
522
497
|
$ hereya workspace uninstall hereya/aws-cognito
|
|
523
498
|
```
|
|
524
499
|
|
|
525
|
-
_See code: [src/commands/workspace/uninstall/index.ts](https://github.com/hereya/hereya-cli/blob/v0.
|
|
500
|
+
_See code: [src/commands/workspace/uninstall/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/workspace/uninstall/index.ts)_
|
|
526
501
|
<!-- commandsstop -->
|
package/dist/iac/common.d.ts
CHANGED
|
@@ -4,13 +4,21 @@ export interface Iac {
|
|
|
4
4
|
}
|
|
5
5
|
export declare enum IacType {
|
|
6
6
|
cdk = "cdk",
|
|
7
|
-
|
|
7
|
+
opentf = "opentf",
|
|
8
|
+
opentofu = "opentofu",
|
|
9
|
+
terraform = "terraform",
|
|
10
|
+
tofu = "tofu"
|
|
8
11
|
}
|
|
9
12
|
export type ApplyInput = {
|
|
10
13
|
env: {
|
|
11
14
|
[key: string]: string;
|
|
12
15
|
};
|
|
13
16
|
id: string;
|
|
17
|
+
infraConfig?: {
|
|
18
|
+
terraformStateBucketName: string;
|
|
19
|
+
terraformStateBucketRegion?: string;
|
|
20
|
+
terraformStateLockTableName: string;
|
|
21
|
+
};
|
|
14
22
|
parameters?: {
|
|
15
23
|
[key: string]: string;
|
|
16
24
|
};
|
package/dist/iac/common.js
CHANGED
package/dist/iac/index.js
CHANGED
|
@@ -8,6 +8,15 @@ export function getIac({ type }) {
|
|
|
8
8
|
case IacType.terraform: {
|
|
9
9
|
return { iac: terraform, supported: true };
|
|
10
10
|
}
|
|
11
|
+
case IacType.opentofu: {
|
|
12
|
+
return { iac: terraform, supported: true };
|
|
13
|
+
}
|
|
14
|
+
case IacType.opentf: {
|
|
15
|
+
return { iac: terraform, supported: true };
|
|
16
|
+
}
|
|
17
|
+
case IacType.tofu: {
|
|
18
|
+
return { iac: terraform, supported: true };
|
|
19
|
+
}
|
|
11
20
|
case IacType.cdk: {
|
|
12
21
|
return { iac: cdk, supported: true };
|
|
13
22
|
}
|
package/dist/iac/terraform.d.ts
CHANGED
|
@@ -2,8 +2,9 @@ import { ApplyInput, ApplyOutput, DestroyInput, DestroyOutput, Iac } from './com
|
|
|
2
2
|
export declare class Terraform implements Iac {
|
|
3
3
|
apply(input: ApplyInput): Promise<ApplyOutput>;
|
|
4
4
|
destroy(input: DestroyInput): Promise<DestroyOutput>;
|
|
5
|
-
|
|
5
|
+
downloadTerraform(): Promise<boolean>;
|
|
6
6
|
private getEnv;
|
|
7
7
|
private getTerraformBinary;
|
|
8
|
+
private getWithRedirect;
|
|
8
9
|
private terraformBinPath;
|
|
9
10
|
}
|
package/dist/iac/terraform.js
CHANGED
|
@@ -8,34 +8,63 @@ import { mapObject } from '../lib/object-utils.js';
|
|
|
8
8
|
import { runShell } from '../lib/shell.js';
|
|
9
9
|
export class Terraform {
|
|
10
10
|
async apply(input) {
|
|
11
|
+
if (input.infraConfig &&
|
|
12
|
+
input.infraConfig.terraformStateBucketName &&
|
|
13
|
+
input.infraConfig.terraformStateLockTableName &&
|
|
14
|
+
input.infraConfig.terraformStateBucketRegion) {
|
|
15
|
+
const backendConfig = `
|
|
16
|
+
terraform {
|
|
17
|
+
backend "s3" {
|
|
18
|
+
bucket = "${input.infraConfig.terraformStateBucketName}"
|
|
19
|
+
key = "${input.id}/terraform.tfstate"
|
|
20
|
+
region = "${input.infraConfig.terraformStateBucketRegion}"
|
|
21
|
+
dynamodb_table = "${input.infraConfig.terraformStateLockTableName}"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
const backendFile = path.join(input.pkgPath, 'hereya_terraform_backend.tf');
|
|
26
|
+
await fs.promises.writeFile(backendFile, backendConfig);
|
|
27
|
+
}
|
|
11
28
|
try {
|
|
12
29
|
const terraform = await this.getTerraformBinary();
|
|
13
30
|
runShell(terraform, ['init'], {
|
|
14
31
|
directory: input.pkgPath,
|
|
15
32
|
env: {
|
|
16
|
-
...mapObject(input.env ?? {}, (key, value) => [
|
|
33
|
+
...mapObject(input.env ?? {}, (key, value) => [
|
|
34
|
+
`TF_VAR_${key}`,
|
|
35
|
+
typeof value === 'object' ? JSON.stringify(value) : value,
|
|
36
|
+
]),
|
|
17
37
|
...input.env,
|
|
18
|
-
...mapObject(input.parameters ?? {}, (key, value) => [
|
|
19
|
-
|
|
38
|
+
...mapObject(input.parameters ?? {}, (key, value) => [
|
|
39
|
+
`TF_VAR_${key}`,
|
|
40
|
+
typeof value === 'object' ? JSON.stringify(value) : value,
|
|
41
|
+
]),
|
|
42
|
+
},
|
|
20
43
|
});
|
|
21
44
|
runShell(terraform, ['apply', '-auto-approve'], {
|
|
22
45
|
directory: input.pkgPath,
|
|
23
46
|
env: {
|
|
24
|
-
...mapObject(input.env ?? {}, (key, value) => [
|
|
47
|
+
...mapObject(input.env ?? {}, (key, value) => [
|
|
48
|
+
`TF_VAR_${key}`,
|
|
49
|
+
typeof value === 'object' ? JSON.stringify(value) : value,
|
|
50
|
+
]),
|
|
25
51
|
...input.env,
|
|
26
|
-
...mapObject(input.parameters ?? {}, (key, value) => [
|
|
27
|
-
|
|
52
|
+
...mapObject(input.parameters ?? {}, (key, value) => [
|
|
53
|
+
`TF_VAR_${key}`,
|
|
54
|
+
typeof value === 'object' ? JSON.stringify(value) : value,
|
|
55
|
+
]),
|
|
56
|
+
},
|
|
28
57
|
});
|
|
29
58
|
const env = await this.getEnv(input.pkgPath);
|
|
30
59
|
return {
|
|
31
60
|
env,
|
|
32
|
-
success: true
|
|
61
|
+
success: true,
|
|
33
62
|
};
|
|
34
63
|
}
|
|
35
64
|
catch (error) {
|
|
36
65
|
return {
|
|
37
66
|
reason: error.message,
|
|
38
|
-
success: false
|
|
67
|
+
success: false,
|
|
39
68
|
};
|
|
40
69
|
}
|
|
41
70
|
}
|
|
@@ -45,48 +74,60 @@ export class Terraform {
|
|
|
45
74
|
runShell(terraform, ['init'], {
|
|
46
75
|
directory: input.pkgPath,
|
|
47
76
|
env: {
|
|
48
|
-
...mapObject(input.env ?? {}, (key, value) => [
|
|
77
|
+
...mapObject(input.env ?? {}, (key, value) => [
|
|
78
|
+
`TF_VAR_${key}`,
|
|
79
|
+
typeof value === 'object' ? JSON.stringify(value) : value,
|
|
80
|
+
]),
|
|
49
81
|
...input.env,
|
|
50
|
-
...mapObject(input.parameters ?? {}, (key, value) => [
|
|
51
|
-
|
|
82
|
+
...mapObject(input.parameters ?? {}, (key, value) => [
|
|
83
|
+
`TF_VAR_${key}`,
|
|
84
|
+
typeof value === 'object' ? JSON.stringify(value) : value,
|
|
85
|
+
]),
|
|
86
|
+
},
|
|
52
87
|
});
|
|
53
88
|
const env = await this.getEnv(input.pkgPath);
|
|
54
89
|
runShell(terraform, ['destroy', '-auto-approve'], {
|
|
55
90
|
directory: input.pkgPath,
|
|
56
91
|
env: {
|
|
57
|
-
...mapObject(input.env ?? {}, (key, value) => [
|
|
92
|
+
...mapObject(input.env ?? {}, (key, value) => [
|
|
93
|
+
`TF_VAR_${key}`,
|
|
94
|
+
typeof value === 'object' ? JSON.stringify(value) : value,
|
|
95
|
+
]),
|
|
58
96
|
...input.env,
|
|
59
|
-
...mapObject(input.parameters ?? {}, (key, value) => [
|
|
60
|
-
|
|
97
|
+
...mapObject(input.parameters ?? {}, (key, value) => [
|
|
98
|
+
`TF_VAR_${key}`,
|
|
99
|
+
typeof value === 'object' ? JSON.stringify(value) : value,
|
|
100
|
+
]),
|
|
101
|
+
},
|
|
61
102
|
});
|
|
62
103
|
return {
|
|
63
104
|
env,
|
|
64
|
-
success: true
|
|
105
|
+
success: true,
|
|
65
106
|
};
|
|
66
107
|
}
|
|
67
108
|
catch (error) {
|
|
68
109
|
return {
|
|
69
110
|
reason: error.message,
|
|
70
|
-
success: false
|
|
111
|
+
success: false,
|
|
71
112
|
};
|
|
72
113
|
}
|
|
73
114
|
}
|
|
74
115
|
async downloadTerraform() {
|
|
75
116
|
const TERRAFORM_DOWNLOAD_URLS = new Map([
|
|
76
|
-
['darwin_arm64', 'https://
|
|
77
|
-
['darwin_x64', 'https://
|
|
78
|
-
['freebsd_arm', 'https://
|
|
79
|
-
['freebsd_ia32', 'https://
|
|
80
|
-
['freebsd_x64', 'https://
|
|
81
|
-
['linux_arm', 'https://
|
|
82
|
-
['linux_arm64', 'https://
|
|
83
|
-
['linux_ia32', 'https://
|
|
84
|
-
['linux_x64', 'https://
|
|
85
|
-
['openbsd_ia32', 'https://
|
|
86
|
-
['openbsd_x64', 'https://
|
|
87
|
-
['sunos_x64', 'https://
|
|
88
|
-
['win32_ia32', 'https://
|
|
89
|
-
['win32_x64', 'https://
|
|
117
|
+
['darwin_arm64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_darwin_arm64.zip'],
|
|
118
|
+
['darwin_x64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_darwin_amd64.zip'],
|
|
119
|
+
['freebsd_arm', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_freebsd_arm.zip'],
|
|
120
|
+
['freebsd_ia32', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_freebsd_386.zip'],
|
|
121
|
+
['freebsd_x64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_freebsd_amd64.zip'],
|
|
122
|
+
['linux_arm', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_linux_arm.zip'],
|
|
123
|
+
['linux_arm64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_linux_arm64.zip'],
|
|
124
|
+
['linux_ia32', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_linux_386.zip'],
|
|
125
|
+
['linux_x64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_linux_amd64.zip'],
|
|
126
|
+
['openbsd_ia32', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_openbsd_386.zip'],
|
|
127
|
+
['openbsd_x64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_openbsd_amd64.zip'],
|
|
128
|
+
['sunos_x64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_solaris_amd64.zip'],
|
|
129
|
+
['win32_ia32', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_windows_386.zip'],
|
|
130
|
+
['win32_x64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_windows_amd64.zip'],
|
|
90
131
|
]);
|
|
91
132
|
const tfPath = this.terraformBinPath();
|
|
92
133
|
try {
|
|
@@ -114,7 +155,7 @@ export class Terraform {
|
|
|
114
155
|
}
|
|
115
156
|
}
|
|
116
157
|
await new Promise((resolve, reject) => {
|
|
117
|
-
|
|
158
|
+
this.getWithRedirect(url, async (response) => {
|
|
118
159
|
try {
|
|
119
160
|
await pipeline(response, decompress({ path: path.dirname(tfPath) }));
|
|
120
161
|
await fs.promises.chmod(tfPath, '0755');
|
|
@@ -123,7 +164,7 @@ export class Terraform {
|
|
|
123
164
|
throw new Error(`could not download terraform: ${error}`);
|
|
124
165
|
}
|
|
125
166
|
resolve(null);
|
|
126
|
-
}
|
|
167
|
+
}, reject);
|
|
127
168
|
});
|
|
128
169
|
return true;
|
|
129
170
|
}
|
|
@@ -131,7 +172,7 @@ export class Terraform {
|
|
|
131
172
|
const terraform = await this.getTerraformBinary();
|
|
132
173
|
const resourceOut = runShell(terraform, ['output', '--json'], {
|
|
133
174
|
directory: pkgPath,
|
|
134
|
-
stdio: 'pipe'
|
|
175
|
+
stdio: 'pipe',
|
|
135
176
|
});
|
|
136
177
|
let outStr = resourceOut.output.toString().trim();
|
|
137
178
|
const start = outStr.indexOf('{');
|
|
@@ -149,7 +190,22 @@ export class Terraform {
|
|
|
149
190
|
await this.downloadTerraform();
|
|
150
191
|
return this.terraformBinPath();
|
|
151
192
|
}
|
|
193
|
+
async getWithRedirect(url, handler, reject) {
|
|
194
|
+
https
|
|
195
|
+
.get(url, (response) => {
|
|
196
|
+
if (response.statusCode === 302) {
|
|
197
|
+
const { location } = response.headers;
|
|
198
|
+
if (location) {
|
|
199
|
+
this.getWithRedirect(location, handler, reject);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
handler(response);
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
.on('error', (error) => reject(error));
|
|
207
|
+
}
|
|
152
208
|
terraformBinPath() {
|
|
153
|
-
return path.join(os.homedir(), '.hereya', 'iac', 'terraform', os.platform() === 'win32' ? '
|
|
209
|
+
return path.join(os.homedir(), '.hereya', 'iac', 'terraform', 'tofu_1', os.platform() === 'win32' ? 'tofu.exe' : 'tofu');
|
|
154
210
|
}
|
|
155
211
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BootstrapInput, DeployInput, DeployOutput, DestroyInput, DestroyOutput, Infrastructure, ProvisionInput, ProvisionOutput, ResolveEnvInput, ResolveEnvOutput, SaveEnvInput, SaveEnvOutput, StoreEnvInput, StoreEnvOutput, UndeployInput, UndeployOutput, UnstoreEnvInput, UnstoreEnvOutput } from './common.js';
|
|
2
2
|
export declare class AwsInfrastructure implements Infrastructure {
|
|
3
|
+
private configKey;
|
|
3
4
|
bootstrap(_: BootstrapInput): Promise<void>;
|
|
4
5
|
deploy(input: DeployInput): Promise<DeployOutput>;
|
|
5
6
|
destroy(input: DestroyInput): Promise<DestroyOutput>;
|
|
@@ -10,9 +11,5 @@ export declare class AwsInfrastructure implements Infrastructure {
|
|
|
10
11
|
unbootstrap(_: BootstrapInput): Promise<void>;
|
|
11
12
|
undeploy(input: UndeployInput): Promise<UndeployOutput>;
|
|
12
13
|
unstoreEnv(input: UnstoreEnvInput): Promise<UnstoreEnvOutput>;
|
|
13
|
-
private
|
|
14
|
-
private getFilesToUpload;
|
|
15
|
-
private removeEnv;
|
|
16
|
-
private runCodeBuild;
|
|
17
|
-
private uploadProjectFiles;
|
|
14
|
+
private getConfig;
|
|
18
15
|
}
|
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
import { BatchGetBuildsCommand, CodeBuildClient, StartBuildCommand } from '@aws-sdk/client-codebuild';
|
|
2
|
-
import { DeleteObjectsCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
3
1
|
import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
|
|
4
2
|
import { DeleteParameterCommand, GetParameterCommand, PutParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
|
|
5
3
|
import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';
|
|
6
|
-
import { glob } from 'glob';
|
|
7
|
-
import ignore from 'ignore';
|
|
8
4
|
import { randomUUID } from 'node:crypto';
|
|
9
5
|
import fs from 'node:fs/promises';
|
|
6
|
+
import os from 'node:os';
|
|
10
7
|
import path from 'node:path';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { objectToBase64 } from '../lib/object-utils.js';
|
|
8
|
+
import { getIac } from '../iac/index.js';
|
|
9
|
+
import { downloadPackage } from '../lib/package/index.js';
|
|
14
10
|
import { runShell } from '../lib/shell.js';
|
|
15
|
-
import { InfrastructureType, } from './common.js';
|
|
16
11
|
import { destroyPackage, provisionPackage } from './index.js';
|
|
17
12
|
export class AwsInfrastructure {
|
|
13
|
+
configKey = '/hereya-bootstrap/config';
|
|
18
14
|
async bootstrap(_) {
|
|
19
15
|
const stsClient = new STSClient({});
|
|
20
16
|
const { Account: accountId } = await stsClient.send(new GetCallerIdentityCommand({}));
|
|
@@ -26,7 +22,7 @@ export class AwsInfrastructure {
|
|
|
26
22
|
throw new Error(output.reason);
|
|
27
23
|
}
|
|
28
24
|
const { env } = output;
|
|
29
|
-
const key =
|
|
25
|
+
const key = this.configKey;
|
|
30
26
|
const ssmClient = new SSMClient({});
|
|
31
27
|
const value = JSON.stringify(env);
|
|
32
28
|
await ssmClient.send(new PutParameterCommand({
|
|
@@ -37,55 +33,77 @@ export class AwsInfrastructure {
|
|
|
37
33
|
}));
|
|
38
34
|
}
|
|
39
35
|
async deploy(input) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
({ files, s3Bucket, s3Client, s3Key } = await this.uploadProjectFiles(input));
|
|
47
|
-
input.parameters = {
|
|
48
|
-
...input.parameters,
|
|
49
|
-
hereyaProjectEnv: objectToBase64(input.projectEnv),
|
|
50
|
-
};
|
|
51
|
-
const output = await this.runCodeBuild({
|
|
52
|
-
...input,
|
|
53
|
-
deploy: true,
|
|
54
|
-
sourceS3Key: s3Key,
|
|
55
|
-
});
|
|
56
|
-
if (!output.success) {
|
|
57
|
-
return output;
|
|
58
|
-
}
|
|
59
|
-
const env = await this.getEnv(input.id);
|
|
60
|
-
return { env, success: true };
|
|
61
|
-
}
|
|
62
|
-
finally {
|
|
63
|
-
if (s3Key && files.length > 0) {
|
|
64
|
-
await s3Client.send(new DeleteObjectsCommand({
|
|
65
|
-
Bucket: s3Bucket,
|
|
66
|
-
Delete: {
|
|
67
|
-
Objects: files.map((file) => ({ Key: `${s3Key}/${file}` })),
|
|
68
|
-
},
|
|
69
|
-
}));
|
|
70
|
-
}
|
|
71
|
-
}
|
|
36
|
+
input.parameters = {
|
|
37
|
+
...input.parameters,
|
|
38
|
+
hereyaProjectEnv: JSON.stringify(input.projectEnv ?? {}),
|
|
39
|
+
hereyaProjectRootDir: input.projectRootDir,
|
|
40
|
+
};
|
|
41
|
+
return this.provision(input);
|
|
72
42
|
}
|
|
73
43
|
async destroy(input) {
|
|
74
|
-
const
|
|
75
|
-
const
|
|
44
|
+
const destPath = path.join(os.homedir(), '.hereya', input.id, input.canonicalName);
|
|
45
|
+
const downloadPath = await downloadPackage(input.pkgUrl, destPath);
|
|
46
|
+
const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
|
|
47
|
+
const infraConfig = {
|
|
48
|
+
...await this.getConfig(),
|
|
49
|
+
region,
|
|
50
|
+
};
|
|
51
|
+
if (!infraConfig.terraformStateBucketName || !infraConfig.terraformStateLockTableName) {
|
|
52
|
+
return {
|
|
53
|
+
reason: 'could not find AWS infrastructure config. Did you run `hereya bootstrap aws`?',
|
|
54
|
+
success: false,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const iac$ = getIac({ type: input.iacType });
|
|
58
|
+
if (!iac$.supported) {
|
|
59
|
+
return { reason: iac$.reason, success: false };
|
|
60
|
+
}
|
|
61
|
+
const { iac } = iac$;
|
|
62
|
+
const output = await iac.destroy({
|
|
63
|
+
env: input.env ?? {},
|
|
64
|
+
id: input.id,
|
|
65
|
+
infraConfig,
|
|
66
|
+
parameters: input.parameters,
|
|
67
|
+
pkgPath: downloadPath,
|
|
68
|
+
});
|
|
76
69
|
if (!output.success) {
|
|
77
|
-
return output;
|
|
70
|
+
return { reason: output.reason, success: false };
|
|
78
71
|
}
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
// Remove downloaded package
|
|
73
|
+
await fs.rm(downloadPath, { recursive: true });
|
|
74
|
+
return { env: output.env, success: true };
|
|
81
75
|
}
|
|
82
76
|
async provision(input) {
|
|
83
|
-
const
|
|
77
|
+
const destPath = path.join(os.homedir(), '.hereya', input.id, input.canonicalName);
|
|
78
|
+
const downloadPath = await downloadPackage(input.pkgUrl, destPath);
|
|
79
|
+
const config = await this.getConfig();
|
|
80
|
+
const terraformStateBucketRegion = config.terraformStateBucketRegion || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
|
|
81
|
+
const infraConfig = {
|
|
82
|
+
...config,
|
|
83
|
+
terraformStateBucketRegion,
|
|
84
|
+
};
|
|
85
|
+
if (!infraConfig.terraformStateBucketName || !infraConfig.terraformStateLockTableName) {
|
|
86
|
+
return {
|
|
87
|
+
reason: 'could not find AWS infrastructure config. Did you run `hereya bootstrap aws`?',
|
|
88
|
+
success: false,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const iac$ = getIac({ type: input.iacType });
|
|
92
|
+
if (!iac$.supported) {
|
|
93
|
+
return { reason: iac$.reason, success: false };
|
|
94
|
+
}
|
|
95
|
+
const { iac } = iac$;
|
|
96
|
+
const output = await iac.apply({
|
|
97
|
+
env: input.env ?? {},
|
|
98
|
+
id: input.id,
|
|
99
|
+
infraConfig,
|
|
100
|
+
parameters: input.parameters,
|
|
101
|
+
pkgPath: downloadPath,
|
|
102
|
+
});
|
|
84
103
|
if (!output.success) {
|
|
85
|
-
return output;
|
|
104
|
+
return { reason: output.reason, success: false };
|
|
86
105
|
}
|
|
87
|
-
|
|
88
|
-
return { env, success: true };
|
|
106
|
+
return { env: output.env, success: true };
|
|
89
107
|
}
|
|
90
108
|
async resolveEnv(input) {
|
|
91
109
|
try {
|
|
@@ -175,45 +193,12 @@ export class AwsInfrastructure {
|
|
|
175
193
|
}
|
|
176
194
|
}
|
|
177
195
|
async undeploy(input) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
env = await this.getEnv(input.id);
|
|
185
|
-
}
|
|
186
|
-
catch (error) {
|
|
187
|
-
console.log(`Could not get env for ${input.id}: ${error.message}. Continuing with undeployment...`);
|
|
188
|
-
}
|
|
189
|
-
try {
|
|
190
|
-
;
|
|
191
|
-
({ files, s3Bucket, s3Client, s3Key } = await this.uploadProjectFiles(input));
|
|
192
|
-
input.parameters = {
|
|
193
|
-
...input.parameters,
|
|
194
|
-
hereyaProjectEnv: objectToBase64(input.projectEnv),
|
|
195
|
-
};
|
|
196
|
-
const output = await this.runCodeBuild({
|
|
197
|
-
...input,
|
|
198
|
-
deploy: true,
|
|
199
|
-
destroy: true,
|
|
200
|
-
sourceS3Key: s3Key,
|
|
201
|
-
});
|
|
202
|
-
if (!output.success) {
|
|
203
|
-
return output;
|
|
204
|
-
}
|
|
205
|
-
return { env, success: true };
|
|
206
|
-
}
|
|
207
|
-
finally {
|
|
208
|
-
if (s3Key && files.length > 0) {
|
|
209
|
-
await s3Client.send(new DeleteObjectsCommand({
|
|
210
|
-
Bucket: s3Bucket,
|
|
211
|
-
Delete: {
|
|
212
|
-
Objects: files.map((file) => ({ Key: `${s3Key}/${file}` })),
|
|
213
|
-
},
|
|
214
|
-
}));
|
|
215
|
-
}
|
|
216
|
-
}
|
|
196
|
+
input.parameters = {
|
|
197
|
+
...input.parameters,
|
|
198
|
+
hereyaProjectEnv: JSON.stringify(input.projectEnv ?? {}),
|
|
199
|
+
hereyaProjectRootDir: input.projectRootDir,
|
|
200
|
+
};
|
|
201
|
+
return this.destroy(input);
|
|
217
202
|
}
|
|
218
203
|
async unstoreEnv(input) {
|
|
219
204
|
const parameterStoreArnPattern = /^arn:aws:ssm:[\da-z-]+:\d{12}:parameter\/[\w./-]+$/;
|
|
@@ -233,171 +218,11 @@ export class AwsInfrastructure {
|
|
|
233
218
|
}));
|
|
234
219
|
return { success: true };
|
|
235
220
|
}
|
|
236
|
-
async
|
|
237
|
-
const ssmClient = new SSMClient({});
|
|
238
|
-
const ssmParameterName = `/hereya/${id}`;
|
|
239
|
-
try {
|
|
240
|
-
const ssmParameter = await ssmClient.send(new GetParameterCommand({
|
|
241
|
-
Name: ssmParameterName,
|
|
242
|
-
}));
|
|
243
|
-
return JSON.parse(ssmParameter.Parameter?.Value ?? '{}');
|
|
244
|
-
}
|
|
245
|
-
catch (error) {
|
|
246
|
-
if (error.name === 'ParameterNotFound') {
|
|
247
|
-
console.debug(`Parameter "${ssmParameterName}" does not exist.`);
|
|
248
|
-
return {};
|
|
249
|
-
}
|
|
250
|
-
throw error;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
async getFilesToUpload(rootDir) {
|
|
254
|
-
const ig = ignore.default();
|
|
255
|
-
const ignoreFilePath = await getAnyPath(`${rootDir}/.hereyaignore`, `${rootDir}/.gitignore`);
|
|
256
|
-
if (await fileExists(ignoreFilePath)) {
|
|
257
|
-
const ignoreFileContent = await fs.readFile(ignoreFilePath, 'utf8');
|
|
258
|
-
ig.add(ignoreFileContent);
|
|
259
|
-
}
|
|
260
|
-
const files = glob.sync('**/*', { cwd: rootDir, nodir: true });
|
|
261
|
-
return files.filter((file) => !ig.ignores(file));
|
|
262
|
-
}
|
|
263
|
-
async removeEnv(id) {
|
|
264
|
-
const ssmClient = new SSMClient({});
|
|
265
|
-
const ssmParameterName = `/hereya/${id}`;
|
|
266
|
-
await ssmClient.send(new DeleteParameterCommand({
|
|
267
|
-
Name: ssmParameterName,
|
|
268
|
-
}));
|
|
269
|
-
}
|
|
270
|
-
async runCodeBuild(input) {
|
|
271
|
-
const codebuildClient = new CodeBuildClient({});
|
|
272
|
-
let codebuildProjectName = '';
|
|
273
|
-
switch (input.iacType) {
|
|
274
|
-
case IacType.cdk: {
|
|
275
|
-
codebuildProjectName = 'hereyaCdk';
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
case IacType.terraform: {
|
|
279
|
-
codebuildProjectName = 'hereyaTerraform';
|
|
280
|
-
break;
|
|
281
|
-
}
|
|
282
|
-
default: {
|
|
283
|
-
return { reason: `IAC type ${input.iacType} is not supported yet!`, success: false };
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
const ssmClient = new SSMClient({});
|
|
287
|
-
const parameterName = `/hereya/package-parameters/${input.id}`;
|
|
288
|
-
const parameterValue = Object.entries(input.parameters ?? {})
|
|
289
|
-
.map(([key, value]) => `${key}=${typeof value === 'object' ? objectToBase64(value) : value}`)
|
|
290
|
-
.join(',');
|
|
291
|
-
if (parameterValue) {
|
|
292
|
-
await ssmClient.send(new PutParameterCommand({
|
|
293
|
-
Name: parameterName,
|
|
294
|
-
Overwrite: true,
|
|
295
|
-
Type: 'SecureString',
|
|
296
|
-
Value: parameterValue,
|
|
297
|
-
}));
|
|
298
|
-
}
|
|
299
|
-
const response = await codebuildClient.send(new StartBuildCommand({
|
|
300
|
-
environmentVariablesOverride: [
|
|
301
|
-
{
|
|
302
|
-
name: 'HEREYA_ID',
|
|
303
|
-
type: 'PLAINTEXT',
|
|
304
|
-
value: input.id,
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
name: 'HEREYA_IAC_TYPE',
|
|
308
|
-
type: 'PLAINTEXT',
|
|
309
|
-
value: input.iacType,
|
|
310
|
-
},
|
|
311
|
-
{
|
|
312
|
-
name: 'HEREYA_INFRA_TYPE',
|
|
313
|
-
type: 'PLAINTEXT',
|
|
314
|
-
value: InfrastructureType.aws,
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
name: 'HEREYA_PARAMETERS',
|
|
318
|
-
type: parameterValue ? 'PARAMETER_STORE' : 'PLAINTEXT',
|
|
319
|
-
value: parameterValue ? parameterName : '',
|
|
320
|
-
},
|
|
321
|
-
{
|
|
322
|
-
name: 'HEREYA_WORKSPACE_ENV',
|
|
323
|
-
type: 'PLAINTEXT',
|
|
324
|
-
value: Object.entries(input.env ?? {})
|
|
325
|
-
.map(([key, value]) => `${key}=${typeof value === 'object' ? objectToBase64(value) : value}`)
|
|
326
|
-
.join(','),
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
name: 'PKG_REPO_URL',
|
|
330
|
-
type: 'PLAINTEXT',
|
|
331
|
-
value: input.pkgUrl,
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
name: 'HEREYA_DESTROY',
|
|
335
|
-
type: 'PLAINTEXT',
|
|
336
|
-
value: input.destroy ? 'true' : '',
|
|
337
|
-
},
|
|
338
|
-
{
|
|
339
|
-
name: 'HEREYA_DEPLOY',
|
|
340
|
-
type: 'PLAINTEXT',
|
|
341
|
-
value: input.deploy ? 'true' : '',
|
|
342
|
-
},
|
|
343
|
-
{
|
|
344
|
-
name: 'HEREYA_PROJECT_S3_KEY',
|
|
345
|
-
type: 'PLAINTEXT',
|
|
346
|
-
value: input.deploy ? input.sourceS3Key : '',
|
|
347
|
-
},
|
|
348
|
-
],
|
|
349
|
-
projectName: codebuildProjectName,
|
|
350
|
-
}));
|
|
351
|
-
console.log(`Deployment ${response.build?.id} started successfully.`);
|
|
352
|
-
const command = new BatchGetBuildsCommand({
|
|
353
|
-
ids: [response.build?.id ?? ''],
|
|
354
|
-
});
|
|
355
|
-
const deploymentResult = await new Promise((resolve) => {
|
|
356
|
-
const handle = setInterval(async () => {
|
|
357
|
-
const buildResponse = await codebuildClient.send(command);
|
|
358
|
-
const build = buildResponse.builds?.[0];
|
|
359
|
-
if (build?.buildStatus === 'IN_PROGRESS') {
|
|
360
|
-
console.log(`Deployment ${response.build?.id} still in progress...`);
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
clearInterval(handle);
|
|
364
|
-
console.log(`Deployment ${response.build?.id} finished with status ${build?.buildStatus}.`);
|
|
365
|
-
resolve(build);
|
|
366
|
-
}, 10_000); // 10 seconds
|
|
367
|
-
});
|
|
368
|
-
if (deploymentResult?.buildStatus !== 'SUCCEEDED') {
|
|
369
|
-
return { reason: `Deployment failed with status ${deploymentResult?.buildStatus}`, success: false };
|
|
370
|
-
}
|
|
371
|
-
// remove the parameter
|
|
372
|
-
if (parameterValue) {
|
|
373
|
-
await ssmClient.send(new DeleteParameterCommand({
|
|
374
|
-
Name: parameterName,
|
|
375
|
-
}));
|
|
376
|
-
}
|
|
377
|
-
return { success: true };
|
|
378
|
-
}
|
|
379
|
-
async uploadProjectFiles(input) {
|
|
380
|
-
const key = '/hereya-bootstrap/config';
|
|
221
|
+
async getConfig() {
|
|
381
222
|
const ssmClient = new SSMClient({});
|
|
382
|
-
const
|
|
383
|
-
Name:
|
|
384
|
-
}));
|
|
385
|
-
const bootstrapConfig = JSON.parse(response.Parameter?.Value ?? '{}');
|
|
386
|
-
if (!bootstrapConfig.hereyaSourceCodeBucketName) {
|
|
387
|
-
throw new Error('hereyaSourceCodeBucketName not found in bootstrap config');
|
|
388
|
-
}
|
|
389
|
-
const s3Key = `${input.id}/${randomUUID()}`;
|
|
390
|
-
const s3Bucket = bootstrapConfig.hereyaSourceCodeBucketName;
|
|
391
|
-
const files = await this.getFilesToUpload(input.projectRootDir);
|
|
392
|
-
const s3Client = new S3Client({});
|
|
393
|
-
await Promise.all(files.map(async (file) => {
|
|
394
|
-
console.log(`Uploading ${file} to s3://${s3Bucket}/${s3Key}`);
|
|
395
|
-
await s3Client.send(new PutObjectCommand({
|
|
396
|
-
Body: await fs.readFile(path.join(input.projectRootDir, file)),
|
|
397
|
-
Bucket: s3Bucket,
|
|
398
|
-
Key: `${s3Key}/${file}`,
|
|
399
|
-
}));
|
|
223
|
+
const ssmParameter = await ssmClient.send(new GetParameterCommand({
|
|
224
|
+
Name: this.configKey,
|
|
400
225
|
}));
|
|
401
|
-
return
|
|
226
|
+
return JSON.parse(ssmParameter.Parameter?.Value ?? '{}');
|
|
402
227
|
}
|
|
403
228
|
}
|
|
@@ -14,6 +14,4 @@ export declare class LocalInfrastructure implements Infrastructure {
|
|
|
14
14
|
unbootstrap(): Promise<void>;
|
|
15
15
|
undeploy(input: UndeployInput): Promise<UndeployOutput>;
|
|
16
16
|
unstoreEnv(_: UnstoreEnvInput): Promise<UnstoreEnvOutput>;
|
|
17
|
-
private download;
|
|
18
|
-
private isNotEmpty;
|
|
19
17
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import * as os from 'node:os';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
-
import { simpleGit } from 'simple-git';
|
|
5
4
|
import { getIac } from '../iac/index.js';
|
|
5
|
+
import { downloadPackage } from '../lib/package/index.js';
|
|
6
6
|
export class LocalInfrastructure {
|
|
7
7
|
async bootstrap() {
|
|
8
8
|
console.log('Bootstrapping local infrastructure');
|
|
@@ -18,7 +18,7 @@ export class LocalInfrastructure {
|
|
|
18
18
|
async destroy(input) {
|
|
19
19
|
// noinspection DuplicatedCode
|
|
20
20
|
const destPath = path.join(os.homedir(), '.hereya', input.id, input.canonicalName);
|
|
21
|
-
const downloadPath = await
|
|
21
|
+
const downloadPath = await downloadPackage(input.pkgUrl, destPath);
|
|
22
22
|
const iac$ = getIac({ type: input.iacType });
|
|
23
23
|
if (!iac$.supported) {
|
|
24
24
|
return { reason: iac$.reason, success: false };
|
|
@@ -40,7 +40,7 @@ export class LocalInfrastructure {
|
|
|
40
40
|
async provision(input) {
|
|
41
41
|
// noinspection DuplicatedCode
|
|
42
42
|
const destPath = path.join(os.homedir(), '.hereya', input.id, input.canonicalName);
|
|
43
|
-
const downloadPath = await
|
|
43
|
+
const downloadPath = await downloadPackage(input.pkgUrl, destPath);
|
|
44
44
|
const iac$ = getIac({ type: input.iacType });
|
|
45
45
|
if (!iac$.supported) {
|
|
46
46
|
return { reason: iac$.reason, success: false };
|
|
@@ -81,26 +81,4 @@ export class LocalInfrastructure {
|
|
|
81
81
|
async unstoreEnv(_) {
|
|
82
82
|
return { success: true };
|
|
83
83
|
}
|
|
84
|
-
async download(pkgUrl, destPath) {
|
|
85
|
-
if (await this.isNotEmpty(destPath)) {
|
|
86
|
-
console.log(`Package already downloaded at ${destPath}`);
|
|
87
|
-
return destPath;
|
|
88
|
-
}
|
|
89
|
-
await fs.mkdir(destPath, { recursive: true });
|
|
90
|
-
console.log(`Downloading package from ${pkgUrl}`);
|
|
91
|
-
// Initialize simple-git
|
|
92
|
-
const git = simpleGit();
|
|
93
|
-
// Clone repository into temp directory
|
|
94
|
-
await git.clone(pkgUrl, destPath, ['--depth=1']);
|
|
95
|
-
return destPath;
|
|
96
|
-
}
|
|
97
|
-
async isNotEmpty(directoryPath) {
|
|
98
|
-
try {
|
|
99
|
-
const files = await fs.readdir(directoryPath);
|
|
100
|
-
return files.length > 0;
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
return false; // or you can handle the error as needed
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
84
|
}
|
package/dist/lib/filesystem.d.ts
CHANGED
package/dist/lib/filesystem.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { access, constants } from 'node:fs/promises';
|
|
1
|
+
import { access, constants, readdir } from 'node:fs/promises';
|
|
2
2
|
export async function getAnyPath(...candidates) {
|
|
3
3
|
const checkAccess = async (index) => {
|
|
4
4
|
if (index >= candidates.length)
|
|
@@ -22,3 +22,12 @@ export async function fileExists(filePath) {
|
|
|
22
22
|
return false;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
export async function isNotEmpty(directoryPath) {
|
|
26
|
+
try {
|
|
27
|
+
const files = await readdir(directoryPath);
|
|
28
|
+
return files.length > 0;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -6,6 +6,7 @@ export declare const packageManager: PackageManager;
|
|
|
6
6
|
export declare function getPackageManager(): PackageManager;
|
|
7
7
|
export declare function resolvePackage(input: ResolvePackageInput): Promise<ResolvePackageOutput>;
|
|
8
8
|
export declare function getPackageCanonicalName(packageName: string): string;
|
|
9
|
+
export declare function downloadPackage(pkgUrl: string, destPath: string): Promise<string>;
|
|
9
10
|
export type ResolvePackageInput = {
|
|
10
11
|
isDeploying?: boolean;
|
|
11
12
|
package: string;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import { simpleGit } from 'simple-git';
|
|
1
3
|
import * as yaml from 'yaml';
|
|
2
4
|
import { z } from 'zod';
|
|
3
5
|
import { IacType } from '../../iac/common.js';
|
|
4
6
|
import { InfrastructureType } from '../../infrastructure/common.js';
|
|
7
|
+
import { isNotEmpty } from '../filesystem.js';
|
|
5
8
|
import { GitHubPackageManager } from './github.js';
|
|
6
9
|
export const packageManager = new GitHubPackageManager();
|
|
7
10
|
export function getPackageManager() {
|
|
@@ -18,7 +21,7 @@ export async function resolvePackage(input) {
|
|
|
18
21
|
const metadataContentCandidates = (await Promise.all([
|
|
19
22
|
packageManager.getRepoContent({ owner, path: 'hereyarc.yaml', repo }),
|
|
20
23
|
packageManager.getRepoContent({ owner, path: 'hereyarc.yml', repo }),
|
|
21
|
-
])).filter(content$ => content$.found);
|
|
24
|
+
])).filter((content$) => content$.found);
|
|
22
25
|
if (metadataContentCandidates.length === 0) {
|
|
23
26
|
return { found: false, reason: `No hereya metadata file found in ${pkgUrl}` };
|
|
24
27
|
}
|
|
@@ -31,16 +34,16 @@ export async function resolvePackage(input) {
|
|
|
31
34
|
if (input.isDeploying && metadata.onDeploy) {
|
|
32
35
|
return resolvePackage({ package: metadata.onDeploy.pkg });
|
|
33
36
|
}
|
|
34
|
-
if (process.env.HEREYA_OVERRIDE_INFRA) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
37
|
+
// if (process.env.HEREYA_OVERRIDE_INFRA) {
|
|
38
|
+
// metadata.originalInfra = metadata.infra
|
|
39
|
+
// metadata.infra = process.env.HEREYA_OVERRIDE_INFRA as InfrastructureType
|
|
40
|
+
// }
|
|
38
41
|
return {
|
|
39
42
|
canonicalName: getPackageCanonicalName(input.package),
|
|
40
43
|
found: true,
|
|
41
44
|
metadata,
|
|
42
45
|
packageUri: pkgUrl,
|
|
43
|
-
pkgName: input.package
|
|
46
|
+
pkgName: input.package,
|
|
44
47
|
};
|
|
45
48
|
}
|
|
46
49
|
catch (error) {
|
|
@@ -50,14 +53,29 @@ export async function resolvePackage(input) {
|
|
|
50
53
|
export function getPackageCanonicalName(packageName) {
|
|
51
54
|
return packageName.replace('/', '-');
|
|
52
55
|
}
|
|
56
|
+
export async function downloadPackage(pkgUrl, destPath) {
|
|
57
|
+
if (await isNotEmpty(destPath)) {
|
|
58
|
+
console.log(`Package already downloaded at ${destPath}`);
|
|
59
|
+
return destPath;
|
|
60
|
+
}
|
|
61
|
+
await fs.mkdir(destPath, { recursive: true });
|
|
62
|
+
console.log(`Downloading package from ${pkgUrl}`);
|
|
63
|
+
// Initialize simple-git
|
|
64
|
+
const git = simpleGit();
|
|
65
|
+
// Clone repository into temp directory
|
|
66
|
+
await git.clone(pkgUrl, destPath, ['--depth=1']);
|
|
67
|
+
return destPath;
|
|
68
|
+
}
|
|
53
69
|
export const PackageMetadata = z.object({
|
|
54
70
|
dependencies: z.record(z.string()).optional(),
|
|
55
71
|
deploy: z.boolean().optional(),
|
|
56
72
|
iac: z.nativeEnum(IacType),
|
|
57
73
|
infra: z.nativeEnum(InfrastructureType),
|
|
58
|
-
onDeploy: z
|
|
74
|
+
onDeploy: z
|
|
75
|
+
.object({
|
|
59
76
|
pkg: z.string(),
|
|
60
77
|
version: z.string(),
|
|
61
|
-
})
|
|
78
|
+
})
|
|
79
|
+
.optional(),
|
|
62
80
|
originalInfra: z.nativeEnum(InfrastructureType).optional(),
|
|
63
81
|
});
|
package/oclif.manifest.json
CHANGED
|
@@ -504,56 +504,6 @@
|
|
|
504
504
|
"index.js"
|
|
505
505
|
]
|
|
506
506
|
},
|
|
507
|
-
"remote:exec": {
|
|
508
|
-
"aliases": [],
|
|
509
|
-
"args": {
|
|
510
|
-
"pkgPath": {
|
|
511
|
-
"description": "The path to the package to provision or destroy",
|
|
512
|
-
"name": "pkgPath",
|
|
513
|
-
"required": false
|
|
514
|
-
}
|
|
515
|
-
},
|
|
516
|
-
"description": "remotely provision or destroy a package",
|
|
517
|
-
"examples": [
|
|
518
|
-
"<%= config.bin %> <%= command.id %>"
|
|
519
|
-
],
|
|
520
|
-
"flags": {
|
|
521
|
-
"output": {
|
|
522
|
-
"char": "o",
|
|
523
|
-
"description": "The path to store the output env in",
|
|
524
|
-
"name": "output",
|
|
525
|
-
"required": false,
|
|
526
|
-
"hasDynamicHelp": false,
|
|
527
|
-
"multiple": false,
|
|
528
|
-
"type": "option"
|
|
529
|
-
},
|
|
530
|
-
"source": {
|
|
531
|
-
"char": "s",
|
|
532
|
-
"description": "The source of the project to provision or destroy the package for",
|
|
533
|
-
"name": "source",
|
|
534
|
-
"required": false,
|
|
535
|
-
"hasDynamicHelp": false,
|
|
536
|
-
"multiple": false,
|
|
537
|
-
"type": "option"
|
|
538
|
-
}
|
|
539
|
-
},
|
|
540
|
-
"hasDynamicHelp": false,
|
|
541
|
-
"hiddenAliases": [],
|
|
542
|
-
"id": "remote:exec",
|
|
543
|
-
"pluginAlias": "hereya-cli",
|
|
544
|
-
"pluginName": "hereya-cli",
|
|
545
|
-
"pluginType": "core",
|
|
546
|
-
"strict": true,
|
|
547
|
-
"enableJsonFlag": false,
|
|
548
|
-
"isESM": true,
|
|
549
|
-
"relativePath": [
|
|
550
|
-
"dist",
|
|
551
|
-
"commands",
|
|
552
|
-
"remote",
|
|
553
|
-
"exec",
|
|
554
|
-
"index.js"
|
|
555
|
-
]
|
|
556
|
-
},
|
|
557
507
|
"workspace:create": {
|
|
558
508
|
"aliases": [],
|
|
559
509
|
"args": {
|
|
@@ -898,5 +848,5 @@
|
|
|
898
848
|
]
|
|
899
849
|
}
|
|
900
850
|
},
|
|
901
|
-
"version": "0.
|
|
851
|
+
"version": "0.12.0"
|
|
902
852
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
|
-
export default class RemoteExec extends Command {
|
|
3
|
-
static args: {
|
|
4
|
-
pkgPath: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
-
};
|
|
6
|
-
static description: string;
|
|
7
|
-
static examples: string[];
|
|
8
|
-
static flags: {
|
|
9
|
-
output: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
-
source: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
11
|
-
};
|
|
12
|
-
run(): Promise<void>;
|
|
13
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { getIac } from '../../../iac/index.js';
|
|
4
|
-
import { getInfrastructure } from '../../../infrastructure/index.js';
|
|
5
|
-
import { tryBase64ToJSONString } from '../../../lib/object-utils.js';
|
|
6
|
-
import { save } from '../../../lib/yaml-utils.js';
|
|
7
|
-
export default class RemoteExec extends Command {
|
|
8
|
-
static args = {
|
|
9
|
-
pkgPath: Args.string({
|
|
10
|
-
description: 'The path to the package to provision or destroy',
|
|
11
|
-
required: false,
|
|
12
|
-
}),
|
|
13
|
-
};
|
|
14
|
-
static description = 'remotely provision or destroy a package';
|
|
15
|
-
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
16
|
-
static flags = {
|
|
17
|
-
output: Flags.string({
|
|
18
|
-
char: 'o',
|
|
19
|
-
description: 'The path to store the output env in',
|
|
20
|
-
required: false,
|
|
21
|
-
}),
|
|
22
|
-
source: Flags.string({
|
|
23
|
-
char: 's',
|
|
24
|
-
description: 'The source of the project to provision or destroy the package for',
|
|
25
|
-
required: false,
|
|
26
|
-
}),
|
|
27
|
-
};
|
|
28
|
-
async run() {
|
|
29
|
-
const { args, flags } = await this.parse(RemoteExec);
|
|
30
|
-
const workspaceEnv = Object.fromEntries((process.env.HEREYA_WORKSPACE_ENV?.split(',') ?? [])
|
|
31
|
-
.filter((param) => param.trim())
|
|
32
|
-
.map((param) => param.split('='))
|
|
33
|
-
.map(([key, value]) => [key, tryBase64ToJSONString(value)]));
|
|
34
|
-
const parameters = Object.fromEntries((process.env.HEREYA_PARAMETERS?.split(',') ?? [])
|
|
35
|
-
.filter((param) => param.trim())
|
|
36
|
-
.map((param) => param.split('='))
|
|
37
|
-
.map(([key, value]) => [key, tryBase64ToJSONString(value)]));
|
|
38
|
-
const id = process.env.HEREYA_ID;
|
|
39
|
-
const iacType = process.env.HEREYA_IAC_TYPE;
|
|
40
|
-
const destroy = process.env.HEREYA_DESTROY === 'true';
|
|
41
|
-
const infraType = process.env.HEREYA_INFRA_TYPE;
|
|
42
|
-
const deploy = process.env.HEREYA_DEPLOY === 'true';
|
|
43
|
-
const source = flags.source ? path.resolve(flags.source) : '';
|
|
44
|
-
if (deploy && !source) {
|
|
45
|
-
return this.error('Deploy packages provisioning requires a source path');
|
|
46
|
-
}
|
|
47
|
-
if (!id || !iacType || !infraType) {
|
|
48
|
-
return this.error(`
|
|
49
|
-
Missing required environment variables:
|
|
50
|
-
HEREYA_ID: ${id}
|
|
51
|
-
HEREYA_IAC_TYPE: ${iacType}
|
|
52
|
-
HEREYA_INFRA_TYPE: ${infraType}
|
|
53
|
-
`);
|
|
54
|
-
}
|
|
55
|
-
const input = {
|
|
56
|
-
env: workspaceEnv,
|
|
57
|
-
id,
|
|
58
|
-
parameters,
|
|
59
|
-
pkgPath: args.pkgPath || process.cwd(),
|
|
60
|
-
};
|
|
61
|
-
if (deploy) {
|
|
62
|
-
input.parameters = {
|
|
63
|
-
...input.parameters,
|
|
64
|
-
hereyaProjectRootDir: source,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
const iac$ = getIac({ type: iacType });
|
|
68
|
-
if (!iac$.supported) {
|
|
69
|
-
return this.error(iac$.reason);
|
|
70
|
-
}
|
|
71
|
-
const { iac } = iac$;
|
|
72
|
-
const output = await (destroy ? iac.destroy(input) : iac.apply(input));
|
|
73
|
-
if (!output.success) {
|
|
74
|
-
return this.error(output.reason);
|
|
75
|
-
}
|
|
76
|
-
if (flags.output) {
|
|
77
|
-
await save(output.env, flags.output);
|
|
78
|
-
this.log(`Output env saved to ${flags.output}`);
|
|
79
|
-
}
|
|
80
|
-
const infra$ = getInfrastructure({ type: infraType });
|
|
81
|
-
if (!infra$.supported) {
|
|
82
|
-
return this.error(infra$.reason);
|
|
83
|
-
}
|
|
84
|
-
const { infrastructure } = infra$;
|
|
85
|
-
const saveOutput = await infrastructure.saveEnv({ env: output.env, id });
|
|
86
|
-
if (!saveOutput.success) {
|
|
87
|
-
return this.error(saveOutput.reason);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|