@gershy/lilac 0.0.12 → 0.0.13

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/cmp/cjs/main.d.ts CHANGED
@@ -2,27 +2,22 @@ import '../sideEffects.js';
2
2
  import { PetalTerraform } from './petal/terraform/terraform.ts';
3
3
  import Logger from '@gershy/logger';
4
4
  import { Fact } from '@gershy/disk';
5
+ import { Soil } from './soil/soil.ts';
6
+ import { SuperIterable } from './util/superIterable.ts';
5
7
  export type Context = {
6
8
  name: string;
7
9
  logger: Logger;
8
10
  fact: Fact;
9
11
  patioFact: Fact;
10
- aws: {
11
- accountId: string;
12
- accessKey: {
13
- id: string;
14
- '!secret': string;
15
- };
16
- region: string;
17
- };
18
12
  maturity: string;
19
13
  debug: boolean;
20
14
  pfx: string;
21
15
  };
22
16
  export declare class Flower {
17
+ static getAwsServices(): Soil.LocalStackAwsService[];
23
18
  constructor();
24
19
  getDependencies(): Generator<Flower>;
25
- getPetals(ctx: Context): Iterable<PetalTerraform.Base> | AsyncIterable<PetalTerraform.Base>;
20
+ getPetals(ctx: Context): SuperIterable<PetalTerraform.Base>;
26
21
  }
