@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.
- package/cmp/cjs/main.d.ts +4 -1
- package/cmp/cjs/main.js +64 -22
- 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 +11 -5
- package/cmp/cjs/soil/soil.js +50 -28
- 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 +3 -1
- 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 +4 -1
- package/cmp/mjs/main.js +62 -20
- 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 +11 -5
- package/cmp/mjs/soil/soil.js +46 -24
- 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 +3 -1
- 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
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
3
|
import type Logger from '@gershy/logger';
|
|
4
|
-
import type
|
|
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
|
|
31
|
-
|
|
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) ?
|
|
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,7 +149,7 @@ 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 =>
|
|
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)));
|
|
@@ -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)('
|
|
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
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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',
|
|
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,
|
|
223
|
-
|
|
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,
|
|
230
|
-
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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 (
|
|
26
|
+
if (isCls(val, String))
|
|
19
27
|
return val[hasHead]('| ') ? val.slice('| '.length) : `"${(0, slashEscape_ts_1.default)(val, '"\n')}"`;
|
|
20
|
-
if (
|
|
28
|
+
if (isCls(val, Number))
|
|
21
29
|
return `${val.toString(10)}`;
|
|
22
|
-
if (
|
|
30
|
+
if (isCls(val, Boolean))
|
|
23
31
|
return val ? 'true' : 'false';
|
|
24
|
-
if (
|
|
32
|
+
if (isCls(val, Array)) {
|
|
25
33
|
const vals = val[map](v => this.terraformEncode(v));
|
|
26
|
-
if (vals.some(v =>
|
|
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 (
|
|
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 && !
|
|
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 &&
|
|
58
|
-
return [(0, util_phrasing_1.default)(
|
|
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 &&
|
|
61
|
-
return [[(0, util_phrasing_1.default)(
|
|
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)(
|
|
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:
|
|
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 (!
|
|
91
|
+
if (!isCls(props, Array))
|
|
84
92
|
props = [props];
|
|
85
|
-
const base = `${(0, util_phrasing_1.default)(
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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)}`;
|
package/cmp/cjs/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
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/cjs/soil/soil.js
CHANGED
|
@@ -7,14 +7,31 @@ exports.Soil = void 0;
|
|
|
7
7
|
const nodejs_proc_1 = __importDefault(require("@gershy/nodejs-proc"));
|
|
8
8
|
const main_ts_1 = require("../main.js");
|
|
9
9
|
const util_retry_1 = __importDefault(require("@gershy/util-retry"));
|
|
10
|
-
|
|
10
|
+
require("@gershy/clearing");
|
|
11
11
|
const util_http_1 = __importDefault(require("@gershy/util-http"));
|
|
12
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)
|
|
13
28
|
var Soil;
|
|
14
29
|
(function (Soil) {
|
|
15
30
|
class Base {
|
|
31
|
+
logger;
|
|
16
32
|
registry;
|
|
17
33
|
constructor(args) {
|
|
34
|
+
this.logger = args.logger;
|
|
18
35
|
this.registry = args.registry;
|
|
19
36
|
}
|
|
20
37
|
async getTerraformPetals(ctx) {
|
|
@@ -27,16 +44,14 @@ var Soil;
|
|
|
27
44
|
static localStackInternalPort = 4566;
|
|
28
45
|
aws;
|
|
29
46
|
localStackDocker;
|
|
30
|
-
procArgs;
|
|
31
47
|
constructor(args) {
|
|
32
|
-
super(args);
|
|
48
|
+
super({ ...args, logger: args.logger.kid('localStack') });
|
|
33
49
|
this.aws = args.aws;
|
|
34
50
|
this.localStackDocker = {
|
|
35
51
|
image: 'localstack/localstack:latest',
|
|
36
52
|
port: LocalStack.localStackInternalPort,
|
|
37
53
|
containerName: 'gershyLilacLocalStack'
|
|
38
54
|
}[merge](args.localStackDocker ?? {});
|
|
39
|
-
this.procArgs = { env: process.env };
|
|
40
55
|
}
|
|
41
56
|
getAwsServices() {
|
|
42
57
|
// Note that "overhead" services are essential for initializing localstack:
|
|
@@ -48,29 +63,29 @@ var Soil;
|
|
|
48
63
|
}
|
|
49
64
|
async getDockerContainers() {
|
|
50
65
|
const { containerName } = this.localStackDocker;
|
|
51
|
-
const dockerPs = await (0, nodejs_proc_1.default)(`docker ps -a --filter "name=${containerName}" --format "{{.Names}},{{.State}}"
|
|
66
|
+
const dockerPs = await (0, nodejs_proc_1.default)(`docker ps -a --filter "name=${containerName}" --format "{{.Names}},{{.State}}"`);
|
|
52
67
|
return dockerPs
|
|
53
68
|
.output
|
|
54
|
-
.split('\n')[map](v => v.trim() ||
|
|
69
|
+
.split('\n')[map](v => v.trim() || skip)[map](v => v[cut](',', 1))[map](([name, state]) => ({ name, state }))
|
|
55
70
|
// Exclude containers which match the `docker ps` filter but don't have the prefix
|
|
56
|
-
[map](v => (v.name === containerName || v.name[hasHead](`${containerName}-`)) ? v :
|
|
71
|
+
[map](v => (v.name === containerName || v.name[hasHead](`${containerName}-`)) ? v : skip);
|
|
57
72
|
}
|
|
58
|
-
run(
|
|
59
|
-
return
|
|
73
|
+
run() {
|
|
74
|
+
return this.logger.scope('run', {}, async (logger) => {
|
|
60
75
|
// Run a localStack container in docker, enabling `terraform apply` on an aws-like target
|
|
61
76
|
const { image, port, containerName } = this.localStackDocker;
|
|
62
77
|
const awsServices = this.getAwsServices();
|
|
63
78
|
await logger.scope('dockerDeploy', { image, containerName, port }, async (logger) => {
|
|
64
|
-
await (0, nodejs_proc_1.default)('docker info'
|
|
79
|
+
await (0, nodejs_proc_1.default)('docker info').catch(({ output }) => Error('docker unavailable')[fire]({ output }));
|
|
65
80
|
logger.log({ $$: 'dockerActive' });
|
|
66
81
|
const containers = await this.getDockerContainers();
|
|
67
82
|
let state = containers.find(c => c.name === containerName)?.state ?? 'nonexistent';
|
|
68
83
|
// First if a container already exists ensure it's compatible with our given config
|
|
69
84
|
if (['running', 'paused', 'exited'][has](state)) {
|
|
70
85
|
const isExistingContainerReusable = await (async () => {
|
|
71
|
-
const { output: inspectJson } = await (0, nodejs_proc_1.default)(`docker inspect ${containerName}
|
|
86
|
+
const { output: inspectJson } = await (0, nodejs_proc_1.default)(`docker inspect ${containerName}`);
|
|
72
87
|
const [containerInfo] = JSON.parse(inspectJson);
|
|
73
|
-
|
|
88
|
+
logger.log({ $$: 'reusableCheck', containerInfo });
|
|
74
89
|
const containerImage = containerInfo.Config.Image;
|
|
75
90
|
const containerEnv = containerInfo.Config.Env[toObj](v => v[cut]('=', 1));
|
|
76
91
|
const containerPort = Number(containerInfo.HostConfig.PortBindings[`${LocalStack.localStackInternalPort}/tcp`]?.[0]?.HostPort ?? 0);
|
|
@@ -83,9 +98,9 @@ var Soil;
|
|
|
83
98
|
})();
|
|
84
99
|
if (isExistingContainerReusable) {
|
|
85
100
|
if (state === 'paused')
|
|
86
|
-
await (0, nodejs_proc_1.default)(`docker unpause ${containerName}
|
|
101
|
+
await (0, nodejs_proc_1.default)(`docker unpause ${containerName}`);
|
|
87
102
|
if (state === 'exited')
|
|
88
|
-
await (0, nodejs_proc_1.default)(`docker start ${containerName}
|
|
103
|
+
await (0, nodejs_proc_1.default)(`docker start ${containerName}`);
|
|
89
104
|
logger.log({ $$: 'containerReused' });
|
|
90
105
|
state = 'running';
|
|
91
106
|
}
|
|
@@ -107,8 +122,8 @@ var Soil;
|
|
|
107
122
|
| -e SERVICES=${awsServices[toArr](v => v).join(',')}
|
|
108
123
|
| -e DEFAULT_REGION=${this.aws.region}
|
|
109
124
|
| ${image}
|
|
110
|
-
`).split('\n')[map](ln => ln.trim() ||
|
|
111
|
-
await (0, nodejs_proc_1.default)(runCmd
|
|
125
|
+
`).split('\n')[map](ln => ln.trim() || skip).join(' ');
|
|
126
|
+
await (0, nodejs_proc_1.default)(runCmd);
|
|
112
127
|
state = 'running';
|
|
113
128
|
}
|
|
114
129
|
if (state !== 'running')
|
|
@@ -131,29 +146,36 @@ var Soil;
|
|
|
131
146
|
if (res.code !== 200)
|
|
132
147
|
throw Error('unhealthy')[mod]({ retry: true });
|
|
133
148
|
const { ya = [], no = [] } = res.body.services[group](v => v === 'available' ? 'ya' : 'no')[map](group => group[toArr]((v, k) => k));
|
|
134
|
-
const missingServices = no[map](svc => awsServices.has(svc) ? svc :
|
|
149
|
+
const missingServices = no[map](svc => awsServices.has(svc) ? svc : skip);
|
|
135
150
|
if (missingServices.length)
|
|
136
151
|
throw Error('services unavailable')[mod]({ missingServices })[mod]({ retry: true });
|
|
137
|
-
return { services: ya };
|
|
152
|
+
return { res, services: ya };
|
|
138
153
|
},
|
|
139
154
|
retryable: err => !!err.retry,
|
|
140
155
|
}).catch(err => err[fire]({ numErrs: err.errs.length, errs: null }));
|
|
141
|
-
logger.log({ $$: '
|
|
156
|
+
logger.log({ $$: 'result', services });
|
|
157
|
+
const netProc = { proto: 'http', addr: 'localhost', port };
|
|
142
158
|
return {
|
|
143
|
-
aws: { services: [...awsServices], region: this.aws.region
|
|
144
|
-
netProc
|
|
145
|
-
|
|
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
|
+
}
|
|
146
167
|
};
|
|
147
168
|
});
|
|
148
169
|
}
|
|
149
170
|
async end(args) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
.catch(err => {
|
|
153
|
-
|
|
154
|
-
|
|
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;
|
|
155
178
|
});
|
|
156
|
-
return containers;
|
|
157
179
|
}
|
|
158
180
|
async getTerraformPetals(ctx) {
|
|
159
181
|
const { aws } = this;
|
package/cmp/cjs/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/cjs/util/aws.js
CHANGED
|
@@ -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
|
-
|
|
7
|
+
require("@gershy/clearing");
|
|
8
8
|
const util_phrasing_1 = __importDefault(require("@gershy/util-phrasing"));
|
|
9
9
|
const capitalKeys = (v) => {
|
|
10
|
-
if (
|
|
11
|
-
return v[map](v => (0, exports.capitalKeys)(v));
|
|
12
|
-
if (
|
|
13
|
-
return v[mapk]((val, key) => [(0, util_phrasing_1.default)(
|
|
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,13 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
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 (
|
|
9
|
+
if (isCls(val, String))
|
|
9
10
|
return val;
|
|
10
|
-
if (
|
|
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 (
|
|
20
|
-
return val[map](v => normalize(v, seen));
|
|
21
|
-
if (
|
|
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:
|
|
24
|
+
return normalize({ $form: getClsName(val), ...val }, seen);
|
|
24
25
|
};
|
|
25
26
|
exports.default = normalize;
|
|
@@ -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;
|