@gershy/lilac 0.0.14 → 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.
@@ -17,14 +17,19 @@ exports.default = (fact, cmd, opts = {}) => {
17
17
  timeoutMs: 0,
18
18
  ...opts,
19
19
  cwd: fact,
20
- env: { TF_DATA_DIR: '' }
20
+ env: {
21
+ ...process.env,
22
+ TF_DATA_DIR: '',
23
+ TF_CLI_CONFIG_FILE: '',
24
+ ...opts.env
25
+ }
21
26
  });
22
27
  return Object.assign(prm.then(async (result) => {
23
28
  const logDb = await writeLog(result.output);
24
29
  return { logDb, output: result.output.split('\n').slice(-numTailingTfLogLines).join('\n') };
25
30
  }, async (err) => {
26
- const logDb = await writeLog(err.output ?? err[limn]());
27
- throw Error(`terraform failed (${err.message})`)[mod]({
31
+ const logDb = await writeLog(err.output ?? err[cl.limn]());
32
+ throw Error(`terraform failed (${err.message})`)[cl.mod]({
28
33
  logDb,
29
34
  ...(err.output ? { output: err.output.split('\n').slice(-numTailingTfLogLines).join('\n') } : { cause: err })
30
35
  });
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = (str, escapeChars) => {
4
- if (!escapeChars[has]('\\'))
4
+ if (!escapeChars[cl.has]('\\'))
5
5
  escapeChars += '\\';
6
6
  // The regex to construct must have "\" and "]" escaped:
7
7
  const escChars = escapeChars.replace(/([\\\]])/g, '\\$1');
@@ -6,7 +6,7 @@ const embed = (v) => '${' + v + '}';
6
6
  exports.embed = embed;
7
7
  const json = (v) => [
8
8
  '| <<EOF',
9
- JSON.stringify(v, null, 2)[indent](2),
9
+ JSON.stringify(v, null, 2)[cl.indent](2),
10
10
  'EOF'
11
11
  ].join('\n');
12
12
  exports.json = json;
package/cmp/mjs/main.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import '../sideEffects.js';
2
2
  import { PetalTerraform } from './petal/terraform/terraform.ts';
3
3
  import type Logger from '@gershy/logger';
4
- import type { Fact } from '@gershy/disk';
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/mjs/main.js CHANGED
@@ -2,15 +2,23 @@
2
2
  // A more generic (beyond just tf) provider is very hard to support due to the multiplicity of
3
3
  // provider/petal combos - e.g. "api" flower would need to support ,api.getCloudformationPetals,
4
4
  // api.getTerraformPetals, etc... supporting just terraform for now
5
- // Watch out when working lilac into an npm dependency, need to allow the user a way to declare
6
- // their absolute repo path (so "<repo>/..." filenames work in any setup!)
7
- // Can region be dealt with any better??
8
5
  // Support test-mode (Flowers need to be able to do setup, share config, write to volumes, etc)
9
6
  import { PetalTerraform } from "./petal/terraform/terraform.js";
10
- import { isCls, skip } from '@gershy/clearing';
11
- import procTerraform from "./util/procTerraform.js";
7
+ import { tempFact } from '@gershy/disk';
8
+ import '@gershy/clearing';
12
9
  import tryWithHealing from '@gershy/util-try-with-healing';
13
10
  import phrasing from '@gershy/util-phrasing';
11
+ import proc from '@gershy/nodejs-proc';
12
+ const { isCls, skip } = cl;
13
+ const toArr = cl.toArr;
14
+ const allObj = cl.allObj;
15
+ const has = cl.has;
16
+ const map = cl.map;
17
+ const mod = cl.mod;
18
+ const walk = cl.walk;
19
+ const merge = cl.merge;
20
+ const upper = cl.upper;
21
+ const baseline = cl.baseline;
14
22
  export class Flower {
15
23
  // TODO: The downside of having this static is that different instances may use different
16
24
  // services - e.g. api gateway instance may have "useEdge: true", in which case we'd like to
@@ -40,7 +48,7 @@ export class Registry {
40
48
  }
41
49
  getAwsServices() {
42
50
  const services = new Set();
43
- for (const [k, { real }] of this.flowers)
51
+ for (const [k, { real }] of this.flowers[walk]())
44
52
  for (const awsService of real.getAwsServices())
45
53
  services.add(awsService);
46
54
  return services[toArr](v => v);
@@ -58,11 +66,21 @@ export class Garden {
58
66
  ctx;
59
67
  reg;
60
68
  def;
69
+ tfProcArgs;
61
70
  constructor(args) {
62
71
  const { define, registry, context } = args;
63
72
  this.ctx = context;
64
73
  this.reg = registry;
65
74
  this.def = define;
75
+ this.tfProcArgs = {
76
+ timeoutMs: 0,
77
+ env: {
78
+ ...process.env,
79
+ TF_LOG: 'DEBUG',
80
+ TF_DATA_DIR: '',
81
+ TF_CLI_CONFIG_FILE: ''
82
+ }
83
+ };
66
84
  }
67
85
  async *getPetals() {
68
86
  // TODO: We always use the "real" flowers from the registry - this is part of the shift to
@@ -154,7 +172,7 @@ export class Garden {
154
172
  // Create ddb tf state locking table
155
173
  await writePetalTfAndFiles(new PetalTerraform.Resource('awsDynamodbTable', 'tfState', {
156
174
  name: ddbName,
157
- billingMode: phrasing('payPerRequest', 'camel', 'snake')[upper](),
175
+ billingMode: phrasing('camel->snake', 'payPerRequest')[upper](),
158
176
  hashKey: 'LockID',
159
177
  $attribute: { name: 'LockID', type: 'S' }
160
178
  }));
@@ -181,31 +199,55 @@ export class Garden {
181
199
  });
182
200
  });
183
201
  }
184
- terraformInit(fact, args) {
202
+ // TODO: Write terraform output to logs??
203
+ async terraformInit(fact) {
204
+ // Consider if we ever want to pass "-reconfigure" and "-migrate-state" options; these are
205
+ // useful if we are moving backends (e.g. one aws account to another), and want to move our
206
+ // full iac definition too
207
+ // Ensure the mirror directory exists in the shed
208
+ const mirrorFact = this.ctx.shedFact.kid(['lilacTerraformMirror']);
209
+ await mirrorFact.kid(['note.txt']).setData(`Root of terraform mirror for @gershy/lilac`);
185
210
  return this.ctx.logger.scope('execTf.init', { fact: fact.fsp() }, async (logger) => {
186
- // Consider if we ever want to pass "-reconfigure" and "-migrate-state" options; these are
187
- // useful if we are moving backends (e.g. one aws account to another), and want to move our
188
- // full iac definition too
189
- // TODO: Some terraform commands fail when offline - can this be covered up? Possibly by
190
- // checking terraform binaries into the repo? (Cross-platform nightmare though...)
191
- const result = await procTerraform(fact, `terraform init -input=false`, {
192
- onData: async (mode, data) => logger.log({ $$: 'notice', mode, data }) ?? null
211
+ const { output: result } = await tryWithHealing({
212
+ fn: () => logger.scope('attempt', {}, async (logger) => {
213
+ const configFact = tempFact.kid([`${Math.random().toString(36).slice(2)}.terraform.rc`]);
214
+ await configFact.setData(String[baseline](`
215
+ | provider_installation {
216
+ | filesystem_mirror {
217
+ | path = "${mirrorFact.fsp().replaceAll('\\', '/')}"
218
+ | }
219
+ | }
220
+ `));
221
+ return proc(`terraform init -input=false`, {}[merge](this.tfProcArgs)[merge]({
222
+ cwd: fact,
223
+ env: { TF_CLI_CONFIG_FILE: configFact.fsp() }
224
+ })).finally(() => configFact.rem());
225
+ }),
226
+ canHeal: err => (err?.output ?? '')[has]('Could not retrieve the list of available versions for provider'),
227
+ heal: () => logger.scope('mirror', { fsp: mirrorFact.fsp() }, async (logger) => {
228
+ const { output: result } = await proc(`terraform providers mirror "${mirrorFact.fsp().replaceAll('\\', '/')}"`, { cwd: fact, timeoutMs: 0 });
229
+ logger.log({ $$: 'result', result });
230
+ })
193
231
  });
194
- logger.log({ $$: 'result', logFp: result.logDb.toString(), msg: result.output });
232
+ logger.log({ $$: 'result', result });
195
233
  return result;
196
234
  });
197
235
  }
198
236
  terraformPlan(fact, args) {
199
237
  return this.ctx.logger.scope('execTf.plan', { fact: fact.fsp() }, async (logger) => {
200
- const result = await procTerraform(fact, `terraform plan -input=false`);
201
- logger.log({ $$: 'result', logFp: result.logDb.toString(), msg: result.output });
238
+ const { output: result } = await proc(`terraform plan -input=false`, {}[merge](this.tfProcArgs)[merge]({
239
+ cwd: fact,
240
+ }));
241
+ logger.log({ $$: 'result', result });
202
242
  return result;
203
243
  });
204
244
  }
205
245
  terraformApply(fact, args) {
206
246
  return this.ctx.logger.scope('execTf.apply', { fact: fact.fsp() }, async (logger) => {
207
- const result = await procTerraform(fact, `terraform apply -input=false -auto-approve`);
208
- logger.log({ $$: 'result', logFp: result.logDb.toString(), msg: result.output });
247
+ const { output: result } = await proc(`terraform apply -input=false -auto-approve`, {}[merge](this.tfProcArgs)[merge]({
248
+ cwd: fact
249
+ }));
250
+ logger.log({ $$: 'result', result });
209
251
  return result;
210
252
  });
211
253
  }
@@ -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 {
@@ -1,7 +1,15 @@
1
- import { getClsName, isCls } from '@gershy/clearing';
1
+ import '@gershy/clearing';
2
2
  import slashEscape from "../../util/slashEscape.js";
3
3
  import Petal from "../petal.js";
4
4
  import ph from '@gershy/util-phrasing';
5
+ const { getClsName, isCls } = cl;
6
+ const hasHead = cl.hasHead;
7
+ const map = cl.map;
8
+ const hasTail = cl.hasTail;
9
+ const indent = cl.indent;
10
+ const mod = cl.mod;
11
+ const toArr = cl.toArr;
12
+ const has = cl.has;
5
13
  export var PetalTerraform;
6
14
  (function (PetalTerraform) {
7
15
  class Base extends Petal {
@@ -49,13 +57,13 @@ export var PetalTerraform;
49
57
  throw Error('tf key of this form must correspond to object value')[mod]({ k, v });
50
58
  // Resolve to raw string?
51
59
  if (special && isCls(v, String))
52
- return [ph(pcs[0], 'camel', 'snake'), ' = ', this.terraformEncode(v[hasHead]('| ') ? v : `| ${v}`)];
60
+ return [ph('camel->snake', pcs[0]), ' = ', this.terraformEncode(v[hasHead]('| ') ? v : `| ${v}`)];
53
61
  // Resolve to nested block?
54
62
  if (special && isCls(v, Object))
55
- return [[ph(pcs[0], 'camel', 'snake'), ...pcs.slice(1)[map](pc => ph(pc, 'camel', 'snake'))].join(' '), ' ', this.terraformEncode(v)];
63
+ return [[ph('camel->snake', pcs[0]), ...pcs.slice(1)[map](pc => ph('camel->snake', pc))].join(' '), ' ', this.terraformEncode(v)];
56
64
  // Resolve anything else to typical property - use the key exactly as provided (to support,
57
65
  // e.g., aws format for keys in policies, any other specific format, etc.)
58
- return [ph(pcs[0], 'camel', 'snake'), ' = ', this.terraformEncode(v)];
66
+ return [ph('camel->snake', pcs[0]), ' = ', this.terraformEncode(v)];
59
67
  });
60
68
  const len = entryItems.length;
61
69
  if (len === 0)
@@ -76,9 +84,9 @@ export var PetalTerraform;
76
84
  refStr(props = []) {
77
85
  if (!isCls(props, Array))
78
86
  props = [props];
79
- const base = `${ph(this.getType(), 'camel', 'snake')}.${ph(this.getHandle(), 'camel', 'snake')}`;
87
+ const base = `${ph('camel->snake', this.getType())}.${ph('camel->snake', this.getHandle())}`;
80
88
  return props.length
81
- ? `${base}.${props[map](v => ph(v, 'camel', 'snake')).join('.')}`
89
+ ? `${base}.${props[map](v => ph('camel->snake', v)).join('.')}`
82
90
  : base;
83
91
  }
84
92
  ref(props = []) {
@@ -124,7 +132,7 @@ export var PetalTerraform;
124
132
  getHandle() { return this.handle; }
125
133
  getProps() { return this.props; }
126
134
  async getResultHeader() {
127
- return `resource "${ph(this.type, 'camel', 'snake')}" "${ph(this.handle, 'camel', 'snake')}"`;
135
+ return `resource "${ph('camel->snake', this.type)}" "${ph('camel->snake', this.handle)}"`;
128
136
  }
129
137
  }
130
138
  PetalTerraform.Resource = Resource;
@@ -139,7 +147,7 @@ export var PetalTerraform;
139
147
  }
140
148
  getProps() { return this.props; }
141
149
  async getResultHeader() {
142
- return `provider "${ph(this.name, 'camel', 'snake')}"`;
150
+ return `provider "${ph('camel->snake', this.name)}"`;
143
151
  }
144
152
  }
145
153
  PetalTerraform.Provider = Provider;
@@ -158,7 +166,7 @@ export var PetalTerraform;
158
166
  getHandle() { return this.handle; }
159
167
  getProps() { return this.props; }
160
168
  async getResultHeader() {
161
- return `data "${ph(this.type, 'camel', 'snake')}" "${ph(this.handle, 'camel', 'snake')}"`;
169
+ return `data "${ph('camel->snake', this.type)}" "${ph('camel->snake', this.handle)}"`;
162
170
  }
163
171
  refStr(props = []) {
164
172
  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
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']>>;
@@ -1,14 +1,31 @@
1
1
  import proc from '@gershy/nodejs-proc';
2
2
  import { PetalTerraform } from "../main.js";
3
3
  import retry from '@gershy/util-retry';
4
- import { skip } from '@gershy/clearing';
4
+ import '@gershy/clearing';
5
5
  import http from '@gershy/util-http';
6
6
  import { regions as awsRegions } from "../util/aws.js";
7
+ import { APIGatewayClient, GetRestApisCommand } from '@aws-sdk/client-api-gateway';
8
+ const { skip } = clearing;
9
+ const merge = cl.merge;
10
+ const map = cl.map;
11
+ const cut = cl.cut;
12
+ const fire = cl.fire;
13
+ const hasHead = cl.hasHead;
14
+ const has = cl.has;
15
+ const toObj = cl.toObj;
16
+ const toArr = cl.toArr;
17
+ const baseline = cl.baseline;
18
+ const mod = cl.mod;
19
+ const group = cl.group;
20
+ // TODO: Consider splitting each Soil implementation into its own unit
21
+ // (Soil.LocalStack unit can, e.g., isolate the `@aws-sdk/client-api-gateway` dependency)
7
22
  export var Soil;
8
23
  (function (Soil) {
9
24
  class Base {
25
+ logger;
10
26
  registry;
11
27
  constructor(args) {
28
+ this.logger = args.logger;
12
29
  this.registry = args.registry;
13
30
  }
14
31
  async getTerraformPetals(ctx) {
@@ -21,16 +38,14 @@ export var Soil;
21
38
  static localStackInternalPort = 4566;
22
39
  aws;
23
40
  localStackDocker;
24
- procArgs;
25
41
  constructor(args) {
26
- super(args);
42
+ super({ ...args, logger: args.logger.kid('localStack') });
27
43
  this.aws = args.aws;
28
44
  this.localStackDocker = {
29
45
  image: 'localstack/localstack:latest',
30
46
  port: LocalStack.localStackInternalPort,
31
47
  containerName: 'gershyLilacLocalStack'
32
48
  }[merge](args.localStackDocker ?? {});
33
- this.procArgs = { env: process.env };
34
49
  }
35
50
  getAwsServices() {
36
51
  // Note that "overhead" services are essential for initializing localstack:
@@ -42,29 +57,29 @@ export var Soil;
42
57
  }
43
58
  async getDockerContainers() {
44
59
  const { containerName } = this.localStackDocker;
45
- const dockerPs = await proc(`docker ps -a --filter "name=${containerName}" --format "{{.Names}},{{.State}}"`, this.procArgs);
60
+ const dockerPs = await proc(`docker ps -a --filter "name=${containerName}" --format "{{.Names}},{{.State}}"`);
46
61
  return dockerPs
47
62
  .output
48
63
  .split('\n')[map](v => v.trim() || skip)[map](v => v[cut](',', 1))[map](([name, state]) => ({ name, state }))
49
64
  // Exclude containers which match the `docker ps` filter but don't have the prefix
50
65
  [map](v => (v.name === containerName || v.name[hasHead](`${containerName}-`)) ? v : skip);
51
66
  }
52
- run(args) {
53
- return args.logger.scope('localStack', {}, async (logger) => {
67
+ run() {
68
+ return this.logger.scope('run', {}, async (logger) => {
54
69
  // Run a localStack container in docker, enabling `terraform apply` on an aws-like target
55
70
  const { image, port, containerName } = this.localStackDocker;
56
71
  const awsServices = this.getAwsServices();
57
72
  await logger.scope('dockerDeploy', { image, containerName, port }, async (logger) => {
58
- await proc('docker info', this.procArgs).catch(({ output }) => Error('docker unavailable')[fire]({ output }));
73
+ await proc('docker info').catch(({ output }) => Error('docker unavailable')[fire]({ output }));
59
74
  logger.log({ $$: 'dockerActive' });
60
75
  const containers = await this.getDockerContainers();
61
76
  let state = containers.find(c => c.name === containerName)?.state ?? 'nonexistent';
62
77
  // First if a container already exists ensure it's compatible with our given config
63
78
  if (['running', 'paused', 'exited'][has](state)) {
64
79
  const isExistingContainerReusable = await (async () => {
65
- const { output: inspectJson } = await proc(`docker inspect ${containerName}`, this.procArgs);
80
+ const { output: inspectJson } = await proc(`docker inspect ${containerName}`);
66
81
  const [containerInfo] = JSON.parse(inspectJson);
67
- console.log('DOCKER INSPECT', containerInfo);
82
+ logger.log({ $$: 'reusableCheck', containerInfo });
68
83
  const containerImage = containerInfo.Config.Image;
69
84
  const containerEnv = containerInfo.Config.Env[toObj](v => v[cut]('=', 1));
70
85
  const containerPort = Number(containerInfo.HostConfig.PortBindings[`${LocalStack.localStackInternalPort}/tcp`]?.[0]?.HostPort ?? 0);
@@ -77,9 +92,9 @@ export var Soil;
77
92
  })();
78
93
  if (isExistingContainerReusable) {
79
94
  if (state === 'paused')
80
- await proc(`docker unpause ${containerName}`, this.procArgs);
95
+ await proc(`docker unpause ${containerName}`);
81
96
  if (state === 'exited')
82
- await proc(`docker start ${containerName}`, this.procArgs);
97
+ await proc(`docker start ${containerName}`);
83
98
  logger.log({ $$: 'containerReused' });
84
99
  state = 'running';
85
100
  }
@@ -102,7 +117,7 @@ export var Soil;
102
117
  | -e DEFAULT_REGION=${this.aws.region}
103
118
  | ${image}
104
119
  `).split('\n')[map](ln => ln.trim() || skip).join(' ');
105
- await proc(runCmd, this.procArgs);
120
+ await proc(runCmd);
106
121
  state = 'running';
107
122
  }
108
123
  if (state !== 'running')
@@ -128,26 +143,33 @@ export var Soil;
128
143
  const missingServices = no[map](svc => awsServices.has(svc) ? svc : skip);
129
144
  if (missingServices.length)
130
145
  throw Error('services unavailable')[mod]({ missingServices })[mod]({ retry: true });
131
- return { services: ya };
146
+ return { res, services: ya };
132
147
  },
133
148
  retryable: err => !!err.retry,
134
149
  }).catch(err => err[fire]({ numErrs: err.errs.length, errs: null }));
135
- logger.log({ $$: 'localStackActive', services });
150
+ logger.log({ $$: 'result', services });
151
+ const netProc = { proto: 'http', addr: 'localhost', port };
136
152
  return {
137
- aws: { services: [...awsServices], region: this.aws.region, },
138
- netProc: { proto: 'http', addr: 'localhost', port },
139
- url: `http://localhost:${port}`
153
+ aws: { services: [...awsServices], region: this.aws.region },
154
+ netProc,
155
+ getApis: async () => {
156
+ const client = new APIGatewayClient({ region: this.aws.region, endpoint: `${netProc.proto}://${netProc.addr}:${netProc.port}` });
157
+ const apiRes = await client.send(new GetRestApisCommand({}));
158
+ const apis = (apiRes.items ?? []);
159
+ return apis[toObj](api => [api.name, api]);
160
+ }
140
161
  };
141
162
  });
142
163
  }
143
164
  async end(args) {
144
- const containers = args?.containers ?? await this.getDockerContainers();
145
- await proc(`docker rm -f ${containers.map(c => c.name).join(' ')}`, this.procArgs)
146
- .catch(err => {
147
- console.log('ERROR ENDING LOCALSTACK DOCKER CONTAINER:\n', err.output);
148
- return;
165
+ return this.logger.scope('end', {}, async (logger) => {
166
+ const containers = args?.containers ?? await this.getDockerContainers();
167
+ await proc(`docker rm -f ${containers.map(c => c.name).join(' ')}`).catch(err => {
168
+ logger.log({ $$: 'glitch', cmdOutput: err.output });
169
+ return;
170
+ });
171
+ return containers;
149
172
  });
150
- return containers;
151
173
  }
152
174
  async getTerraformPetals(ctx) {
153
175
  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'];
@@ -1,10 +1,10 @@
1
- import { isCls } from '@gershy/clearing';
1
+ import '@gershy/clearing';
2
2
  import ph from '@gershy/util-phrasing';
3
3
  export const capitalKeys = (v) => {
4
- if (isCls(v, Array))
5
- return v[map](v => capitalKeys(v));
6
- if (isCls(v, Object))
7
- return v[mapk]((val, key) => [ph(key, 'camel', 'kamel'), capitalKeys(val)]);
4
+ if (cl.isCls(v, Array))
5
+ return v[cl.map](v => capitalKeys(v));
6
+ if (cl.isCls(v, Object))
7
+ return v[cl.mapk]((val, key) => [ph('camel->kamel', key), capitalKeys(val)]);
8
8
  return v;
9
9
  };
10
10
  export const regions = [
@@ -47,7 +47,7 @@ export const regions = [
47
47
  // Ignoring these for now...
48
48
  // 'us-gov-east-1',
49
49
  // 'us-gov-west-1',
50
- ][map](region => {
50
+ ][cl.map](region => {
51
51
  const [c, z, num] = region.split('-');
52
52
  const [dir0, dir1 = dir0] = z.match(/central|north|south|east|west/g);
53
53
  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,4 +1,5 @@
1
- import { isCls, getClsName } from '@gershy/clearing';
1
+ import '@gershy/clearing';
2
+ const { isCls, getClsName } = clearing;
2
3
  const normalize = (val, seen = new Set()) => {
3
4
  // Derives a json-stringifiable value from *any* value
4
5
  // E.g. to hash *anything*: hash(JSON.stringify(normalized(anything)));
@@ -15,7 +16,7 @@ const normalize = (val, seen = new Set()) => {
15
16
  return '<<!circ!>>';
16
17
  seen.add(val);
17
18
  if (isCls(val, Array))
18
- return val[map](v => normalize(v, seen));
19
+ return val[cl.map](v => normalize(v, seen));
19
20
  if (isCls(val, Object))
20
21
  return normalize(Object.entries(val).sort((e0, e1) => e0[0] < e1[0] ? -1 : 1), seen);
21
22
  return normalize({ $form: getClsName(val), ...val }, seen);
@@ -1,6 +1,8 @@
1
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;
@@ -12,14 +12,19 @@ export default (fact, cmd, opts = {}) => {
12
12
  timeoutMs: 0,
13
13
  ...opts,
14
14
  cwd: fact,
15
- env: { TF_DATA_DIR: '' }
15
+ env: {
16
+ ...process.env,
17
+ TF_DATA_DIR: '',
18
+ TF_CLI_CONFIG_FILE: '',
19
+ ...opts.env
20
+ }
16
21
  });
17
22
  return Object.assign(prm.then(async (result) => {
18
23
  const logDb = await writeLog(result.output);
19
24
  return { logDb, output: result.output.split('\n').slice(-numTailingTfLogLines).join('\n') };
20
25
  }, async (err) => {
21
- const logDb = await writeLog(err.output ?? err[limn]());
22
- throw Error(`terraform failed (${err.message})`)[mod]({
26
+ const logDb = await writeLog(err.output ?? err[cl.limn]());
27
+ throw Error(`terraform failed (${err.message})`)[cl.mod]({
23
28
  logDb,
24
29
  ...(err.output ? { output: err.output.split('\n').slice(-numTailingTfLogLines).join('\n') } : { cause: err })
25
30
  });
@@ -1,5 +1,5 @@
1
1
  export default (str, escapeChars) => {
2
- if (!escapeChars[has]('\\'))
2
+ if (!escapeChars[cl.has]('\\'))
3
3
  escapeChars += '\\';
4
4
  // The regex to construct must have "\" and "]" escaped:
5
5
  const escChars = escapeChars.replace(/([\\\]])/g, '\\$1');
@@ -2,6 +2,6 @@
2
2
  export const embed = (v) => '${' + v + '}';
3
3
  export const json = (v) => [
4
4
  '| <<EOF',
5
- JSON.stringify(v, null, 2)[indent](2),
5
+ JSON.stringify(v, null, 2)[cl.indent](2),
6
6
  'EOF'
7
7
  ].join('\n');