27
22
  type RegistryFlowers<R extends Registry<any>, M extends 'real' | 'test'> = R extends Registry<infer Flowers> ? {
28
23
  [K in keyof Flowers]: Flowers[K][M];
@@ -33,6 +28,7 @@ export declare class Registry<Flowers extends Obj<{
33
28
  }> = Obj<never>> {
34
29
  private flowers;
35
30
  constructor(flowers: Flowers);
31
+ getAwsServices(): Soil.LocalStackAwsService[];
36
32
  add<MoreFlowers extends Obj<{
37
33
  real: typeof Flower;
38
34
  test: typeof Flower;
@@ -41,32 +37,23 @@ export declare class Registry<Flowers extends Obj<{
41
37
  }
42
38
  export declare class Garden<Reg extends Registry<any>> {
43
39
  private ctx;
44
- private registry;
45
- private define;
40
+ private reg;
41
+ private def;
46
42
  constructor(args: {
47
- name: string;
48
- logger: Logger;
49
- fact: Fact;
50
- patioFact: Fact;
51
- aws: {
52
- accountId: string;
53
- accessKey: {
54
- id: string;
55
- '!secret': string;
56
- };
57
- region: string;
58
- };
59
- maturity: string;
60
- debug: boolean;
61
- pfx: string;
43
+ context: Context;
62
44
  registry: Reg;
63
- define: Garden<Reg>['define'];
45
+ define: Garden<Reg>['def'];
64
46
  });
65
47
  private getPetals;
66
- prepare(): Promise<Obj<Fact>>;
48
+ genTerraform(deployTarget: Soil.Base): Promise<Obj<Fact>>;
67
49
  private terraformInit;
68
50
  private terraformPlan;
69
51
  private terraformApply;
70
- grow(mode: 'real' | 'test'): Promise<void>;
52
+ grow(deploy: {
53
+ type: 'real';
54
+ soil: Soil.Base;
55
+ } | {
56
+ type: 'test';
57
+ }): Promise<void>;
71
58
  }
72
59
  export * from './petal/terraform/terraform.ts';
package/cmp/cjs/main.js CHANGED
@@ -27,12 +27,19 @@ exports.Garden = exports.Registry = exports.Flower = void 0;
27
27
  // Can region be dealt with any better??
28
28
  // Support test-mode (Flowers need to be able to do setup, share config, write to volumes, etc)
29
29
  const terraform_ts_1 = require("./petal/terraform/terraform.js");
30
- const aws_ts_1 = require("./util/aws.js");
31
30
  const clearing_1 = require("@gershy/clearing");
32
31
  const procTerraform_ts_1 = __importDefault(require("./util/procTerraform.js"));
33
32
  const util_try_with_healing_1 = __importDefault(require("@gershy/util-try-with-healing"));
34
- const { File, Provider, Terraform } = terraform_ts_1.PetalTerraform;
33
+ const util_phrasing_1 = __importDefault(require("@gershy/util-phrasing"));
35
34
  class Flower {
35
+ // TODO: The downside of having this static is that different instances may use different
36
+ // services - e.g. api gateway instance may have "useEdge: true", in which case we'd like to
37
+ // include cloudfront and omit it otherwise... but having it on the instance is annoying since
38
+ // we want to enumerate all services *before* instantiating any Flowers... probably better this
39
+ // way? And heirarchical design can probably avoid most unecessary service inclusion...
40
+ // TODO: The naming of these services is coupled to LocalStack - consider using Lilac-scoped
41
+ // naming, and add a translation layer from Lilac->LocalStack in Soil.LocalStack?
42
+ static getAwsServices() { return []; }
36
43
  constructor() { }
37
44
  *getDependencies() {
38
45
  yield this;
@@ -44,10 +51,21 @@ class Flower {
44
51
  exports.Flower = Flower;
45
52
  ;
46
53
  class Registry {
54
+ // Note that maintaining a duality of classes for each Flower (one for testing, one for remote
55
+ // deploy) is essential to keep test functionality out of deployed code bundles. If a single
56
+ // class supported both test and prod functionality, these two pieces of functionality would
57
+ // always be bundled together.
47
58
  flowers;
48
59
  constructor(flowers) {
49
60
  this.flowers = {}[merge](flowers);
50
61
  }
62
+ getAwsServices() {
63
+ const services = new Set();
64
+ for (const [k, { real }] of this.flowers)
65
+ for (const awsService of real.getAwsServices())
66
+ services.add(awsService);
67
+ return services[toArr](v => v);
68
+ }
51
69
  add(flowers) {
52
70
  return new Registry({ ...this.flowers, ...flowers });
53
71
  }
@@ -60,23 +78,28 @@ exports.Registry = Registry;
60
78
  class Garden {
61
79
  // Note this class currently is coupled to terraform logic
62
80
  ctx;
63
- registry;
64
- define;
81
+ reg;
82
+ def;
65
83
  constructor(args) {
66
- const { define, registry, ...ctx } = args;
67
- this.ctx = ctx;
68
- this.registry = registry;
69
- this.define = define;
84
+ const { define, registry, context } = args;
85
+ this.ctx = context;
86
+ this.reg = registry;
87
+ this.def = define;
70
88
  }
71
- async *getPetals(mode) {
89
+ async *getPetals() {
90
+ // TODO: We always use the "real" flowers from the registry - this is part of the shift to
91
+ // localStack; we always generate genuine terraform and apply it to the docker localStack.
92
+ // Eventually may want to support ultra-lightweight dockerless/localStackless js flower mocks;
93
+ // that would be the time to add "fake" flowers alongside each real flower, and start
94
+ // conditionally calling `this.registry.get('fake')`...
72
95
  const seenFlowers = new Set();
73
96
  const seenPetals = new Set();
74
- for await (const topLevelFlower of this.define(this.ctx, this.registry.get(mode))) {
97
+ for await (const topLevelFlower of await this.def(this.ctx, this.reg.get('real'))) {
75
98
  for (const flower of topLevelFlower.getDependencies()) {
76
99
  if (seenFlowers.has(flower))
77
100
  continue;
78
101
  seenFlowers.add(flower);
79
- for await (const petal of flower.getPetals(this.ctx)) {
102
+ for await (const petal of await flower.getPetals(this.ctx)) {
80
103
  if (seenPetals.has(petal))
81
104
  continue;
82
105
  yield petal;
@@ -84,8 +107,9 @@ class Garden {
84
107
  }
85
108
  }
86
109
  }
87
- async prepare( /* Note this only pertains to "real" mode */) {
88
- return this.ctx.logger.scope('garden.prepare', {}, async (logger) => {
110
+ async genTerraform(deployTarget) {
111
+ const soilTfPetalsPrm = deployTarget.getTerraformPetals(this.ctx);
112
+ return this.ctx.logger.scope('garden.genTerraform', {}, async (logger) => {
89
113
  const setupTfProj = async (args) => args.logger.scope('tf', { proj: this.ctx.name, tf: args.term }, async (logger) => {
90
114
  // Allows a terraform project to be defined in terms of a function which writes to main.tf,
91
115
  // and adds any arbitrary additional files to the terraform project
@@ -104,11 +128,22 @@ class Garden {
104
128
  // Write new terraform
105
129
  await logger.scope('files.generate', {}, async (logger) => {
106
130
  const stream = await args.fact.kid(['main.tf']).getDataHeadStream();
107
- await args.setup(args.fact, stream);
131
+ await args.setup(args.fact, stream, async (petal) => {
132
+ // Include a utility function the caller can use to easily write petals
133
+ const { tf, files = {} } = await petal.getResult()
134
+ .then(tf => (0, clearing_1.isCls)(tf, String) ? { tf } : tf);
135
+ if (tf)
136
+ await stream.write(`${tf}\n`);
137
+ await Promise.all(files[toArr]((data, kfp) => args.fact.kid(kfp.split('/')).setData(data)));
138
+ return petal;
139
+ });
108
140
  await stream.end(); // TODO: @gershy/disk should allow `await headStream.end()`
109
141
  });
110
142
  return args.fact;
111
143
  });
144
+ // Pick names for the s3 and ddb terraform state persistence entities
145
+ const s3Name = `${this.ctx.pfx}-tf-state`;
146
+ const ddbName = `${this.ctx.pfx}-tf-state`;
112
147
  // We generate *two* terraform projects for every logical project - overall we want a
113
148
  // terraform project which saves its state in the cloud; in order to do this we need to first
114
149
  // provision the cloud storage engines to save the terraform state. The "boot" tf project
@@ -118,172 +153,106 @@ class Garden {
118
153
  term: 'boot',
119
154
  logger,
120
155
  fact: this.ctx.fact.kid(['boot']),
121
- setup: async (fact, mainWritable) => {
122
- await mainWritable.write(String[baseline](`
123
- | terraform {
124
- | required_providers {
125
- | aws = {
126
- | source = "hashicorp/aws"
127
- | version = "~> 5.0"
128
- | }
129
- | }
130
- | }
131
- | provider "aws" {
132
- | shared_credentials_files = [ "creds.ini" ]
133
- | profile = "default"
134
- | region = "ca-central-1"
135
- | }
136
- | resource "aws_s3_bucket" "tf_state" {
137
- | bucket = "${this.ctx.pfx}-tf-state"
138
- | }
139
- | resource "aws_s3_bucket_ownership_controls" "tf_state" {
140
- | bucket = aws_s3_bucket.tf_state.bucket
141
- | rule {
142
- | object_ownership = "ObjectWriter"
143
- | }
144
- | }
145
- | resource "aws_s3_bucket_acl" "tf_state" {
146
- | bucket = aws_s3_bucket.tf_state.bucket
147
- | acl = "private"
148
- | depends_on = [ aws_s3_bucket_ownership_controls.tf_state ]
149
- | }
150
- | resource "aws_dynamodb_table" "tf_state" {
151
- | name = "${this.ctx.pfx}-tf-state"
152
- | billing_mode = "PAY_PER_REQUEST"
153
- | hash_key = "LockID"
154
- | attribute {
155
- | name = "LockID"
156
- | type = "S"
157
- | }
158
- | }
159
- `));
160
- await fact.kid(['creds.ini']).setData(String[baseline](`
161
- | [default]
162
- | aws_region = ${this.ctx.aws.region}
163
- | aws_access_key_id = ${this.ctx.aws.accessKey.id}
164
- | aws_secret_access_key = ${this.ctx.aws.accessKey['!secret']}
165
- `));
156
+ setup: async (fact, mainWritable, writePetalTfAndFiles) => {
157
+ // Include the soil's infrastructure
158
+ const { boot } = await soilTfPetalsPrm;
159
+ for await (const petal of await boot({ s3Name, ddbName }))
160
+ await writePetalTfAndFiles(petal);
161
+ // Create s3 tf state bucket
162
+ const s3 = await writePetalTfAndFiles(new terraform_ts_1.PetalTerraform.Resource('awsS3Bucket', 'tfState', {
163
+ bucket: s3Name
164
+ }));
165
+ const s3Controls = await writePetalTfAndFiles(new terraform_ts_1.PetalTerraform.Resource('awsS3BucketOwnershipControls', 'tfState', {
166
+ bucket: s3.ref('bucket'),
167
+ $rule: {
168
+ objectOwnership: 'ObjectWriter'
169
+ }
170
+ }));
171
+ await writePetalTfAndFiles(new terraform_ts_1.PetalTerraform.Resource('awsS3BucketAcl', 'tfState', {
172
+ bucket: s3.ref('bucket'),
173
+ acl: 'private',
174
+ dependsOn: [s3Controls.ref()]
175
+ }));
176
+ // Create ddb tf state locking table
177
+ await writePetalTfAndFiles(new terraform_ts_1.PetalTerraform.Resource('awsDynamodbTable', 'tfState', {
178
+ name: ddbName,
179
+ billingMode: (0, util_phrasing_1.default)('payPerRequest', 'camel', 'snake')[upper](),
180
+ hashKey: 'LockID',
181
+ $attribute: { name: 'LockID', type: 'S' }
182
+ }));
166
183
  }
167
184
  }),
168
185
  mainFact: setupTfProj({
169
186
  term: 'main',
170
187
  logger,
171
188
  fact: this.ctx.fact.kid(['main']),
172
- setup: async (fact, mainWritable) => {
173
- const garden = this;
174
- const iteratePetals = async function* () {
175
- const tfAwsCredsFile = new File('creds.ini', String[baseline](`
176
- | [default]
177
- | aws_region = ${garden.ctx.aws.region}
178
- | aws_access_key_id = ${garden.ctx.aws.accessKey.id}
179
- | aws_secret_access_key = ${garden.ctx.aws.accessKey['!secret']}
180
- `));
181
- yield tfAwsCredsFile;
182
- const terraform = new Terraform({
183
- $requiredProviders: {
184
- aws: {
185
- source: 'hashicorp/aws',
186
- version: `~> 5.0` // Consider parameterizing??
187
- }
188
- },
189
- '$backend.s3': {
190
- region: garden.ctx.aws.region,
191
- encrypt: true,
192
- // Note references not allowed in terraform.backend!!
193
- bucket: `${garden.ctx.pfx}-tf-state`,
194
- key: `tf`,
195
- dynamodbTable: `${garden.ctx.pfx}-tf-state`, // Dynamodb table is aws-account-wide
196
- sharedCredentialsFiles: [tfAwsCredsFile.tfRef()],
197
- profile: 'default', // References a section within the credentials file
198
- }
199
- });
200
- yield terraform;
201
- for (const { term } of aws_ts_1.regions)
202
- yield new Provider('aws', {
203
- sharedCredentialsFiles: [tfAwsCredsFile.tfRef()],
204
- profile: 'default', // References a section within the credentials file
205
- region: term,
206
- // Omit the alias for the default provider!
207
- ...(term !== garden.ctx.aws.region && { alias: term.split('-').join('_') })
208
- });
209
- yield* garden.getPetals('real');
210
- };
211
- const patioTfHclFact = garden.ctx.patioFact.kid(['main', '.terraform.lock.hcl']);
189
+ setup: async (fact, mainWritable, writePetalTfAndFiles) => {
190
+ // Include the soil's infrastructure
191
+ const { main } = await soilTfPetalsPrm;
192
+ for await (const petal of await main({ s3Name, ddbName }))
193
+ await writePetalTfAndFiles(petal);
194
+ for await (const petal of this.getPetals())
195
+ await writePetalTfAndFiles(petal);
196
+ // Propagate any terraform lock found in version control
197
+ const patioTfHclFact = this.ctx.patioFact.kid(['main', '.terraform.lock.hcl']);
212
198
  const tfHclData = await patioTfHclFact.getData('str');
213
199
  if (tfHclData)
214
200
  await fact.kid(['.terraform.lock.hcl']).setData(tfHclData);
215
- for await (const petal of iteratePetals()) {
216
- const result = await (async () => {
217
- const result = await petal.getResult();
218
- if (!(0, clearing_1.isCls)(result, Object))
219
- return { tf: result, files: {} };
220
- return { files: {}, ...result };
221
- })();
222
- if (result.tf)
223
- await mainWritable.write(`${result.tf}\n`);
224
- await Promise.all(result.files[toArr]((data, kfp) => fact.kid(kfp.split('/')).setData(data)));
225
- }
226
201
  }
227
- })
202
+ }),
228
203
  });
229
204
  });
230
205
  }
231
206
  terraformInit(fact, args) {
232
- return this.ctx.logger.scope('execTf.init', {}, async (logger) => {
207
+ return this.ctx.logger.scope('execTf.init', { fact: fact.fsp() }, async (logger) => {
233
208
  // Consider if we ever want to pass "-reconfigure" and "-migrate-state" options; these are
234
209
  // useful if we are moving backends (e.g. one aws account to another), and want to move our
235
210
  // full iac definition too
236
- // TODO: Some terraform commands fail when offline - can this be covered up?
237
- const result = await (0, procTerraform_ts_1.default)(fact, `terraform init -input=false`);
211
+ // TODO: Some terraform commands fail when offline - can this be covered up? Possibly by
212
+ // checking terraform binaries into the repo? (Cross-platform nightmare though...)
213
+ const result = await (0, procTerraform_ts_1.default)(fact, `terraform init -input=false`, {
214
+ onData: async (mode, data) => logger.log({ $$: 'notice', mode, data }) ?? null
215
+ });
238
216
  logger.log({ $$: 'result', logFp: result.logDb.toString(), msg: result.output });
239
217
  return result;
240
218
  });
241
219
  }
242
220
  terraformPlan(fact, args) {
243
- return this.ctx.logger.scope('execTf.plan', {}, async (logger) => {
221
+ return this.ctx.logger.scope('execTf.plan', { fact: fact.fsp() }, async (logger) => {
244
222
  const result = await (0, procTerraform_ts_1.default)(fact, `terraform plan -input=false`);
245
223
  logger.log({ $$: 'result', logFp: result.logDb.toString(), msg: result.output });
246
224
  return result;
247
225
  });
248
226
  }
249
227
  terraformApply(fact, args) {
250
- return this.ctx.logger.scope('execTf.apply', {}, async (logger) => {
228
+ return this.ctx.logger.scope('execTf.apply', { fact: fact.fsp() }, async (logger) => {
251
229
  const result = await (0, procTerraform_ts_1.default)(fact, `terraform apply -input=false -auto-approve`);
252
230
  logger.log({ $$: 'result', logFp: result.logDb.toString(), msg: result.output });
253
231
  return result;
254
232
  });
255
233
  }
256
- async grow(mode) {
257
- // - Avoid tf preventing deletions on populated dbs - e.g. s3 tf needs "force_destroy = true"
258
- // - Note `terraform init` generates a ".terraform.lock.hcl" file - this should be checked into
259
- // consumer's source control! May need consumers to provide an optional `repoFact`, and if
260
- // present after `terraform init` we can copy the ".terraform.lock.hcl" file
261
- // Note that test mode does not involve any iac - it all runs locally
262
- if (mode === 'test')
263
- throw Error('test mode not implemented yet');
264
- const { bootFact, mainFact } = await this.prepare();
265
- const logicalApply = (args) => {
266
- return (0, util_try_with_healing_1.default)({
267
- fn: () => this.terraformApply(args.mainFact),
268
- canHeal: err => (err.output ?? '')[has]('please run "terraform init"'),
234
+ async grow(deploy) {
235
+ if (deploy.type === 'test')
236
+ throw Error('not implemented')[mod]({ type: 'test' }); // TODO: Can be nice to have local service mocks!
237
+ const { bootFact, mainFact } = await this.genTerraform(deploy.soil);
238
+ // Init+apply both "boot" and "main", in optimistic fashion
239
+ const isHealableTerraformApply = err => /run[^a-zA-Z0-9]+terraform init/.test(err.output ?? '');
240
+ await (0, util_try_with_healing_1.default)({
241
+ fn: () => this.terraformApply(mainFact),
242
+ canHeal: isHealableTerraformApply,
243
+ heal: () => (0, util_try_with_healing_1.default)({
244
+ fn: async () => {
245
+ await this.terraformInit(mainFact);
246
+ await this.ctx.patioFact.kid(['main', '.terraform.lock.hcl']).setData(await mainFact.kid(['.terraform.lock.hcl']).getData('str'));
247
+ },
248
+ canHeal: err => true,
269
249
  heal: () => (0, util_try_with_healing_1.default)({
270
- fn: async () => {
271
- await this.terraformInit(args.mainFact);
272
- await this.ctx.patioFact.kid(['main', '.terraform.lock.hcl']).setData(await args.mainFact.kid(['.terraform.lock.hcl']).getData('str'));
273
- },
274
- canHeal: err => true,
275
- heal: () => (0, util_try_with_healing_1.default)({
276
- fn: () => this.terraformApply(args.bootFact),
277
- canHeal: err => (err.output ?? '')[has]('please run "terraform init"'),
278
- heal: () => this.terraformInit(args.bootFact)
279
- })
250
+ fn: () => this.terraformApply(bootFact),
251
+ canHeal: isHealableTerraformApply,
252
+ heal: () => this.terraformInit(bootFact)
280
253
  })
281
- });
282
- };
283
- // TODO: HEEERE do `logicalApply` instead!
284
- await this.terraformInit(bootFact);
285
- // const result = await logicalApply({ mainFact, bootFact });
286
- //const result = await execTf.init(bootFact);
254
+ })
255
+ });
287
256
  }
288
257
  }
289
258
  exports.Garden = Garden;
@@ -8,8 +8,8 @@ export declare namespace PetalTerraform {
8
8
  getProps(): {
9
9
  [key: string]: Json;
10
10
  };
11
- tfRef(props?: string | string[]): string;
12
- tfRefp(props?: string | string[]): `| ${string}`;
11
+ refStr(props?: string | string[]): string;
12
+ ref(props?: string | string[]): `| ${string}`;
13
13
  getResultHeader(): Promise<string>;
14
14
  getResult(): Promise<string | {
15
15
  tf: string;
@@ -64,7 +64,7 @@ export declare namespace PetalTerraform {
64
64
  [key: string]: Json;
65
65
  };
66
66
  getResultHeader(): Promise<string>;
67
- tfRef(props?: string | string[]): string;
67
+ refStr(props?: string | string[]): string;
68
68
  }
69
69
  class File extends Base {
70
70
  private fp;
@@ -77,6 +77,6 @@ export declare namespace PetalTerraform {
77
77
  [x: string]: string | Buffer<ArrayBufferLike>;
78
78
  };
79
79
  }>;
80
- tfRef(props?: string | string[]): string;
80
+ refStr(props?: string | string[]): string;
81
81
  }
82
82
  }
@@ -79,7 +79,7 @@ var PetalTerraform;
79
79
  getType() { throw Error('not implemented'); }
80
80
  getHandle() { throw Error('not implemented'); }
81
81
  getProps() { throw Error('not implemented'); }
82
- tfRef(props = []) {
82
+ refStr(props = []) {
83
83
  if (!(0, clearing_1.isCls)(props, Array))
84
84
  props = [props];
85
85
  const base = `${(0, util_phrasing_1.default)(this.getType(), 'camel', 'snake')}.${(0, util_phrasing_1.default)(this.getHandle(), 'camel', 'snake')}`;
@@ -87,9 +87,9 @@ var PetalTerraform;
87
87
  ? `${base}.${props[map](v => (0, util_phrasing_1.default)(v, 'camel', 'snake')).join('.')}`
88
88
  : base;
89
89
  }
90
- tfRefp(props = []) {
90
+ ref(props = []) {
91
91
  // "plain ref" - uses "| " to avoid being quoted within terraform
92
- return `| ${this.tfRef(props)}`;
92
+ return `| ${this.refStr(props)}`;
93
93
  }
94
94
  async getResultHeader() { throw Error('not implemented'); }
95
95
  async getResult() {
@@ -166,8 +166,8 @@ var PetalTerraform;
166
166
  async getResultHeader() {
167
167
  return `data "${(0, util_phrasing_1.default)(this.type, 'camel', 'snake')}" "${(0, util_phrasing_1.default)(this.handle, 'camel', 'snake')}"`;
168
168
  }
169
- tfRef(props = []) {
170
- return `data.${super.tfRef(props)}`;
169
+ refStr(props = []) {
170
+ return `data.${super.refStr(props)}`;
171
171
  }
172
172
  }
173
173
  PetalTerraform.Data = Data;
@@ -184,7 +184,7 @@ var PetalTerraform;
184
184
  async getResult() {
185
185
  return { tf: '', files: { [this.fp]: this.content } };
186
186
  }
187
- tfRef(props) {
187
+ refStr(props) {
188
188
  return this.fp; // `this.fp` should be quoted but not transformed to a tf handle
189
189
  }
190
190
  }
@@ -0,0 +1,79 @@
1
+ import { Context, PetalTerraform, Registry } from '../main.ts';
2
+ import { RegionTerm } from '../util/aws.ts';
3
+ import { NetProc } from '@gershy/util-http';
4
+ import { SuperIterable } from '../util/superIterable.ts';
5
+ import Logger from '@gershy/logger';
6
+ export declare namespace Soil {
7
+ type PetalProjArgs = {
8
+ s3Name: string;
9
+ ddbName: string;
10
+ };
11
+ type PetalProjResult = {
12
+ [K in 'boot' | 'main']: (args: PetalProjArgs) => SuperIterable<PetalTerraform.Base>;
13
+ };
14
+ type LocalStackAwsService = never | 'acm' | 'apigateway' | 'cloudformation' | 'cloudwatch' | 'config' | 'dynamodb' | 'dynamodbstreams' | 'ec2' | 'es' | 'events' | 'firehose' | 'iam' | 'kinesis' | 'kms' | 'lambda' | 'logs' | 'opensearch' | 'redshift' | 'resource' | 'resourcegroupstaggingapi' | 'route53' | 'route53resolver' | 's3' | 's3control' | 'scheduler' | 'secretsmanager' | 'ses' | 'sns' | 'sqs' | 'ssm' | 'stepfunctions' | 'sts' | 'support' | 'swf' | 'transcribe';
15
+ type BaseArgs = {
16
+ registry: Registry<any>;
17
+ };
18
+ class Base {
19
+ protected registry: Registry<any>;
20
+ constructor(args: BaseArgs);
21
+ getTerraformPetals(ctx: Context): Promise<PetalProjResult>;
22
+ }
23
+ type LocalStackArgs = BaseArgs & {
24
+ aws: {
25
+ region: RegionTerm;
26
+ };
27
+ localStackDocker?: {
28
+ image?: `localstack/localstack${':' | ':latest' | '@'}${string}`;
29
+ containerName?: string;
30
+ port?: number;
31
+ };
32
+ };
33
+ class LocalStack extends Base {
34
+ private static localStackInternalPort;
35
+ private aws;
36
+ private localStackDocker;
37
+ private procArgs;
38
+ constructor(args: LocalStackArgs);
39
+ private getAwsServices;
40
+ private getDockerContainers;
41
+ run(args: {
42
+ logger: Logger;
43
+ }): Promise<{
44
+ aws: {
45
+ services: LocalStackAwsService[];
46
+ region: "ca-central-1" | "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2";
47
+ };
48
+ netProc: NetProc;
49
+ url: string;
50
+ }>;
51
+ end(args?: {
52
+ containers?: Awaited<ReturnType<Soil.LocalStack['getDockerContainers']>>;
53
+ }): Promise<{
54
+ name: string;
55
+ state: "created" | "running" | "paused" | "restarting" | "removing" | "exited" | "dead";
56
+ }[]>;
57
+ getTerraformPetals(ctx: Context): Promise<{
58
+ boot: () => (PetalTerraform.Terraform | PetalTerraform.Provider)[];
59
+ main: (args: any) => Generator<PetalTerraform.Terraform | PetalTerraform.Provider, void, unknown>;
60
+ }>;
61
+ }
62
+ type AwsCloudArgs = BaseArgs & {
63
+ aws: {
64
+ region: RegionTerm;
65
+ accessKey: {
66
+ id: string;
67
+ '!secret': string;
68
+ };
69
+ };
70
+ };
71
+ class AwsCloud extends Base {
72
+ private aws;
73
+ constructor(args: AwsCloudArgs);
74
+ getTerraformPetals(ctx: Context): Promise<{
75
+ boot: () => Generator<PetalTerraform.Terraform | PetalTerraform.Provider | PetalTerraform.File, void, unknown>;
76
+ main: (args: any) => Generator<PetalTerraform.Terraform | PetalTerraform.Provider | PetalTerraform.File, void, unknown>;
77
+ }>;
78
+ }
79
+ }