@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 +5 -2
- package/cmp/cjs/main.js +65 -23
- package/cmp/cjs/petal/terraform/terraform.d.ts +1 -0
- package/cmp/cjs/petal/terraform/terraform.js +28 -20
- package/cmp/cjs/soil/soil.d.ts +12 -6
- package/cmp/cjs/soil/soil.js +50 -29
- package/cmp/cjs/util/aws.d.ts +3 -14
- package/cmp/cjs/util/aws.js +6 -6
- package/cmp/cjs/util/normalize.d.ts +1 -0
- package/cmp/cjs/util/normalize.js +8 -7
- package/cmp/cjs/util/procTerraform.d.ts +4 -2
- package/cmp/cjs/util/procTerraform.js +8 -3
- package/cmp/cjs/util/slashEscape.js +1 -1
- package/cmp/cjs/util/terraform.js +1 -1
- package/cmp/mjs/main.d.ts +5 -2
- package/cmp/mjs/main.js +63 -21
- package/cmp/mjs/petal/terraform/terraform.d.ts +1 -0
- package/cmp/mjs/petal/terraform/terraform.js +17 -9
- package/cmp/mjs/soil/soil.d.ts +12 -6
- package/cmp/mjs/soil/soil.js +46 -25
- package/cmp/mjs/util/aws.d.ts +3 -14
- package/cmp/mjs/util/aws.js +6 -6
- package/cmp/mjs/util/normalize.d.ts +1 -0
- package/cmp/mjs/util/normalize.js +3 -2
- package/cmp/mjs/util/procTerraform.d.ts +4 -2
- package/cmp/mjs/util/procTerraform.js +8 -3
- package/cmp/mjs/util/slashEscape.js +1 -1
- package/cmp/mjs/util/terraform.js +1 -1
- package/package.json +11 -10
- package/readme.md +11 -6
|
@@ -17,14 +17,19 @@ exports.default = (fact, cmd, opts = {}) => {
|
|
|
17
17
|
timeoutMs: 0,
|
|
18
18
|
...opts,
|
|
19
19
|
cwd: fact,
|
|
20
|
-
env: {
|
|
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');
|
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
|
-
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/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 {
|
|
11
|
-
import
|
|
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
|
|
@@ -115,7 +133,7 @@ export class Garden {
|
|
|
115
133
|
await Promise.all(files[toArr]((data, kfp) => args.fact.kid(kfp.split('/')).setData(data)));
|
|
116
134
|
return petal;
|
|
117
135
|
});
|
|
118
|
-
await stream.end();
|
|
136
|
+
await stream.end();
|
|
119
137
|
});
|
|
120
138
|
return args.fact;
|
|
121
139
|
});
|
|
@@ -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('
|
|
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
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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',
|
|
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
|
|
201
|
-
|
|
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
|
|
208
|
-
|
|
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,7 +1,15 @@
|
|
|
1
|
-
import
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
87
|
+
const base = `${ph('camel->snake', this.getType())}.${ph('camel->snake', this.getHandle())}`;
|
|
80
88
|
return props.length
|
|
81
|
-
? `${base}.${props[map](v => ph(
|
|
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(
|
|
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(
|
|
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(
|
|
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)}`;
|
package/cmp/mjs/soil/soil.d.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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']>>;
|
package/cmp/mjs/soil/soil.js
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
|
-
import { rootFact } from '@gershy/disk';
|
|
2
1
|
import proc from '@gershy/nodejs-proc';
|
|
3
2
|
import { PetalTerraform } from "../main.js";
|
|
4
3
|
import retry from '@gershy/util-retry';
|
|
5
|
-
import
|
|
4
|
+
import '@gershy/clearing';
|
|
6
5
|
import http from '@gershy/util-http';
|
|
7
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)
|
|
8
22
|
export var Soil;
|
|
9
23
|
(function (Soil) {
|
|
10
24
|
class Base {
|
|
25
|
+
logger;
|
|
11
26
|
registry;
|
|
12
27
|
constructor(args) {
|
|
28
|
+
this.logger = args.logger;
|
|
13
29
|
this.registry = args.registry;
|
|
14
30
|
}
|
|
15
31
|
async getTerraformPetals(ctx) {
|
|
@@ -22,16 +38,14 @@ export var Soil;
|
|
|
22
38
|
static localStackInternalPort = 4566;
|
|
23
39
|
aws;
|
|
24
40
|
localStackDocker;
|
|
25
|
-
procArgs;
|
|
26
41
|
constructor(args) {
|
|
27
|
-
super(args);
|
|
42
|
+
super({ ...args, logger: args.logger.kid('localStack') });
|
|
28
43
|
this.aws = args.aws;
|
|
29
44
|
this.localStackDocker = {
|
|
30
45
|
image: 'localstack/localstack:latest',
|
|
31
46
|
port: LocalStack.localStackInternalPort,
|
|
32
47
|
containerName: 'gershyLilacLocalStack'
|
|
33
48
|
}[merge](args.localStackDocker ?? {});
|
|
34
|
-
this.procArgs = { cwd: rootFact, env: process.env };
|
|
35
49
|
}
|
|
36
50
|
getAwsServices() {
|
|
37
51
|
// Note that "overhead" services are essential for initializing localstack:
|
|
@@ -43,29 +57,29 @@ export var Soil;
|
|
|
43
57
|
}
|
|
44
58
|
async getDockerContainers() {
|
|
45
59
|
const { containerName } = this.localStackDocker;
|
|
46
|
-
const dockerPs = await proc(`docker ps -a --filter "name=${containerName}" --format "{{.Names}},{{.State}}"
|
|
60
|
+
const dockerPs = await proc(`docker ps -a --filter "name=${containerName}" --format "{{.Names}},{{.State}}"`);
|
|
47
61
|
return dockerPs
|
|
48
62
|
.output
|
|
49
63
|
.split('\n')[map](v => v.trim() || skip)[map](v => v[cut](',', 1))[map](([name, state]) => ({ name, state }))
|
|
50
64
|
// Exclude containers which match the `docker ps` filter but don't have the prefix
|
|
51
65
|
[map](v => (v.name === containerName || v.name[hasHead](`${containerName}-`)) ? v : skip);
|
|
52
66
|
}
|
|
53
|
-
run(
|
|
54
|
-
return
|
|
67
|
+
run() {
|
|
68
|
+
return this.logger.scope('run', {}, async (logger) => {
|
|
55
69
|
// Run a localStack container in docker, enabling `terraform apply` on an aws-like target
|
|
56
70
|
const { image, port, containerName } = this.localStackDocker;
|
|
57
71
|
const awsServices = this.getAwsServices();
|
|
58
72
|
await logger.scope('dockerDeploy', { image, containerName, port }, async (logger) => {
|
|
59
|
-
await proc('docker info'
|
|
73
|
+
await proc('docker info').catch(({ output }) => Error('docker unavailable')[fire]({ output }));
|
|
60
74
|
logger.log({ $$: 'dockerActive' });
|
|
61
75
|
const containers = await this.getDockerContainers();
|
|
62
76
|
let state = containers.find(c => c.name === containerName)?.state ?? 'nonexistent';
|
|
63
77
|
// First if a container already exists ensure it's compatible with our given config
|
|
64
78
|
if (['running', 'paused', 'exited'][has](state)) {
|
|
65
79
|
const isExistingContainerReusable = await (async () => {
|
|
66
|
-
const { output: inspectJson } = await proc(`docker inspect ${containerName}
|
|
80
|
+
const { output: inspectJson } = await proc(`docker inspect ${containerName}`);
|
|
67
81
|
const [containerInfo] = JSON.parse(inspectJson);
|
|
68
|
-
|
|
82
|
+
logger.log({ $$: 'reusableCheck', containerInfo });
|
|
69
83
|
const containerImage = containerInfo.Config.Image;
|
|
70
84
|
const containerEnv = containerInfo.Config.Env[toObj](v => v[cut]('=', 1));
|
|
71
85
|
const containerPort = Number(containerInfo.HostConfig.PortBindings[`${LocalStack.localStackInternalPort}/tcp`]?.[0]?.HostPort ?? 0);
|
|
@@ -78,9 +92,9 @@ export var Soil;
|
|
|
78
92
|
})();
|
|
79
93
|
if (isExistingContainerReusable) {
|
|
80
94
|
if (state === 'paused')
|
|
81
|
-
await proc(`docker unpause ${containerName}
|
|
95
|
+
await proc(`docker unpause ${containerName}`);
|
|
82
96
|
if (state === 'exited')
|
|
83
|
-
await proc(`docker start ${containerName}
|
|
97
|
+
await proc(`docker start ${containerName}`);
|
|
84
98
|
logger.log({ $$: 'containerReused' });
|
|
85
99
|
state = 'running';
|
|
86
100
|
}
|
|
@@ -103,7 +117,7 @@ export var Soil;
|
|
|
103
117
|
| -e DEFAULT_REGION=${this.aws.region}
|
|
104
118
|
| ${image}
|
|
105
119
|
`).split('\n')[map](ln => ln.trim() || skip).join(' ');
|
|
106
|
-
await proc(runCmd
|
|
120
|
+
await proc(runCmd);
|
|
107
121
|
state = 'running';
|
|
108
122
|
}
|
|
109
123
|
if (state !== 'running')
|
|
@@ -129,26 +143,33 @@ export var Soil;
|
|
|
129
143
|
const missingServices = no[map](svc => awsServices.has(svc) ? svc : skip);
|
|
130
144
|
if (missingServices.length)
|
|
131
145
|
throw Error('services unavailable')[mod]({ missingServices })[mod]({ retry: true });
|
|
132
|
-
return { services: ya };
|
|
146
|
+
return { res, services: ya };
|
|
133
147
|
},
|
|
134
148
|
retryable: err => !!err.retry,
|
|
135
149
|
}).catch(err => err[fire]({ numErrs: err.errs.length, errs: null }));
|
|
136
|
-
logger.log({ $$: '
|
|
150
|
+
logger.log({ $$: 'result', services });
|
|
151
|
+
const netProc = { proto: 'http', addr: 'localhost', port };
|
|
137
152
|
return {
|
|
138
|
-
aws: { services: [...awsServices], region: this.aws.region
|
|
139
|
-
netProc
|
|
140
|
-
|
|
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
|
+
}
|
|
141
161
|
};
|
|
142
162
|
});
|
|
143
163
|
}
|
|
144
164
|
async end(args) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
.catch(err => {
|
|
148
|
-
|
|
149
|
-
|
|
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;
|
|
150
172
|
});
|
|
151
|
-
return containers;
|
|
152
173
|
}
|
|
153
174
|
async getTerraformPetals(ctx) {
|
|
154
175
|
const { aws } = this;
|
package/cmp/mjs/util/aws.d.ts
CHANGED
|
@@ -1,19 +1,8 @@
|
|
|
1
|
+
import '@gershy/clearing';
|
|
1
2
|
export declare const capitalKeys: (v: any) => any;
|
|
2
|
-
export declare const regions:
|
|
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'];
|
package/cmp/mjs/util/aws.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
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(
|
|
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,4 +1,5 @@
|
|
|
1
|
-
import
|
|
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
|
-
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;
|
|
@@ -12,14 +12,19 @@ export default (fact, cmd, opts = {}) => {
|
|
|
12
12
|
timeoutMs: 0,
|
|
13
13
|
...opts,
|
|
14
14
|
cwd: fact,
|
|
15
|
-
env: {
|
|
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
|
});
|