@gershy/lilac 0.0.13 → 0.0.15

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
@@ -1,7 +1,8 @@
1
1
  import '../sideEffects.js';
2
2
  import { PetalTerraform } from './petal/terraform/terraform.ts';
3
- import Logger from '@gershy/logger';
4
- import { Fact } from '@gershy/disk';
3
+ import type Logger from '@gershy/logger';
4
+ import { type Fact } from '@gershy/disk';
5
+ import '@gershy/clearing';
5
6
  import { Soil } from './soil/soil.ts';
6
7
  import { SuperIterable } from './util/superIterable.ts';
7
8
  export type Context = {
@@ -9,6 +10,7 @@ export type Context = {
9
10
  logger: Logger;
10
11
  fact: Fact;
11
12
  patioFact: Fact;
13
+ shedFact: Fact;
12
14
  maturity: string;
13
15
  debug: boolean;
14
16
  pfx: string;
@@ -39,6 +41,7 @@ export declare class Garden<Reg extends Registry<any>> {
39
41
  private ctx;
40
42
  private reg;
41
43
  private def;
44
+ private tfProcArgs;
42
45
  constructor(args: {
43
46
  context: Context;
44
47
  registry: Reg;
package/cmp/cjs/main.js CHANGED
@@ -22,15 +22,23 @@ exports.Garden = exports.Registry = exports.Flower = void 0;
22
22
  // A more generic (beyond just tf) provider is very hard to support due to the multiplicity of
23
23
  // provider/petal combos - e.g. "api" flower would need to support ,api.getCloudformationPetals,
24
24
  // api.getTerraformPetals, etc... supporting just terraform for now
25
- // Watch out when working lilac into an npm dependency, need to allow the user a way to declare
26
- // their absolute repo path (so "<repo>/..." filenames work in any setup!)
27
- // Can region be dealt with any better??
28
25
  // Support test-mode (Flowers need to be able to do setup, share config, write to volumes, etc)
29
26
  const terraform_ts_1 = require("./petal/terraform/terraform.js");
30
- const clearing_1 = require("@gershy/clearing");
31
- const procTerraform_ts_1 = __importDefault(require("./util/procTerraform.js"));
27
+ const disk_1 = require("@gershy/disk");
28
+ require("@gershy/clearing");
32
29
  const util_try_with_healing_1 = __importDefault(require("@gershy/util-try-with-healing"));
33
30
  const util_phrasing_1 = __importDefault(require("@gershy/util-phrasing"));
31
+ const nodejs_proc_1 = __importDefault(require("@gershy/nodejs-proc"));
32
+ const { isCls, skip } = cl;
33
+ const toArr = cl.toArr;
34
+ const allObj = cl.allObj;
35
+ const has = cl.has;
36
+ const map = cl.map;
37
+ const mod = cl.mod;
38
+ const walk = cl.walk;
39
+ const merge = cl.merge;
40
+ const upper = cl.upper;
41
+ const baseline = cl.baseline;
34
42
  class Flower {
35
43
  // TODO: The downside of having this static is that different instances may use different
36
44
  // services - e.g. api gateway instance may have "useEdge: true", in which case we'd like to
@@ -61,7 +69,7 @@ class Registry {
61
69
  }
62
70
  getAwsServices() {
63
71
  const services = new Set();
64
- for (const [k, { real }] of this.flowers)
72
+ for (const [k, { real }] of this.flowers[walk]())
65
73
  for (const awsService of real.getAwsServices())
66
74
  services.add(awsService);
67
75
  return services[toArr](v => v);
@@ -80,11 +88,21 @@ class Garden {
80
88
  ctx;
81
89
  reg;
82
90
  def;
91
+ tfProcArgs;
83
92
  constructor(args) {
84
93
  const { define, registry, context } = args;
85
94
  this.ctx = context;
86
95
  this.reg = registry;
87
96
  this.def = define;
97
+ this.tfProcArgs = {
98
+ timeoutMs: 0,
99
+ env: {
100
+ ...process.env,
101
+ TF_LOG: 'DEBUG',
102
+ TF_DATA_DIR: '',
103
+ TF_CLI_CONFIG_FILE: ''
104
+ }
105
+ };
88
106
  }
89
107
  async *getPetals() {
90
108
  // TODO: We always use the "real" flowers from the registry - this is part of the shift to
@@ -123,7 +141,7 @@ class Garden {
123
141
  'terraform.tfstate.backup'
124
142
  ]);
125
143
  const kids = await args.fact.getKids();
126
- await Promise.all(kids[toArr]((kid, k) => tfFilesToPreserve.has(k) ? clearing_1.skip : kid.rem()));
144
+ await Promise.all(kids[toArr]((kid, k) => tfFilesToPreserve.has(k) ? skip : kid.rem()));
127
145
  });
128
146
  // Write new terraform
129
147
  await logger.scope('files.generate', {}, async (logger) => {
@@ -131,13 +149,13 @@ class Garden {
131
149
  await args.setup(args.fact, stream, async (petal) => {
132
150
  // Include a utility function the caller can use to easily write petals
133
151
  const { tf, files = {} } = await petal.getResult()
134
- .then(tf => (0, clearing_1.isCls)(tf, String) ? { tf } : tf);
152
+ .then(tf => isCls(tf, String) ? { tf } : tf);
135
153
  if (tf)
136
154
  await stream.write(`${tf}\n`);
137
155
  await Promise.all(files[toArr]((data, kfp) => args.fact.kid(kfp.split('/')).setData(data)));
138
156
  return petal;
139
157
  });
140
- await stream.end(); // TODO: @gershy/disk should allow `await headStream.end()`
158
+ await stream.end();
141
159
  });
142
160
  return args.fact;
143
161
  });
@@ -176,7 +194,7 @@ class Garden {
176
194
  // Create ddb tf state locking table
177
195
  await writePetalTfAndFiles(new terraform_ts_1.PetalTerraform.Resource('awsDynamodbTable', 'tfState', {
178
196
  name: ddbName,
179
- billingMode: (0, util_phrasing_1.default)('payPerRequest', 'camel', 'snake')[upper](),
197
+ billingMode: (0, util_phrasing_1.default)('camel->snake', 'payPerRequest')[upper](),
180
198
  hashKey: 'LockID',
181
199
  $attribute: { name: 'LockID', type: 'S' }
182
200
  }));
@@ -203,31 +221,55 @@ class Garden {
203
221
  });
204
222
  });
205
223
  }
206
- terraformInit(fact, args) {
224
+ // TODO: Write terraform output to logs??
225
+ async terraformInit(fact) {
226
+ // Consider if we ever want to pass "-reconfigure" and "-migrate-state" options; these are
227
+ // useful if we are moving backends (e.g. one aws account to another), and want to move our
228
+ // full iac definition too
229
+ // Ensure the mirror directory exists in the shed
230
+ const mirrorFact = this.ctx.shedFact.kid(['lilacTerraformMirror']);
231
+ await mirrorFact.kid(['note.txt']).setData(`Root of terraform mirror for @gershy/lilac`);
207
232
  return this.ctx.logger.scope('execTf.init', { fact: fact.fsp() }, async (logger) => {
208
- // Consider if we ever want to pass "-reconfigure" and "-migrate-state" options; these are
209
- // useful if we are moving backends (e.g. one aws account to another), and want to move our
210
- // full iac definition too
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
233
+ const { output: result } = await (0, util_try_with_healing_1.default)({
234
+ fn: () => logger.scope('attempt', {}, async (logger) => {
235
+ const configFact = disk_1.tempFact.kid([`${Math.random().toString(36).slice(2)}.terraform.rc`]);
236
+ await configFact.setData(String[baseline](`
237
+ | provider_installation {
238
+ | filesystem_mirror {
239
+ | path = "${mirrorFact.fsp().replaceAll('\\', '/')}"
240
+ | }
241
+ | }
242
+ `));
243
+ return (0, nodejs_proc_1.default)(`terraform init -input=false`, {}[merge](this.tfProcArgs)[merge]({
244
+ cwd: fact,
245
+ env: { TF_CLI_CONFIG_FILE: configFact.fsp() }
246
+ })).finally(() => configFact.rem());
247
+ }),
248
+ canHeal: err => (err?.output ?? '')[has]('Could not retrieve the list of available versions for provider'),
249
+ heal: () => logger.scope('mirror', { fsp: mirrorFact.fsp() }, async (logger) => {
250
+ const { output: result } = await (0, nodejs_proc_1.default)(`terraform providers mirror "${mirrorFact.fsp().replaceAll('\\', '/')}"`, { cwd: fact, timeoutMs: 0 });
251
+ logger.log({ $$: 'result', result });
252
+ })
215
253
  });
216
- logger.log({ $$: 'result', logFp: result.logDb.toString(), msg: result.output });
254
+ logger.log({ $$: 'result', result });
217
255
  return result;
218
256
  });
219
257
  }
220
258
  terraformPlan(fact, args) {
221
259
  return this.ctx.logger.scope('execTf.plan', { fact: fact.fsp() }, async (logger) => {
222
- const result = await (0, procTerraform_ts_1.default)(fact, `terraform plan -input=false`);
223
- logger.log({ $$: 'result', logFp: result.logDb.toString(), msg: result.output });
260
+ const { output: result } = await (0, nodejs_proc_1.default)(`terraform plan -input=false`, {}[merge](this.tfProcArgs)[merge]({
261
+ cwd: fact,
262
+ }));
263
+ logger.log({ $$: 'result', result });
224
264
  return result;
225
265
  });
226
266
  }
227
267
  terraformApply(fact, args) {
228
268
  return this.ctx.logger.scope('execTf.apply', { fact: fact.fsp() }, async (logger) => {
229
- const result = await (0, procTerraform_ts_1.default)(fact, `terraform apply -input=false -auto-approve`);
230
- logger.log({ $$: 'result', logFp: result.logDb.toString(), msg: result.output });
269
+ const { output: result } = await (0, nodejs_proc_1.default)(`terraform apply -input=false -auto-approve`, {}[merge](this.tfProcArgs)[merge]({
270
+ cwd: fact
271
+ }));
272
+ logger.log({ $$: 'result', result });
231
273
  return result;
232
274
  });
233
275
  }
@@ -1,3 +1,4 @@
1
+ import '@gershy/clearing';
1
2
  import Petal from '../petal.ts';
2
3
  export declare namespace PetalTerraform {
3
4
  class Base extends Petal {
@@ -4,10 +4,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.PetalTerraform = void 0;
7
- const clearing_1 = require("@gershy/clearing");
7
+ require("@gershy/clearing");
8
8
  const slashEscape_ts_1 = __importDefault(require("../../util/slashEscape.js"));
9
9
  const petal_ts_1 = __importDefault(require("../petal.js"));
10
10
  const util_phrasing_1 = __importDefault(require("@gershy/util-phrasing"));
11
+ const { getClsName, isCls } = cl;
12
+ const hasHead = cl.hasHead;
13
+ const map = cl.map;
14
+ const hasTail = cl.hasTail;
15
+ const indent = cl.indent;
16
+ const mod = cl.mod;
17
+ const toArr = cl.toArr;
18
+ const has = cl.has;
11
19
  var PetalTerraform;
12
20
  (function (PetalTerraform) {
13
21
  class Base extends petal_ts_1.default {
@@ -15,15 +23,15 @@ var PetalTerraform;
15
23
  static terraformEncode = (val) => {
16
24
  if (val === null)
17
25
  return 'null';
18
- if ((0, clearing_1.isCls)(val, String))
26
+ if (isCls(val, String))
19
27
  return val[hasHead]('| ') ? val.slice('| '.length) : `"${(0, slashEscape_ts_1.default)(val, '"\n')}"`;
20
- if ((0, clearing_1.isCls)(val, Number))
28
+ if (isCls(val, Number))
21
29
  return `${val.toString(10)}`;
22
- if ((0, clearing_1.isCls)(val, Boolean))
30
+ if (isCls(val, Boolean))
23
31
  return val ? 'true' : 'false';
24
- if ((0, clearing_1.isCls)(val, Array)) {
32
+ if (isCls(val, Array)) {
25
33
  const vals = val[map](v => this.terraformEncode(v));
26
- if (vals.some(v => (0, clearing_1.isCls)(v, String) && v[hasHead]('| ')))
34
+ if (vals.some(v => isCls(v, String) && v[hasHead]('| ')))
27
35
  process.exit(0);
28
36
  if (vals.length === 0)
29
37
  return '[]';
@@ -31,7 +39,7 @@ var PetalTerraform;
31
39
  return `[ ${vals[0]} ]`;
32
40
  return `[\n${vals.join(',\n')[indent](' ')}\n]`;
33
41
  }
34
- if ((0, clearing_1.isCls)(val, Object)) {
42
+ if (isCls(val, Object)) {
35
43
  // Strings use "| " to avoid any quoting (enabling complex/arbitrary tf)
36
44
  // Objects use "$" as a key-prefix to define "nested blocks" instead of "inline maps"
37
45
  const keys = Object.keys(val);
@@ -51,17 +59,17 @@ var PetalTerraform;
51
59
  if (dedup)
52
60
  pcs.pop(); // Remove last item
53
61
  // Multi-component keys must pertain to objects
54
- if (pcs.length > 1 && !(0, clearing_1.isCls)(v, Object))
62
+ if (pcs.length > 1 && !isCls(v, Object))
55
63
  throw Error('tf key of this form must correspond to object value')[mod]({ k, v });
56
64
  // Resolve to raw string?
57
- if (special && (0, clearing_1.isCls)(v, String))
58
- return [(0, util_phrasing_1.default)(pcs[0], 'camel', 'snake'), ' = ', this.terraformEncode(v[hasHead]('| ') ? v : `| ${v}`)];
65
+ if (special && isCls(v, String))
66
+ return [(0, util_phrasing_1.default)('camel->snake', pcs[0]), ' = ', this.terraformEncode(v[hasHead]('| ') ? v : `| ${v}`)];
59
67
  // Resolve to nested block?
60
- if (special && (0, clearing_1.isCls)(v, Object))
61
- return [[(0, util_phrasing_1.default)(pcs[0], 'camel', 'snake'), ...pcs.slice(1)[map](pc => (0, util_phrasing_1.default)(pc, 'camel', 'snake'))].join(' '), ' ', this.terraformEncode(v)];
68
+ if (special && isCls(v, Object))
69
+ return [[(0, util_phrasing_1.default)('camel->snake', pcs[0]), ...pcs.slice(1)[map](pc => (0, util_phrasing_1.default)('camel->snake', pc))].join(' '), ' ', this.terraformEncode(v)];
62
70
  // Resolve anything else to typical property - use the key exactly as provided (to support,
63
71
  // e.g., aws format for keys in policies, any other specific format, etc.)
64
- return [(0, util_phrasing_1.default)(pcs[0], 'camel', 'snake'), ' = ', this.terraformEncode(v)];
72
+ return [(0, util_phrasing_1.default)('camel->snake', pcs[0]), ' = ', this.terraformEncode(v)];
65
73
  });
66
74
  const len = entryItems.length;
67
75
  if (len === 0)
@@ -73,18 +81,18 @@ var PetalTerraform;
73
81
  return `{ ${entries[0]} }`;
74
82
  return `{\n` + entries.join('\n')[indent](' ') + '\n}';
75
83
  }
76
- throw Error('unexpected val')[mod]({ form: (0, clearing_1.getClsName)(val), val });
84
+ throw Error('unexpected val')[mod]({ form: getClsName(val), val });
77
85
  };
78
86
  constructor() { super(); }
79
87
  getType() { throw Error('not implemented'); }
80
88
  getHandle() { throw Error('not implemented'); }
81
89
  getProps() { throw Error('not implemented'); }
82
90
  refStr(props = []) {
83
- if (!(0, clearing_1.isCls)(props, Array))
91
+ if (!isCls(props, Array))
84
92
  props = [props];
85
- const base = `${(0, util_phrasing_1.default)(this.getType(), 'camel', 'snake')}.${(0, util_phrasing_1.default)(this.getHandle(), 'camel', 'snake')}`;
93
+ const base = `${(0, util_phrasing_1.default)('camel->snake', this.getType())}.${(0, util_phrasing_1.default)('camel->snake', this.getHandle())}`;
86
94
  return props.length
87
- ? `${base}.${props[map](v => (0, util_phrasing_1.default)(v, 'camel', 'snake')).join('.')}`
95
+ ? `${base}.${props[map](v => (0, util_phrasing_1.default)('camel->snake', v)).join('.')}`
88
96
  : base;
89
97
  }
90
98
  ref(props = []) {
@@ -130,7 +138,7 @@ var PetalTerraform;
130
138
  getHandle() { return this.handle; }
131
139
  getProps() { return this.props; }
132
140
  async getResultHeader() {
133
- return `resource "${(0, util_phrasing_1.default)(this.type, 'camel', 'snake')}" "${(0, util_phrasing_1.default)(this.handle, 'camel', 'snake')}"`;
141
+ return `resource "${(0, util_phrasing_1.default)('camel->snake', this.type)}" "${(0, util_phrasing_1.default)('camel->snake', this.handle)}"`;
134
142
  }
135
143
  }
136
144
  PetalTerraform.Resource = Resource;
@@ -145,7 +153,7 @@ var PetalTerraform;
145
153
  }
146
154
  getProps() { return this.props; }
147
155
  async getResultHeader() {
148
- return `provider "${(0, util_phrasing_1.default)(this.name, 'camel', 'snake')}"`;
156
+ return `provider "${(0, util_phrasing_1.default)('camel->snake', this.name)}"`;
149
157
  }
150
158
  }
151
159
  PetalTerraform.Provider = Provider;
@@ -164,7 +172,7 @@ var PetalTerraform;
164
172
  getHandle() { return this.handle; }
165
173
  getProps() { return this.props; }
166
174
  async getResultHeader() {
167
- return `data "${(0, util_phrasing_1.default)(this.type, 'camel', 'snake')}" "${(0, util_phrasing_1.default)(this.handle, 'camel', 'snake')}"`;
175
+ return `data "${(0, util_phrasing_1.default)('camel->snake', this.type)}" "${(0, util_phrasing_1.default)('camel->snake', this.handle)}"`;
168
176
  }
169
177
  refStr(props = []) {
170
178
  return `data.${super.refStr(props)}`;
@@ -1,8 +1,10 @@
1
1
  import { Context, PetalTerraform, Registry } from '../main.ts';
2
2
  import { RegionTerm } from '../util/aws.ts';
3
+ import '@gershy/clearing';
3
4
  import { NetProc } from '@gershy/util-http';
4
5
  import { SuperIterable } from '../util/superIterable.ts';
5
- import Logger from '@gershy/logger';
6
+ import type Logger from '@gershy/logger';
7
+ import { RestApi } from '@aws-sdk/client-api-gateway';
6
8
  export declare namespace Soil {
7
9
  type PetalProjArgs = {
8
10
  s3Name: string;
@@ -13,9 +15,11 @@ export declare namespace Soil {
13
15
  };
14
16
  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
17
  type BaseArgs = {
18
+ logger: Logger;
16
19
  registry: Registry<any>;
17
20
  };
18
21
  class Base {
22
+ protected logger: Logger;
19
23
  protected registry: Registry<any>;
20
24
  constructor(args: BaseArgs);
21
25
  getTerraformPetals(ctx: Context): Promise<PetalProjResult>;
@@ -34,19 +38,21 @@ export declare namespace Soil {
34
38
  private static localStackInternalPort;
35
39
  private aws;
36
40
  private localStackDocker;
37
- private procArgs;
38
41
  constructor(args: LocalStackArgs);
39
42
  private getAwsServices;
40
43
  private getDockerContainers;
41
- run(args: {
42
- logger: Logger;
43
- }): Promise<{
44
+ run(): Promise<{
44
45
  aws: {
45
46
  services: LocalStackAwsService[];
46
47
  region: "ca-central-1" | "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2";
47
48
  };
48
49
  netProc: NetProc;
49
- url: string;
50
+ getApis: () => Promise<{
51
+ [x: string]: RestApi & {
52
+ id: string;
53
+ name: string;
54
+ };
55
+ }>;
50
56
  }>;
51
57
  end(args?: {
52
58
  containers?: Awaited<ReturnType<Soil.LocalStack['getDockerContainers']>>;
@@ -4,18 +4,34 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Soil = void 0;
7
- const disk_1 = require("@gershy/disk");
8
7
  const nodejs_proc_1 = __importDefault(require("@gershy/nodejs-proc"));
9
8
  const main_ts_1 = require("../main.js");
10
9
  const util_retry_1 = __importDefault(require("@gershy/util-retry"));
11
- const clearing_1 = require("@gershy/clearing");
10
+ require("@gershy/clearing");
12
11
  const util_http_1 = __importDefault(require("@gershy/util-http"));
13
12
  const aws_ts_1 = require("../util/aws.js");
13
+ const client_api_gateway_1 = require("@aws-sdk/client-api-gateway");
14
+ const { skip } = clearing;
15
+ const merge = cl.merge;
16
+ const map = cl.map;
17
+ const cut = cl.cut;
18
+ const fire = cl.fire;
19
+ const hasHead = cl.hasHead;
20
+ const has = cl.has;
21
+ const toObj = cl.toObj;
22
+ const toArr = cl.toArr;
23
+ const baseline = cl.baseline;
24
+ const mod = cl.mod;
25
+ const group = cl.group;
26
+ // TODO: Consider splitting each Soil implementation into its own unit
27
+ // (Soil.LocalStack unit can, e.g., isolate the `@aws-sdk/client-api-gateway` dependency)
14
28
  var Soil;
15
29
  (function (Soil) {
16
30
  class Base {
31
+ logger;
17
32
  registry;
18
33
  constructor(args) {
34
+ this.logger = args.logger;
19
35
  this.registry = args.registry;
20
36
  }
21
37
  async getTerraformPetals(ctx) {
@@ -28,16 +44,14 @@ var Soil;
28
44
  static localStackInternalPort = 4566;
29
45
  aws;
30
46
  localStackDocker;
31
- procArgs;
32
47
  constructor(args) {
33
- super(args);
48
+ super({ ...args, logger: args.logger.kid('localStack') });
34
49
  this.aws = args.aws;
35
50
  this.localStackDocker = {
36
51
  image: 'localstack/localstack:latest',
37
52
  port: LocalStack.localStackInternalPort,
38
53
  containerName: 'gershyLilacLocalStack'
39
54
  }[merge](args.localStackDocker ?? {});
40
- this.procArgs = { cwd: disk_1.rootFact, env: process.env };
41
55
  }
42
56
  getAwsServices() {
43
57
  // Note that "overhead" services are essential for initializing localstack:
@@ -49,29 +63,29 @@ var Soil;
49
63
  }
50
64
  async getDockerContainers() {
51
65
  const { containerName } = this.localStackDocker;
52
- const dockerPs = await (0, nodejs_proc_1.default)(`docker ps -a --filter "name=${containerName}" --format "{{.Names}},{{.State}}"`, this.procArgs);
66
+ const dockerPs = await (0, nodejs_proc_1.default)(`docker ps -a --filter "name=${containerName}" --format "{{.Names}},{{.State}}"`);
53
67
  return dockerPs
54
68
  .output
55
- .split('\n')[map](v => v.trim() || clearing_1.skip)[map](v => v[cut](',', 1))[map](([name, state]) => ({ name, state }))
69
+ .split('\n')[map](v => v.trim() || skip)[map](v => v[cut](',', 1))[map](([name, state]) => ({ name, state }))
56
70
  // Exclude containers which match the `docker ps` filter but don't have the prefix
57
- [map](v => (v.name === containerName || v.name[hasHead](`${containerName}-`)) ? v : clearing_1.skip);
71
+ [map](v => (v.name === containerName || v.name[hasHead](`${containerName}-`)) ? v : skip);
58
72
  }
59
- run(args) {
60
- return args.logger.scope('localStack', {}, async (logger) => {
73
+ run() {
74
+ return this.logger.scope('run', {}, async (logger) => {
61
75
  // Run a localStack container in docker, enabling `terraform apply` on an aws-like target
62
76
  const { image, port, containerName } = this.localStackDocker;
63
77
  const awsServices = this.getAwsServices();
64
78
  await logger.scope('dockerDeploy', { image, containerName, port }, async (logger) => {
65
- await (0, nodejs_proc_1.default)('docker info', this.procArgs).catch(({ output }) => Error('docker unavailable')[fire]({ output }));
79
+ await (0, nodejs_proc_1.default)('docker info').catch(({ output }) => Error('docker unavailable')[fire]({ output }));
66
80
  logger.log({ $$: 'dockerActive' });
67
81
  const containers = await this.getDockerContainers();
68
82
  let state = containers.find(c => c.name === containerName)?.state ?? 'nonexistent';
69
83
  // First if a container already exists ensure it's compatible with our given config
70
84
  if (['running', 'paused', 'exited'][has](state)) {
71
85
  const isExistingContainerReusable = await (async () => {
72
- const { output: inspectJson } = await (0, nodejs_proc_1.default)(`docker inspect ${containerName}`, this.procArgs);
86
+ const { output: inspectJson } = await (0, nodejs_proc_1.default)(`docker inspect ${containerName}`);
73
87
  const [containerInfo] = JSON.parse(inspectJson);
74
- console.log('DOCKER INSPECT', containerInfo);
88
+ logger.log({ $$: 'reusableCheck', containerInfo });
75
89
  const containerImage = containerInfo.Config.Image;
76
90
  const containerEnv = containerInfo.Config.Env[toObj](v => v[cut]('=', 1));
77
91
  const containerPort = Number(containerInfo.HostConfig.PortBindings[`${LocalStack.localStackInternalPort}/tcp`]?.[0]?.HostPort ?? 0);
@@ -84,9 +98,9 @@ var Soil;
84
98
  })();
85
99
  if (isExistingContainerReusable) {
86
100
  if (state === 'paused')
87
- await (0, nodejs_proc_1.default)(`docker unpause ${containerName}`, this.procArgs);
101
+ await (0, nodejs_proc_1.default)(`docker unpause ${containerName}`);
88
102
  if (state === 'exited')
89
- await (0, nodejs_proc_1.default)(`docker start ${containerName}`, this.procArgs);
103
+ await (0, nodejs_proc_1.default)(`docker start ${containerName}`);
90
104
  logger.log({ $$: 'containerReused' });
91
105
  state = 'running';
92
106
  }
@@ -108,8 +122,8 @@ var Soil;
108
122
  | -e SERVICES=${awsServices[toArr](v => v).join(',')}
109
123
  | -e DEFAULT_REGION=${this.aws.region}
110
124
  | ${image}
111
- `).split('\n')[map](ln => ln.trim() || clearing_1.skip).join(' ');
112
- await (0, nodejs_proc_1.default)(runCmd, this.procArgs);
125
+ `).split('\n')[map](ln => ln.trim() || skip).join(' ');
126
+ await (0, nodejs_proc_1.default)(runCmd);
113
127
  state = 'running';
114
128
  }
115
129
  if (state !== 'running')
@@ -132,29 +146,36 @@ var Soil;
132
146
  if (res.code !== 200)
133
147
  throw Error('unhealthy')[mod]({ retry: true });
134
148
  const { ya = [], no = [] } = res.body.services[group](v => v === 'available' ? 'ya' : 'no')[map](group => group[toArr]((v, k) => k));
135
- const missingServices = no[map](svc => awsServices.has(svc) ? svc : clearing_1.skip);
149
+ const missingServices = no[map](svc => awsServices.has(svc) ? svc : skip);
136
150
  if (missingServices.length)
137
151
  throw Error('services unavailable')[mod]({ missingServices })[mod]({ retry: true });
138
- return { services: ya };
152
+ return { res, services: ya };
139
153
  },
140
154
  retryable: err => !!err.retry,
141
155
  }).catch(err => err[fire]({ numErrs: err.errs.length, errs: null }));
142
- logger.log({ $$: 'localStackActive', services });
156
+ logger.log({ $$: 'result', services });
157
+ const netProc = { proto: 'http', addr: 'localhost', port };
143
158
  return {
144
- aws: { services: [...awsServices], region: this.aws.region, },
145
- netProc: { proto: 'http', addr: 'localhost', port },
146
- url: `http://localhost:${port}`
159
+ aws: { services: [...awsServices], region: this.aws.region },
160
+ netProc,
161
+ getApis: async () => {
162
+ const client = new client_api_gateway_1.APIGatewayClient({ region: this.aws.region, endpoint: `${netProc.proto}://${netProc.addr}:${netProc.port}` });
163
+ const apiRes = await client.send(new client_api_gateway_1.GetRestApisCommand({}));
164
+ const apis = (apiRes.items ?? []);
165
+ return apis[toObj](api => [api.name, api]);
166
+ }
147
167
  };
148
168
  });
149
169
  }
150
170
  async end(args) {
151
- const containers = args?.containers ?? await this.getDockerContainers();
152
- await (0, nodejs_proc_1.default)(`docker rm -f ${containers.map(c => c.name).join(' ')}`, this.procArgs)
153
- .catch(err => {
154
- console.log('ERROR ENDING LOCALSTACK DOCKER CONTAINER:\n', err.output);
155
- return;
171
+ return this.logger.scope('end', {}, async (logger) => {
172
+ const containers = args?.containers ?? await this.getDockerContainers();
173
+ await (0, nodejs_proc_1.default)(`docker rm -f ${containers.map(c => c.name).join(' ')}`).catch(err => {
174
+ logger.log({ $$: 'glitch', cmdOutput: err.output });
175
+ return;
176
+ });
177
+ return containers;
156
178
  });
157
- return containers;
158
179
  }
159
180
  async getTerraformPetals(ctx) {
160
181
  const { aws } = this;
@@ -1,19 +1,8 @@
1
+ import '@gershy/clearing';
1
2
  export declare const capitalKeys: (v: any) => any;
2
- export declare const regions: readonly [{
3
+ export declare const regions: {
3
4
  term: "ca-central-1" | "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2";
4
5
  mini: `${"ca" | "us"}${"c" | "u"}${"c" | "u" | "e" | "w"}${"1" | "2"}`;
5
- }, {
6
- term: "ca-central-1" | "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2";
7
- mini: `${"ca" | "us"}${"c" | "u"}${"c" | "u" | "e" | "w"}${"1" | "2"}`;
8
- }, {
9
- term: "ca-central-1" | "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2";
10
- mini: `${"ca" | "us"}${"c" | "u"}${"c" | "u" | "e" | "w"}${"1" | "2"}`;
11
- }, {
12
- term: "ca-central-1" | "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2";
13
- mini: `${"ca" | "us"}${"c" | "u"}${"c" | "u" | "e" | "w"}${"1" | "2"}`;
14
- }, {
15
- term: "ca-central-1" | "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2";
16
- mini: `${"ca" | "us"}${"c" | "u"}${"c" | "u" | "e" | "w"}${"1" | "2"}`;
17
- }];
6
+ }[];
18
7
  export type RegionTerm = (typeof regions)[number]['term'];
19
8
  export type RegionMini = (typeof regions)[number]['mini'];
@@ -4,13 +4,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.regions = exports.capitalKeys = void 0;
7
- const clearing_1 = require("@gershy/clearing");
7
+ require("@gershy/clearing");
8
8
  const util_phrasing_1 = __importDefault(require("@gershy/util-phrasing"));
9
9
  const capitalKeys = (v) => {
10
- if ((0, clearing_1.isCls)(v, Array))
11
- return v[map](v => (0, exports.capitalKeys)(v));
12
- if ((0, clearing_1.isCls)(v, Object))
13
- return v[mapk]((val, key) => [(0, util_phrasing_1.default)(key, 'camel', 'kamel'), (0, exports.capitalKeys)(val)]);
10
+ if (cl.isCls(v, Array))
11
+ return v[cl.map](v => (0, exports.capitalKeys)(v));
12
+ if (cl.isCls(v, Object))
13
+ return v[cl.mapk]((val, key) => [(0, util_phrasing_1.default)('camel->kamel', key), (0, exports.capitalKeys)(val)]);
14
14
  return v;
15
15
  };
16
16
  exports.capitalKeys = capitalKeys;
@@ -54,7 +54,7 @@ exports.regions = [
54
54
  // Ignoring these for now...
55
55
  // 'us-gov-east-1',
56
56
  // 'us-gov-west-1',
57
- ][map](region => {
57
+ ][cl.map](region => {
58
58
  const [c, z, num] = region.split('-');
59
59
  const [dir0, dir1 = dir0] = z.match(/central|north|south|east|west/g);
60
60
  return {
@@ -1,2 +1,3 @@
1
+ import '@gershy/clearing';
1
2
  declare const normalize: (val: any, seen?: Set<any>) => any;
2
3
  export default normalize;
@@ -1,13 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const clearing_1 = require("@gershy/clearing");
3
+ require("@gershy/clearing");
4
+ const { isCls, getClsName } = clearing;
4
5
  const normalize = (val, seen = new Set()) => {
5
6
  // Derives a json-stringifiable value from *any* value
6
7
  // E.g. to hash *anything*: hash(JSON.stringify(normalized(anything)));
7
8
  // Handles terminations
8
- if ((0, clearing_1.isCls)(val, String))
9
+ if (isCls(val, String))
9
10
  return val;
10
- if ((0, clearing_1.isCls)(val, Number))
11
+ if (isCls(val, Number))
11
12
  return val;
12
13
  if (val === null)
13
14
  return null;
@@ -16,10 +17,10 @@ const normalize = (val, seen = new Set()) => {
16
17
  if (seen.has(val))
17
18
  return '<<!circ!>>';
18
19
  seen.add(val);
19
- if ((0, clearing_1.isCls)(val, Array))
20
- return val[map](v => normalize(v, seen));
21
- if ((0, clearing_1.isCls)(val, Object))
20
+ if (isCls(val, Array))
21
+ return val[cl.map](v => normalize(v, seen));
22
+ if (isCls(val, Object))
22
23
  return normalize(Object.entries(val).sort((e0, e1) => e0[0] < e1[0] ? -1 : 1), seen);
23
- return normalize({ $form: (0, clearing_1.getClsName)(val), ...val }, seen);
24
+ return normalize({ $form: getClsName(val), ...val }, seen);
24
25
  };
25
26
  exports.default = normalize;
@@ -1,6 +1,8 @@
1
- import { Fact } from '@gershy/disk';
1
+ import type { Fact } from '@gershy/disk';
2
2
  import { ProcOpts } from '@gershy/nodejs-proc';
3
- export type ProcTerraformArgs = ProcOpts & {};
3
+ export type ProcTerraformArgs = ProcOpts & {
4
+ config?: string;
5
+ };
4
6
  declare const _default: (fact: Fact, cmd: string, opts?: ProcTerraformArgs) => Promise<{
5
7
  logDb: Fact;
6
8
  output: string;