hereya-cli 0.9.2 → 0.11.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.
@@ -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) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : 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) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : 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) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : 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) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : 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,29 +74,41 @@ export class Terraform {
45
74
  runShell(terraform, ['init'], {
46
75
  directory: input.pkgPath,
47
76
  env: {
48
- ...mapObject(input.env ?? {}, (key, value) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : 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) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : 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) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : 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) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : 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
  }
@@ -114,7 +155,8 @@ export class Terraform {
114
155
  }
115
156
  }
116
157
  await new Promise((resolve, reject) => {
117
- https.get(url, async (response) => {
158
+ https
159
+ .get(url, async (response) => {
118
160
  try {
119
161
  await pipeline(response, decompress({ path: path.dirname(tfPath) }));
120
162
  await fs.promises.chmod(tfPath, '0755');
@@ -123,7 +165,8 @@ export class Terraform {
123
165
  throw new Error(`could not download terraform: ${error}`);
124
166
  }
125
167
  resolve(null);
126
- }).on('error', error => reject(error));
168
+ })
169
+ .on('error', (error) => reject(error));
127
170
  });
128
171
  return true;
129
172
  }
@@ -131,7 +174,7 @@ export class Terraform {
131
174
  const terraform = await this.getTerraformBinary();
132
175
  const resourceOut = runShell(terraform, ['output', '--json'], {
133
176
  directory: pkgPath,
134
- stdio: 'pipe'
177
+ stdio: 'pipe',
135
178
  });
136
179
  let outStr = resourceOut.output.toString().trim();
137
180
  const start = outStr.indexOf('{');
@@ -1,16 +1,15 @@
1
- import { BootstrapInput, DeployInput, DeployOutput, DestroyInput, DestroyOutput, Infrastructure, ProvisionInput, ProvisionOutput, ResolveEnvInput, ResolveEnvOutput, SaveEnvInput, SaveEnvOutput, UndeployInput, UndeployOutput } from './common.js';
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>;
6
7
  provision(input: ProvisionInput): Promise<ProvisionOutput>;
7
8
  resolveEnv(input: ResolveEnvInput): Promise<ResolveEnvOutput>;
8
9
  saveEnv(input: SaveEnvInput): Promise<SaveEnvOutput>;
10
+ storeEnv(input: StoreEnvInput): Promise<StoreEnvOutput>;
9
11
  unbootstrap(_: BootstrapInput): Promise<void>;
10
12
  undeploy(input: UndeployInput): Promise<UndeployOutput>;
11
- private getEnv;
12
- private getFilesToUpload;
13
- private removeEnv;
14
- private runCodeBuild;
15
- private uploadProjectFiles;
13
+ unstoreEnv(input: UnstoreEnvInput): Promise<UnstoreEnvOutput>;
14
+ private getConfig;
16
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 { IacType } from '../iac/common.js';
12
- import { fileExists, getAnyPath } from '../lib/filesystem.js';
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,65 +22,88 @@ export class AwsInfrastructure {
26
22
  throw new Error(output.reason);
27
23
  }
28
24
  const { env } = output;
29
- const key = '/hereya-bootstrap/config';
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({
33
29
  Name: key,
34
30
  Overwrite: true,
35
31
  Type: 'String',
36
- Value: value
32
+ Value: value,
37
33
  }));
38
34
  }
39
35
  async deploy(input) {
40
- let files = [];
41
- let s3Bucket = '';
42
- let s3Client = new S3Client({});
43
- let s3Key = '';
44
- try {
45
- ({ files, s3Bucket, s3Client, s3Key } = await this.uploadProjectFiles(input));
46
- input.parameters = {
47
- ...input.parameters,
48
- hereyaProjectEnv: objectToBase64(input.projectEnv),
49
- };
50
- const output = await this.runCodeBuild({
51
- ...input,
52
- deploy: true,
53
- sourceS3Key: s3Key
54
- });
55
- if (!output.success) {
56
- return output;
57
- }
58
- const env = await this.getEnv(input.id);
59
- return { env, success: true };
60
- }
61
- finally {
62
- if (s3Key && files.length > 0) {
63
- await s3Client.send(new DeleteObjectsCommand({
64
- Bucket: s3Bucket,
65
- Delete: {
66
- Objects: files.map(file => ({ Key: `${s3Key}/${file}` }))
67
- }
68
- }));
69
- }
70
- }
36
+ input.parameters = {
37
+ ...input.parameters,
38
+ hereyaProjectEnv: JSON.stringify(input.projectEnv ?? {}),
39
+ hereyaProjectRootDir: input.projectRootDir,
40
+ };
41
+ return this.provision(input);
71
42
  }
