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