72
43
  async destroy(input) {
73
- const env = await this.getEnv(input.id);
74
- const output = await this.runCodeBuild({ ...input, destroy: true });
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
+ });
75
69
  if (!output.success) {
76
- return output;
70
+ return { reason: output.reason, success: false };
77
71
  }
78
- await this.removeEnv(input.id);
79
- return { env, success: true };
72
+ // Remove downloaded package
73
+ await fs.rm(downloadPath, { recursive: true });
74
+ return { env: output.env, success: true };
80
75
  }
81
76
  async provision(input) {
82
- const output = await this.runCodeBuild(input);
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
+ });
83
103
  if (!output.success) {
84
- return output;
104
+ return { reason: output.reason, success: false };
85
105
  }
86
- const env = await this.getEnv(input.id);
87
- return { env, success: true };
106
+ return { env: output.env, success: true };
88
107
  }
89
108
  async resolveEnv(input) {
90
109
  try {
@@ -97,7 +116,7 @@ export class AwsInfrastructure {
97
116
  }));
98
117
  return {
99
118
  isSecret: response.Parameter?.Type === 'SecureString',
100
- value: response.Parameter?.Value ?? input.value
119
+ value: response.Parameter?.Value ?? input.value,
101
120
  };
102
121
  }
103
122
  const secretManagerArnPattern = /^arn:aws:secretsmanager:[\da-z-]+:\d{12}:secret:[\w-]+/;
@@ -108,7 +127,7 @@ export class AwsInfrastructure {
108
127
  }));
109
128
  return {
110
129
  isSecret: true,
111
- value: response.SecretString ?? input.value
130
+ value: response.SecretString ?? input.value,
112
131
  };
113
132
  }
114
133
  return { value: input.value };
@@ -127,7 +146,7 @@ export class AwsInfrastructure {
127
146
  Name: key,
128
147
  Overwrite: true,
129
148
  Type: 'String',
130
- Value: value
149
+ Value: value,
131
150
  }));
132
151
  return { success: true };
133
152
  }
@@ -135,6 +154,27 @@ export class AwsInfrastructure {
135
154
  return { reason: error.message, success: false };
136
155
  }
137
156
  }
157
+ async storeEnv(input) {
158
+ if (!input.sensitive) {
159
+ return { success: true, value: input.value };
160
+ }
161
+ const ssmClient = new SSMClient({});
162
+ const id = randomUUID();
163
+ const ssmParameterName = input.oldValue ?? `/hereya/${input.name}/${id}`;
164
+ await ssmClient.send(new PutParameterCommand({
165
+ Name: ssmParameterName,
166
+ Overwrite: true,
167
+ Type: 'SecureString',
168
+ Value: input.value,
169
+ }));
170
+ const parameter = await ssmClient.send(new GetParameterCommand({
171
+ Name: ssmParameterName,
172
+ }));
173
+ if (!parameter.Parameter?.ARN) {
174
+ throw new Error(`Could not store env var ${input.name} for AWS infrastructure`);
175
+ }
176
+ return { success: true, value: parameter.Parameter.ARN };
177
+ }
138
178
  async unbootstrap(_) {
139
179
  const ssmClient = new SSMClient({});
140
180
  const key = '/hereya-bootstrap/config';
@@ -153,206 +193,36 @@ export class AwsInfrastructure {
153
193
  }
154
194
  }
155
195
  async undeploy(input) {
156
- let files = [];
157
- let s3Bucket = '';
158
- let s3Client = new S3Client({});
159
- let s3Key = '';
160
- let env = {};
161
- try {
162
- env = await this.getEnv(input.id);
163
- }
164
- catch (error) {
165
- console.log(`Could not get env for ${input.id}: ${error.message}. Continuing with undeployment...`);
166
- }
167
- try {
168
- ({ files, s3Bucket, s3Client, s3Key } = await this.uploadProjectFiles(input));
169
- input.parameters = {
170
- ...input.parameters,
171
- hereyaProjectEnv: objectToBase64(input.projectEnv),
172
- };
173
- const output = await this.runCodeBuild({
174
- ...input,
175
- deploy: true,
176
- destroy: true,
177
- sourceS3Key: s3Key
178
- });
179
- if (!output.success) {
180
- return output;
181
- }
182
- return { env, success: true };
183
- }
184
- finally {
185
- if (s3Key && files.length > 0) {
186
- await s3Client.send(new DeleteObjectsCommand({
187
- Bucket: s3Bucket,
188
- Delete: {
189
- Objects: files.map(file => ({ Key: `${s3Key}/${file}` }))
190
- }
191
- }));
192
- }
193
- }
196
+ input.parameters = {
197
+ ...input.parameters,
198
+ hereyaProjectEnv: JSON.stringify(input.projectEnv ?? {}),
199
+ hereyaProjectRootDir: input.projectRootDir,
200
+ };
201
+ return this.destroy(input);
194
202
  }
195
- async getEnv(id) {
196
- const ssmClient = new SSMClient({});
197
- const ssmParameterName = `/hereya/${id}`;
198
- try {
199
- const ssmParameter = await ssmClient.send(new GetParameterCommand({
200
- Name: ssmParameterName,
201
- }));
202
- return JSON.parse(ssmParameter.Parameter?.Value ?? '{}');
203
- }
204
- catch (error) {
205
- if (error.name === "ParameterNotFound") {
206
- console.debug(`Parameter "${ssmParameterName}" does not exist.`);
207
- return {};
208
- }
209
- throw error;
210
- }
211
- }
212
- async getFilesToUpload(rootDir) {
213
- const ig = ignore.default();
214
- const ignoreFilePath = await getAnyPath(`${rootDir}/.hereyaignore`, `${rootDir}/.gitignore`);
215
- if (await fileExists(ignoreFilePath)) {
216
- const ignoreFileContent = await fs.readFile(ignoreFilePath, 'utf8');
217
- ig.add(ignoreFileContent);
203
+ async unstoreEnv(input) {
204
+ const parameterStoreArnPattern = /^arn:aws:ssm:[\da-z-]+:\d{12}:parameter\/[\w./-]+$/;
205
+ if (!parameterStoreArnPattern.test(input.value)) {
206
+ return { success: true };
218
207
  }
219
- const files = glob.sync('**/*', { cwd: rootDir, nodir: true });
220
- return files.filter(file => !ig.ignores(file));
221
- }
222
- async removeEnv(id) {
223
208
  const ssmClient = new SSMClient({});
224
- const ssmParameterName = `/hereya/${id}`;
225
- await ssmClient.send(new DeleteParameterCommand({
226
- Name: ssmParameterName,
209
+ const parameter = await ssmClient.send(new GetParameterCommand({
210
+ Name: input.value,
227
211
  }));
228
- }
229
- async runCodeBuild(input) {
230
- const codebuildClient = new CodeBuildClient({});
231
- let codebuildProjectName = '';
232
- switch (input.iacType) {
233
- case IacType.cdk: {
234
- codebuildProjectName = 'hereyaCdk';
235
- break;
236
- }
237
- case IacType.terraform: {
238
- codebuildProjectName = 'hereyaTerraform';
239
- break;
240
- }
241
- default: {
242
- return { reason: `IAC type ${input.iacType} is not supported yet!`, success: false };
243
- }
244
- }
245
- const ssmClient = new SSMClient({});
246
- const parameterName = `/hereya/package-parameters/${input.id}`;
247
- const parameterValue = Object.entries(input.parameters ?? {}).map(([key, value]) => `${key}=${typeof value === "object" ? objectToBase64(value) : value}`).join(',');
248
- if (parameterValue) {
249
- await ssmClient.send(new PutParameterCommand({
250
- Name: parameterName,
251
- Overwrite: true,
252
- Type: 'SecureString',
253
- Value: parameterValue,
254
- }));
212
+ const ssmParameterName = parameter.Parameter?.Name;
213
+ if (!ssmParameterName) {
214
+ return { success: true };
255
215
  }
256
- const response = await codebuildClient.send(new StartBuildCommand({
257
- environmentVariablesOverride: [
258
- {
259
- name: 'HEREYA_ID',
260
- type: 'PLAINTEXT',
261
- value: input.id,
262
- },
263
- {
264
- name: 'HEREYA_IAC_TYPE',
265
- type: 'PLAINTEXT',
266
- value: input.iacType,
267
- },
268
- {
269
- name: 'HEREYA_INFRA_TYPE',
270
- type: 'PLAINTEXT',
271
- value: InfrastructureType.aws,
272
- },
273
- {
274
- name: 'HEREYA_PARAMETERS',
275
- type: parameterValue ? 'PARAMETER_STORE' : 'PLAINTEXT',
276
- value: parameterValue ? parameterName : '',
277
- },
278
- {
279
- name: 'HEREYA_WORKSPACE_ENV',
280
- type: 'PLAINTEXT',
281
- value: Object.entries(input.env ?? {}).map(([key, value]) => `${key}=${typeof value === "object" ? objectToBase64(value) : value}`).join(','),
282
- },
283
- {
284
- name: 'PKG_REPO_URL',
285
- type: 'PLAINTEXT',
286
- value: input.pkgUrl,
287
- },
288
- {
289
- name: 'HEREYA_DESTROY',
290
- type: 'PLAINTEXT',
291
- value: input.destroy ? 'true' : '',
292
- },
293
- {
294
- name: 'HEREYA_DEPLOY',
295
- type: 'PLAINTEXT',
296
- value: input.deploy ? 'true' : '',
297
- },
298
- {
299
- name: 'HEREYA_PROJECT_S3_KEY',
300
- type: 'PLAINTEXT',
301
- value: input.deploy ? input.sourceS3Key : '',
302
- }
303
- ],
304
- projectName: codebuildProjectName,
216
+ await ssmClient.send(new DeleteParameterCommand({
217
+ Name: ssmParameterName,
305
218
  }));
306
- console.log(`Deployment ${response.build?.id} started successfully.`);
307
- const command = new BatchGetBuildsCommand({
308
- ids: [response.build?.id ?? ''],
309
- });
310
- const deploymentResult = await new Promise((resolve) => {
311
- const handle = setInterval(async () => {
312
- const buildResponse = await codebuildClient.send(command);
313
- const build = buildResponse.builds?.[0];
314
- if (build?.buildStatus === 'IN_PROGRESS') {
315
- console.log(`Deployment ${response.build?.id} still in progress...`);
316
- return;
317
- }
318
- clearInterval(handle);
319
- console.log(`Deployment ${response.build?.id} finished with status ${build?.buildStatus}.`);
320
- resolve(build);
321
- }, 10_000); // 10 seconds
322
- });
323
- if (deploymentResult?.buildStatus !== 'SUCCEEDED') {
324
- return { reason: `Deployment failed with status ${deploymentResult?.buildStatus}`, success: false };
325
- }
326
- // remove the parameter
327
- if (parameterValue) {
328
- await ssmClient.send(new DeleteParameterCommand({
329
- Name: parameterName,
330
- }));
331
- }
332
219
  return { success: true };
333
220
  }
334
- async uploadProjectFiles(input) {
335
- const key = '/hereya-bootstrap/config';
221
+ async getConfig() {
336
222
  const ssmClient = new SSMClient({});
337
- const response = await ssmClient.send(new GetParameterCommand({
338
- Name: key,
339
- }));
340
- const bootstrapConfig = JSON.parse(response.Parameter?.Value ?? '{}');
341
- if (!bootstrapConfig.hereyaSourceCodeBucketName) {
342
- throw new Error('hereyaSourceCodeBucketName not found in bootstrap config');
343
- }
344
- const s3Key = `${input.id}/${randomUUID()}`;
345
- const s3Bucket = bootstrapConfig.hereyaSourceCodeBucketName;
346
- const files = await this.getFilesToUpload(input.projectRootDir);
347
- const s3Client = new S3Client({});
348
- await Promise.all(files.map(async (file) => {
349
- console.log(`Uploading ${file} to s3://${s3Bucket}/${s3Key}`);
350
- await s3Client.send(new PutObjectCommand({
351
- Body: await fs.readFile(path.join(input.projectRootDir, file)),
352
- Bucket: s3Bucket,
353
- Key: `${s3Key}/${file}`,
354
- }));
223
+ const ssmParameter = await ssmClient.send(new GetParameterCommand({
224
+ Name: this.configKey,
355
225
  }));
356
- return { files, s3Bucket, s3Client, s3Key };
226
+ return JSON.parse(ssmParameter.Parameter?.Value ?? '{}');
357
227
  }
358
228
  }
@@ -12,8 +12,10 @@ export interface Infrastructure {
12
12
  provision(input: ProvisionInput): Promise<ProvisionOutput>;
13
13
  resolveEnv(input: ResolveEnvInput): Promise<ResolveEnvOutput>;
14
14
  saveEnv(input: SaveEnvInput): Promise<SaveEnvOutput>;
15
+ storeEnv(input: StoreEnvInput): Promise<StoreEnvOutput>;
15
16
  unbootstrap(input: BootstrapInput): Promise<void>;
16
17
  undeploy(input: UndeployInput): Promise<UndeployOutput>;
18
+ unstoreEnv(input: UnstoreEnvInput): Promise<UnstoreEnvOutput>;
17
19
  }
18
20
  export type BootstrapInput = {
19
21
  force?: boolean;
@@ -70,3 +72,25 @@ export type SaveEnvOutput = {
70
72
  } | {
71
73
  success: true;
72
74
  };
75
+ export type StoreEnvInput = {
76
+ name: string;
77
+ oldValue?: string;
78
+ sensitive?: boolean;
79
+ value: string;
80
+ };
81
+ export type StoreEnvOutput = {
82
+ reason: string;
83
+ success: false;
84
+ } | {
85
+ success: true;
86
+ value: string;
87
+ };
88
+ export type UnstoreEnvInput = {
89
+ value: string;
90
+ };
91
+ export type UnstoreEnvOutput = {
92
+ reason: string;
93
+ success: false;
94
+ } | {
95
+ success: true;
96
+ };
@@ -1,4 +1,4 @@
1
- import { DeployInput, DeployOutput, Infrastructure, ProvisionInput, ProvisionOutput, SaveEnvInput, SaveEnvOutput, UndeployInput, UndeployOutput } from './common.js';
1
+ import { DeployInput, DeployOutput, Infrastructure, ProvisionInput, ProvisionOutput, SaveEnvInput, SaveEnvOutput, StoreEnvInput, StoreEnvOutput, UndeployInput, UndeployOutput, UnstoreEnvInput, UnstoreEnvOutput } from './common.js';
2
2
  export declare class LocalInfrastructure implements Infrastructure {
3
3
  bootstrap(): Promise<void>;
4
4
  deploy(input: DeployInput): Promise<DeployOutput>;
@@ -10,8 +10,8 @@ export declare class LocalInfrastructure implements Infrastructure {
10
10
  value: string;
11
11
  }>;
12
12
  saveEnv(input: SaveEnvInput): Promise<SaveEnvOutput>;
13
+ storeEnv(input: StoreEnvInput): Promise<StoreEnvOutput>;
13
14
  unbootstrap(): Promise<void>;
14
15
  undeploy(input: UndeployInput): Promise<UndeployOutput>;
15
- private download;
16
- private isNotEmpty;
16
+ unstoreEnv(_: UnstoreEnvInput): Promise<UnstoreEnvOutput>;
17
17
  }