@goldstack/utils-terraform 0.4.0 → 0.4.3

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.
@@ -1,489 +1,489 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.TerraformBuild = exports.getVariablesFromHCL = exports.parseVariables = exports.getVariablesFromProperties = exports.convertFromPythonVariable = exports.convertToPythonVariable = void 0;
7
- const prompt_sync_1 = __importDefault(require("prompt-sync"));
8
- const utils_log_1 = require("@goldstack/utils-log");
9
- const terraformCli_1 = require("./terraformCli");
10
- const utils_sh_1 = require("@goldstack/utils-sh");
11
- const utils_package_1 = require("@goldstack/utils-package");
12
- const assert_1 = __importDefault(require("assert"));
13
- const fs_1 = __importDefault(require("fs"));
14
- const crypto_1 = __importDefault(require("crypto"));
15
- const infra_1 = require("@goldstack/infra");
16
- const json_stable_stringify_1 = __importDefault(require("json-stable-stringify"));
17
- const path_1 = __importDefault(require("path"));
18
- const convertToPythonVariable = (variableName) => {
19
- let res = '';
20
- for (const char of variableName) {
21
- if (char.toLowerCase() === char) {
22
- res += char;
23
- }
24
- else {
25
- res += '_' + char.toLowerCase();
26
- }
27
- }
28
- return res;
29
- };
30
- exports.convertToPythonVariable = convertToPythonVariable;
31
- const convertFromPythonVariable = (variableName) => {
32
- let res = '';
33
- for (let i = 0; i < variableName.length; i++) {
34
- const char = variableName.charAt(i);
35
- if (i > 0 && variableName.charAt(i - 1) === '_') {
36
- res += variableName.charAt(i).toUpperCase();
37
- }
38
- else if (variableName.charAt(i) !== '_') {
39
- res += char;
40
- }
41
- }
42
- return res;
43
- };
44
- exports.convertFromPythonVariable = convertFromPythonVariable;
45
- const getVariablesFromProperties = (properties, terraformVariables) => {
46
- const vars = [];
47
- if (!terraformVariables) {
48
- return vars;
49
- }
50
- for (const key in properties) {
51
- if (terraformVariables.find((varName) => varName === key)) {
52
- const variableName = (0, exports.convertToPythonVariable)(key);
53
- const variableValue = properties[key];
54
- if (variableValue !== '') {
55
- vars.push([variableName, variableValue]);
56
- }
57
- else {
58
- vars.push([
59
- variableName,
60
- process.env[variableName.toLocaleUpperCase()] || '',
61
- ]);
62
- }
63
- }
64
- }
65
- return vars;
66
- };
67
- exports.getVariablesFromProperties = getVariablesFromProperties;
68
- const parseVariables = (hcl) => {
69
- const reg = /variable[^"]*"([^"]*)"[^{]*{/gm;
70
- let result;
71
- const variableNames = [];
72
- while ((result = reg.exec(hcl)) !== null) {
73
- variableNames.push(result[1]);
74
- }
75
- return variableNames;
76
- };
77
- exports.parseVariables = parseVariables;
78
- const getVariablesFromHCL = (properties) => {
79
- if (!fs_1.default.existsSync('./variables.tf')) {
80
- console.warn(`No variables.tf file exists in ${(0, utils_sh_1.pwd)()}. Goldstack only supports declaring variables in a variables.tf file.`);
81
- return [];
82
- }
83
- const variablesHCL = (0, utils_sh_1.read)('./variables.tf');
84
- const hclVariableNames = (0, exports.parseVariables)(variablesHCL);
85
- const jsVariableNames = hclVariableNames.map((hclVarName) => (0, exports.convertFromPythonVariable)(hclVarName));
86
- jsVariableNames.forEach((key) => {
87
- if (!properties.hasOwnProperty(key)) {
88
- console.warn(`Cannot find property "${key}" in Goldstack configuration. Therefore terraform variable ${(0, exports.convertToPythonVariable)(key)} will not be provided by Goldstack.`);
89
- }
90
- });
91
- const vars = [];
92
- for (const key in properties) {
93
- if (key.indexOf('_') !== -1) {
94
- console.warn('Property in Goldstack configuration contains "_". This is not recommended. Property: ' +
95
- key +
96
- ' Please use valid JavaScript variable names. For instance, use "myVar" instead of "my_var". ' +
97
- ' Goldstack will automatically convert these to Terraform variables ("myVar" -> "my_var") when required.');
98
- }
99
- if (jsVariableNames.find((varName) => varName === key)) {
100
- const variableName = (0, exports.convertToPythonVariable)(key);
101
- const variableValue = properties[key];
102
- if (variableValue !== '') {
103
- if (typeof variableValue === 'string') {
104
- vars.push([variableName, variableValue]);
105
- }
106
- else if (typeof variableValue === 'number') {
107
- vars.push([variableName, `${variableValue}`]);
108
- }
109
- else if (typeof variableValue === 'object') {
110
- vars.push([variableName, `${(0, json_stable_stringify_1.default)(variableValue)}`]);
111
- }
112
- else {
113
- throw new Error(`Not supported type for variable ${variableName}: ${typeof variableValue}`);
114
- }
115
- }
116
- else {
117
- const environmentVarialbeName = variableName.toLocaleUpperCase();
118
- if (process.env[environmentVarialbeName] ||
119
- process.env[environmentVarialbeName] === '') {
120
- console.log('Setting terraform variable from environment variable');
121
- vars.push([variableName, process.env[environmentVarialbeName] || '']);
122
- }
123
- else {
124
- console.log('Terraform variable will not be defined', variableName);
125
- }
126
- }
127
- }
128
- }
129
- return vars;
130
- };
131
- exports.getVariablesFromHCL = getVariablesFromHCL;
132
- const getDeployment = (args) => {
133
- if (args.length < 1) {
134
- throw new Error('Please specify the name of the deployment.');
135
- }
136
- const name = args[0];
137
- const packageConfig = (0, utils_package_1.readPackageConfig)();
138
- const deployment = packageConfig.deployments.find((deployment) => deployment.name === name);
139
- if (!deployment) {
140
- (0, utils_log_1.fatal)(`Cannot find configuration for deployment '${name}''`);
141
- throw new Error('Cannot parse configuration.');
142
- }
143
- return deployment;
144
- };
145
- class TerraformBuild {
146
- constructor(provider) {
147
- this.getTfStateVariables = (deployment) => {
148
- const packageConfig = (0, utils_package_1.readPackageConfig)();
149
- const deployments = packageConfig.deployments.filter((d) => d.name === deployment.name);
150
- if (deployments.length !== 1) {
151
- throw new Error(`Cannot find deployment ${deployment.name}`);
152
- }
153
- const deploymentConfig = deployments[0];
154
- // initialise id for key if required
155
- if (!deploymentConfig.tfStateKey) {
156
- const stateHash = crypto_1.default.randomBytes(10).toString('hex');
157
- const stateKey = `${packageConfig.name}-${deployment.name}-${stateHash}.tfstate`;
158
- console.log(`Intialising Terraform State key for ${deployment.name} to ${stateKey}`);
159
- deploymentConfig.tfStateKey = stateKey;
160
- (0, utils_package_1.writePackageConfig)(packageConfig);
161
- }
162
- return this.provider.getTfStateVariables(deploymentConfig);
163
- };
164
- this.getTfVersion = (args) => {
165
- if (args.length > 0) {
166
- const deployment = getDeployment(args);
167
- if (deployment.tfVersion) {
168
- return deployment.tfVersion;
169
- }
170
- }
171
- if (fs_1.default.existsSync('./infra/tfConfig.json')) {
172
- try {
173
- const tsConfig = JSON.parse((0, utils_sh_1.read)('./infra/tfConfig.json'));
174
- return tsConfig.tfVersion;
175
- }
176
- catch (e) {
177
- throw new Error('Invalid Terraform configuration in ' +
178
- path_1.default.resolve('./infra/tfConfig.json'));
179
- }
180
- }
181
- // before Terraform versions were introduced, only version 0.12 was supported
182
- return '0.12';
183
- };
184
- this.init = (args) => {
185
- const deployment = getDeployment(args);
186
- const version = this.getTfVersion(args);
187
- const backendConfig = this.getTfStateVariables(deployment);
188
- (0, utils_sh_1.cd)('./infra/aws');
189
- try {
190
- const provider = this.provider;
191
- let workspace = undefined;
192
- // supplying workspace does not work for first initialisation
193
- // https://discuss.hashicorp.com/t/terraform-init-ignores-input-false-option/16065
194
- // doesn't seem to be an issue in Terraform 1.1
195
- if (fs_1.default.existsSync('./.terraform') && version !== '0.12') {
196
- workspace = deployment.name;
197
- }
198
- (0, terraformCli_1.tf)('init', {
199
- provider,
200
- version,
201
- backendConfig,
202
- workspace: workspace,
203
- options: ['-force-copy', '-reconfigure'],
204
- });
205
- const workspaces = (0, terraformCli_1.tf)('workspace list', {
206
- provider,
207
- version,
208
- silent: true,
209
- });
210
- const deploymentName = args[0];
211
- let workspaceExists = workspaces.split('\n').find((line) => {
212
- return line.indexOf(deploymentName) >= 0;
213
- });
214
- if (!workspaceExists) {
215
- workspaceExists = workspaces.split('\n').find((line) => {
216
- return line.indexOf(deploymentName) >= 0;
217
- });
218
- if (!workspaceExists) {
219
- (0, terraformCli_1.tf)(`workspace new ${deploymentName}`, {
220
- provider,
221
- version,
222
- });
223
- }
224
- (0, terraformCli_1.tf)(`workspace select ${deploymentName}`, {
225
- provider,
226
- version,
227
- });
228
- }
229
- else {
230
- (0, terraformCli_1.tf)(`workspace select ${deploymentName}`, {
231
- provider,
232
- version,
233
- });
234
- }
235
- }
236
- finally {
237
- (0, utils_sh_1.cd)('../..');
238
- }
239
- };
240
- this.ensureInCorrectWorkspace = (params) => {
241
- const currentWorkspace = (0, terraformCli_1.tf)('workspace show', {
242
- provider: params.provider,
243
- version: params.version,
244
- silent: true,
245
- }).trim();
246
- if (currentWorkspace !== params.deploymentName) {
247
- (0, terraformCli_1.tf)(`workspace select ${params.deploymentName}`, {
248
- provider: params.provider,
249
- version: params.version,
250
- silent: true,
251
- });
252
- // init with reconfigure required here in case we are switching to a different
253
- // s3 bucket in a different environment for a different deployment
254
- (0, terraformCli_1.tf)('init', {
255
- provider: params.provider,
256
- backendConfig: params.backendConfig,
257
- version: params.version,
258
- workspace: params.deploymentName,
259
- options: ['-reconfigure'],
260
- });
261
- }
262
- };
263
- this.plan = (args) => {
264
- const deployment = getDeployment(args);
265
- const version = this.getTfVersion(args);
266
- const backendConfig = this.getTfStateVariables(deployment);
267
- (0, utils_sh_1.cd)('./infra/aws');
268
- try {
269
- const provider = this.provider;
270
- this.ensureInCorrectWorkspace({
271
- deploymentName: args[0],
272
- provider,
273
- version,
274
- backendConfig,
275
- });
276
- const variables = [
277
- ...(0, exports.getVariablesFromHCL)({ ...deployment, ...deployment.configuration }),
278
- ];
279
- (0, terraformCli_1.tf)('plan', {
280
- provider,
281
- variables,
282
- version,
283
- options: ['-input=false', '-out tfplan'],
284
- });
285
- }
286
- finally {
287
- (0, utils_sh_1.cd)('../..');
288
- }
289
- };
290
- this.apply = (args, options) => {
291
- const deployment = getDeployment(args);
292
- const version = this.getTfVersion(args);
293
- const backendConfig = this.getTfStateVariables(deployment);
294
- (0, utils_sh_1.cd)('./infra/aws');
295
- try {
296
- const provider = this.provider;
297
- const deploymentName = args[0];
298
- this.ensureInCorrectWorkspace({
299
- deploymentName: args[0],
300
- provider,
301
- version,
302
- backendConfig,
303
- });
304
- let cliOptions = ['-input=false', 'tfplan'];
305
- if (options === null || options === void 0 ? void 0 : options.parallelism) {
306
- cliOptions = [`-parallelism=${options.parallelism}`, ...cliOptions];
307
- }
308
- (0, terraformCli_1.tf)('apply', {
309
- provider,
310
- options: cliOptions,
311
- version,
312
- });
313
- const res = (0, terraformCli_1.tf)('output', {
314
- provider,
315
- options: ['-json'],
316
- version,
317
- }).trim();
318
- const deploymentState = (0, infra_1.readDeploymentState)('./../../', deploymentName, {
319
- createIfNotExist: true,
320
- });
321
- deploymentState.terraform = JSON.parse(res);
322
- (0, infra_1.writeDeploymentState)('./../../', deploymentState);
323
- }
324
- finally {
325
- (0, utils_sh_1.cd)('../..');
326
- }
327
- };
328
- this.destroy = (args) => {
329
- const deployment = getDeployment(args);
330
- const version = this.getTfVersion(args);
331
- const backendConfig = this.getTfStateVariables(deployment);
332
- (0, utils_sh_1.cd)('./infra/aws');
333
- try {
334
- const ciConfirmed = args.find((str) => str === '-y');
335
- if (!ciConfirmed) {
336
- const value = (0, prompt_sync_1.default)()('Are you sure to destroy your deployed resources? If yes, please type `y` and enter.\n' +
337
- 'Otherwise, press enter.\nYour Input: ');
338
- if (value !== 'y') {
339
- (0, utils_log_1.fatal)('Prompt not confirmed with `y`');
340
- }
341
- }
342
- const provider = this.provider;
343
- const variables = [
344
- ...(0, exports.getVariablesFromHCL)({ ...deployment, ...deployment.configuration }),
345
- ];
346
- (0, terraformCli_1.tf)(`workspace select ${args[0]}`, { provider, version });
347
- (0, terraformCli_1.tf)('init', {
348
- provider,
349
- backendConfig,
350
- options: ['-reconfigure'],
351
- version,
352
- });
353
- (0, terraformCli_1.tf)('plan', {
354
- provider,
355
- variables,
356
- version,
357
- workspace: args[0],
358
- options: ['-input=false', '-out tfplan'],
359
- });
360
- (0, terraformCli_1.tf)('destroy', {
361
- provider,
362
- variables,
363
- version,
364
- options: ['-input=false', '-auto-approve'],
365
- });
366
- }
367
- finally {
368
- (0, utils_sh_1.cd)('../..');
369
- }
370
- };
371
- this.performUpgrade = (deploymentName, targetVersion) => {
372
- const provider = this.provider;
373
- const version = this.getTfVersion([deploymentName]);
374
- (0, utils_sh_1.cd)('./infra/aws');
375
- const currentWorkspace = (0, terraformCli_1.tf)('workspace show', {
376
- provider: provider,
377
- version: version,
378
- silent: true,
379
- }).trim();
380
- if (currentWorkspace !== deploymentName) {
381
- const workspaces = (0, terraformCli_1.tf)('workspace list', {
382
- provider,
383
- version,
384
- silent: true,
385
- });
386
- const workspaceExists = workspaces.split('\n').find((line) => {
387
- return line.indexOf(deploymentName) >= 0;
388
- });
389
- if (workspaceExists) {
390
- (0, terraformCli_1.tf)(`workspace select ${deploymentName}`, {
391
- provider,
392
- version,
393
- });
394
- }
395
- else {
396
- // Sometimes it seems that Terraform forgets/destroys a workspace when upgrading the version of
397
- // another deployment.
398
- throw new Error(`Please initialise the deployment to be upgraded first with 'yarn infra init ${deploymentName}'. Please note that the 'init' command may fail with the error 'Error: Invalid legacy provider address' (if you have upgraded another deployment before this). In that case, run the 'upgrade' command regardless after the 'init' command has completed.`);
399
- }
400
- }
401
- if (targetVersion === '0.13') {
402
- const upgradeRes = (0, terraformCli_1.tf)(`${targetVersion}upgrade`, {
403
- version: targetVersion,
404
- provider,
405
- options: ['-yes'],
406
- });
407
- if (upgradeRes.indexOf('Upgrade complete!') === -1) {
408
- throw new Error('Upgrade of Terraform version not successful.');
409
- }
410
- }
411
- (0, utils_sh_1.cd)('../..');
412
- const packageConfig = (0, utils_package_1.readPackageConfig)();
413
- const deploymentInConfig = packageConfig.deployments.find((e) => e.name === deploymentName);
414
- (0, assert_1.default)(deploymentInConfig);
415
- deploymentInConfig.tfVersion = targetVersion;
416
- (0, utils_package_1.writePackageConfig)(packageConfig);
417
- this.init([deploymentName]);
418
- console.log(`Version upgraded to ${targetVersion}. Please run deployment to upgrade remote state before further upgrades.`);
419
- };
420
- this.upgrade = (args) => {
421
- const deployment = getDeployment(args);
422
- const version = deployment.tfVersion || '0.12';
423
- const newVersion = args[1];
424
- if (version === newVersion) {
425
- console.log('Already on version', newVersion);
426
- return;
427
- }
428
- if (version === '0.12' && newVersion === '0.13') {
429
- this.performUpgrade(args[0], '0.13');
430
- return;
431
- }
432
- if (version === '0.13' && newVersion === '0.14') {
433
- this.performUpgrade(args[0], '0.14');
434
- return;
435
- }
436
- if (version === '0.14' && newVersion === '0.15') {
437
- this.performUpgrade(args[0], '0.15');
438
- return;
439
- }
440
- if (version === '0.15' && newVersion === '1.0') {
441
- this.performUpgrade(args[0], '1.0');
442
- return;
443
- }
444
- if (version === '1.0' && newVersion === '1.1') {
445
- this.performUpgrade(args[0], '1.1');
446
- return;
447
- }
448
- throw new Error(`Version upgrade not supported: from [${version}] to [${newVersion}]. Currently only 0.12 -> 0.13 is supported.`);
449
- };
450
- (0, assert_1.default)(provider);
451
- this.provider = provider;
452
- }
453
- terraform(opArgs) {
454
- const provider = this.provider;
455
- const deployment = getDeployment(opArgs);
456
- const version = this.getTfVersion(opArgs);
457
- let backendConfig;
458
- if (opArgs.find((arg) => arg === '--inject-backend-config')) {
459
- backendConfig = this.getTfStateVariables(deployment);
460
- }
461
- let variables;
462
- (0, utils_sh_1.cd)('./infra/aws');
463
- try {
464
- if (opArgs.find((arg) => arg === '--inject-variables')) {
465
- variables = [
466
- ...(0, exports.getVariablesFromHCL)({
467
- ...deployment,
468
- ...deployment.configuration,
469
- }),
470
- ];
471
- }
472
- (0, terraformCli_1.tf)(`workspace select ${opArgs[0]}`, { provider, version });
473
- const remainingArgs = opArgs
474
- .slice(1)
475
- .filter((arg) => arg !== '--inject-backend-config' && arg !== '--inject-variables');
476
- (0, terraformCli_1.tf)(remainingArgs.join(' '), {
477
- provider,
478
- backendConfig,
479
- version,
480
- variables,
481
- });
482
- }
483
- finally {
484
- (0, utils_sh_1.cd)('../..');
485
- }
486
- }
487
- }
488
- exports.TerraformBuild = TerraformBuild;
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TerraformBuild = exports.getVariablesFromHCL = exports.parseVariables = exports.getVariablesFromProperties = exports.convertFromPythonVariable = exports.convertToPythonVariable = void 0;
7
+ const prompt_sync_1 = __importDefault(require("prompt-sync"));
8
+ const utils_log_1 = require("@goldstack/utils-log");
9
+ const terraformCli_1 = require("./terraformCli");
10
+ const utils_sh_1 = require("@goldstack/utils-sh");
11
+ const utils_package_1 = require("@goldstack/utils-package");
12
+ const assert_1 = __importDefault(require("assert"));
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const crypto_1 = __importDefault(require("crypto"));
15
+ const infra_1 = require("@goldstack/infra");
16
+ const json_stable_stringify_1 = __importDefault(require("json-stable-stringify"));
17
+ const path_1 = __importDefault(require("path"));
18
+ const convertToPythonVariable = (variableName) => {
19
+ let res = '';
20
+ for (const char of variableName) {
21
+ if (char.toLowerCase() === char) {
22
+ res += char;
23
+ }
24
+ else {
25
+ res += '_' + char.toLowerCase();
26
+ }
27
+ }
28
+ return res;
29
+ };
30
+ exports.convertToPythonVariable = convertToPythonVariable;
31
+ const convertFromPythonVariable = (variableName) => {
32
+ let res = '';
33
+ for (let i = 0; i < variableName.length; i++) {
34
+ const char = variableName.charAt(i);
35
+ if (i > 0 && variableName.charAt(i - 1) === '_') {
36
+ res += variableName.charAt(i).toUpperCase();
37
+ }
38
+ else if (variableName.charAt(i) !== '_') {
39
+ res += char;
40
+ }
41
+ }
42
+ return res;
43
+ };
44
+ exports.convertFromPythonVariable = convertFromPythonVariable;
45
+ const getVariablesFromProperties = (properties, terraformVariables) => {
46
+ const vars = [];
47
+ if (!terraformVariables) {
48
+ return vars;
49
+ }
50
+ for (const key in properties) {
51
+ if (terraformVariables.find((varName) => varName === key)) {
52
+ const variableName = (0, exports.convertToPythonVariable)(key);
53
+ const variableValue = properties[key];
54
+ if (variableValue !== '') {
55
+ vars.push([variableName, variableValue]);
56
+ }
57
+ else {
58
+ vars.push([
59
+ variableName,
60
+ process.env[variableName.toLocaleUpperCase()] || '',
61
+ ]);
62
+ }
63
+ }
64
+ }
65
+ return vars;
66
+ };
67
+ exports.getVariablesFromProperties = getVariablesFromProperties;
68
+ const parseVariables = (hcl) => {
69
+ const reg = /variable[^"]*"([^"]*)"[^{]*{/gm;
70
+ let result;
71
+ const variableNames = [];
72
+ while ((result = reg.exec(hcl)) !== null) {
73
+ variableNames.push(result[1]);
74
+ }
75
+ return variableNames;
76
+ };
77
+ exports.parseVariables = parseVariables;
78
+ const getVariablesFromHCL = (properties) => {
79
+ if (!fs_1.default.existsSync('./variables.tf')) {
80
+ console.warn(`No variables.tf file exists in ${(0, utils_sh_1.pwd)()}. Goldstack only supports declaring variables in a variables.tf file.`);
81
+ return [];
82
+ }
83
+ const variablesHCL = (0, utils_sh_1.read)('./variables.tf');
84
+ const hclVariableNames = (0, exports.parseVariables)(variablesHCL);
85
+ const jsVariableNames = hclVariableNames.map((hclVarName) => (0, exports.convertFromPythonVariable)(hclVarName));
86
+ jsVariableNames.forEach((key) => {
87
+ if (!properties.hasOwnProperty(key)) {
88
+ console.warn(`Cannot find property "${key}" in Goldstack configuration. Therefore terraform variable ${(0, exports.convertToPythonVariable)(key)} will not be provided by Goldstack.`);
89
+ }
90
+ });
91
+ const vars = [];
92
+ for (const key in properties) {
93
+ if (key.indexOf('_') !== -1) {
94
+ console.warn('Property in Goldstack configuration contains "_". This is not recommended. Property: ' +
95
+ key +
96
+ ' Please use valid JavaScript variable names. For instance, use "myVar" instead of "my_var". ' +
97
+ ' Goldstack will automatically convert these to Terraform variables ("myVar" -> "my_var") when required.');
98
+ }
99
+ if (jsVariableNames.find((varName) => varName === key)) {
100
+ const variableName = (0, exports.convertToPythonVariable)(key);
101
+ const variableValue = properties[key];
102
+ if (variableValue !== '') {
103
+ if (typeof variableValue === 'string') {
104
+ vars.push([variableName, variableValue]);
105
+ }
106
+ else if (typeof variableValue === 'number') {
107
+ vars.push([variableName, `${variableValue}`]);
108
+ }
109
+ else if (typeof variableValue === 'object') {
110
+ vars.push([variableName, `${(0, json_stable_stringify_1.default)(variableValue)}`]);
111
+ }
112
+ else {
113
+ throw new Error(`Not supported type for variable ${variableName}: ${typeof variableValue}`);
114
+ }
115
+ }
116
+ else {
117
+ const environmentVarialbeName = variableName.toLocaleUpperCase();
118
+ if (process.env[environmentVarialbeName] ||
119
+ process.env[environmentVarialbeName] === '') {
120
+ console.log('Setting terraform variable from environment variable');
121
+ vars.push([variableName, process.env[environmentVarialbeName] || '']);
122
+ }
123
+ else {
124
+ console.log('Terraform variable will not be defined', variableName);
125
+ }
126
+ }
127
+ }
128
+ }
129
+ return vars;
130
+ };
131
+ exports.getVariablesFromHCL = getVariablesFromHCL;
132
+ const getDeployment = (args) => {
133
+ if (args.length < 1) {
134
+ throw new Error('Please specify the name of the deployment.');
135
+ }
136
+ const name = args[0];
137
+ const packageConfig = (0, utils_package_1.readPackageConfig)();
138
+ const deployment = packageConfig.deployments.find((deployment) => deployment.name === name);
139
+ if (!deployment) {
140
+ (0, utils_log_1.fatal)(`Cannot find configuration for deployment '${name}''`);
141
+ throw new Error('Cannot parse configuration.');
142
+ }
143
+ return deployment;
144
+ };
145
+ class TerraformBuild {
146
+ constructor(provider) {
147
+ this.getTfStateVariables = (deployment) => {
148
+ const packageConfig = (0, utils_package_1.readPackageConfig)();
149
+ const deployments = packageConfig.deployments.filter((d) => d.name === deployment.name);
150
+ if (deployments.length !== 1) {
151
+ throw new Error(`Cannot find deployment ${deployment.name}`);
152
+ }
153
+ const deploymentConfig = deployments[0];
154
+ // initialise id for key if required
155
+ if (!deploymentConfig.tfStateKey) {
156
+ const stateHash = crypto_1.default.randomBytes(10).toString('hex');
157
+ const stateKey = `${packageConfig.name}-${deployment.name}-${stateHash}.tfstate`;
158
+ console.log(`Intialising Terraform State key for ${deployment.name} to ${stateKey}`);
159
+ deploymentConfig.tfStateKey = stateKey;
160
+ (0, utils_package_1.writePackageConfig)(packageConfig);
161
+ }
162
+ return this.provider.getTfStateVariables(deploymentConfig);
163
+ };
164
+ this.getTfVersion = (args) => {
165
+ if (args.length > 0) {
166
+ const deployment = getDeployment(args);
167
+ if (deployment.tfVersion) {
168
+ return deployment.tfVersion;
169
+ }
170
+ }
171
+ if (fs_1.default.existsSync('./infra/tfConfig.json')) {
172
+ try {
173
+ const tsConfig = JSON.parse((0, utils_sh_1.read)('./infra/tfConfig.json'));
174
+ return tsConfig.tfVersion;
175
+ }
176
+ catch (e) {
177
+ throw new Error('Invalid Terraform configuration in ' +
178
+ path_1.default.resolve('./infra/tfConfig.json'));
179
+ }
180
+ }
181
+ // before Terraform versions were introduced, only version 0.12 was supported
182
+ return '0.12';
183
+ };
184
+ this.init = (args) => {
185
+ const deployment = getDeployment(args);
186
+ const version = this.getTfVersion(args);
187
+ const backendConfig = this.getTfStateVariables(deployment);
188
+ (0, utils_sh_1.cd)('./infra/aws');
189
+ try {
190
+ const provider = this.provider;
191
+ let workspace = undefined;
192
+ // supplying workspace does not work for first initialisation
193
+ // https://discuss.hashicorp.com/t/terraform-init-ignores-input-false-option/16065
194
+ // doesn't seem to be an issue in Terraform 1.1
195
+ if (fs_1.default.existsSync('./.terraform') && version !== '0.12') {
196
+ workspace = deployment.name;
197
+ }
198
+ (0, terraformCli_1.tf)('init', {
199
+ provider,
200
+ version,
201
+ backendConfig,
202
+ workspace: workspace,
203
+ options: ['-force-copy', '-reconfigure'],
204
+ });
205
+ const workspaces = (0, terraformCli_1.tf)('workspace list', {
206
+ provider,
207
+ version,
208
+ silent: true,
209
+ });
210
+ const deploymentName = args[0];
211
+ let workspaceExists = workspaces.split('\n').find((line) => {
212
+ return line.indexOf(deploymentName) >= 0;
213
+ });
214
+ if (!workspaceExists) {
215
+ workspaceExists = workspaces.split('\n').find((line) => {
216
+ return line.indexOf(deploymentName) >= 0;
217
+ });
218
+ if (!workspaceExists) {
219
+ (0, terraformCli_1.tf)(`workspace new ${deploymentName}`, {
220
+ provider,
221
+ version,
222
+ });
223
+ }
224
+ (0, terraformCli_1.tf)(`workspace select ${deploymentName}`, {
225
+ provider,
226
+ version,
227
+ });
228
+ }
229
+ else {
230
+ (0, terraformCli_1.tf)(`workspace select ${deploymentName}`, {
231
+ provider,
232
+ version,
233
+ });
234
+ }
235
+ }
236
+ finally {
237
+ (0, utils_sh_1.cd)('../..');
238
+ }
239
+ };
240
+ this.ensureInCorrectWorkspace = (params) => {
241
+ const currentWorkspace = (0, terraformCli_1.tf)('workspace show', {
242
+ provider: params.provider,
243
+ version: params.version,
244
+ silent: true,
245
+ }).trim();
246
+ if (currentWorkspace !== params.deploymentName) {
247
+ (0, terraformCli_1.tf)(`workspace select ${params.deploymentName}`, {
248
+ provider: params.provider,
249
+ version: params.version,
250
+ silent: true,
251
+ });
252
+ // init with reconfigure required here in case we are switching to a different
253
+ // s3 bucket in a different environment for a different deployment
254
+ (0, terraformCli_1.tf)('init', {
255
+ provider: params.provider,
256
+ backendConfig: params.backendConfig,
257
+ version: params.version,
258
+ workspace: params.deploymentName,
259
+ options: ['-reconfigure'],
260
+ });
261
+ }
262
+ };
263
+ this.plan = (args) => {
264
+ const deployment = getDeployment(args);
265
+ const version = this.getTfVersion(args);
266
+ const backendConfig = this.getTfStateVariables(deployment);
267
+ (0, utils_sh_1.cd)('./infra/aws');
268
+ try {
269
+ const provider = this.provider;
270
+ this.ensureInCorrectWorkspace({
271
+ deploymentName: args[0],
272
+ provider,
273
+ version,
274
+ backendConfig,
275
+ });
276
+ const variables = [
277
+ ...(0, exports.getVariablesFromHCL)({ ...deployment, ...deployment.configuration }),
278
+ ];
279
+ (0, terraformCli_1.tf)('plan', {
280
+ provider,
281
+ variables,
282
+ version,
283
+ options: ['-input=false', '-out tfplan'],
284
+ });
285
+ }
286
+ finally {
287
+ (0, utils_sh_1.cd)('../..');
288
+ }
289
+ };
290
+ this.apply = (args, options) => {
291
+ const deployment = getDeployment(args);
292
+ const version = this.getTfVersion(args);
293
+ const backendConfig = this.getTfStateVariables(deployment);
294
+ (0, utils_sh_1.cd)('./infra/aws');
295
+ try {
296
+ const provider = this.provider;
297
+ const deploymentName = args[0];
298
+ this.ensureInCorrectWorkspace({
299
+ deploymentName: args[0],
300
+ provider,
301
+ version,
302
+ backendConfig,
303
+ });
304
+ let cliOptions = ['-input=false', 'tfplan'];
305
+ if (options === null || options === void 0 ? void 0 : options.parallelism) {
306
+ cliOptions = [`-parallelism=${options.parallelism}`, ...cliOptions];
307
+ }
308
+ (0, terraformCli_1.tf)('apply', {
309
+ provider,
310
+ options: cliOptions,
311
+ version,
312
+ });
313
+ const res = (0, terraformCli_1.tf)('output', {
314
+ provider,
315
+ options: ['-json'],
316
+ version,
317
+ }).trim();
318
+ const deploymentState = (0, infra_1.readDeploymentState)('./../../', deploymentName, {
319
+ createIfNotExist: true,
320
+ });
321
+ deploymentState.terraform = JSON.parse(res);
322
+ (0, infra_1.writeDeploymentState)('./../../', deploymentState);
323
+ }
324
+ finally {
325
+ (0, utils_sh_1.cd)('../..');
326
+ }
327
+ };
328
+ this.destroy = (args) => {
329
+ const deployment = getDeployment(args);
330
+ const version = this.getTfVersion(args);
331
+ const backendConfig = this.getTfStateVariables(deployment);
332
+ (0, utils_sh_1.cd)('./infra/aws');
333
+ try {
334
+ const ciConfirmed = args.find((str) => str === '-y');
335
+ if (!ciConfirmed) {
336
+ const value = (0, prompt_sync_1.default)()('Are you sure to destroy your deployed resources? If yes, please type `y` and enter.\n' +
337
+ 'Otherwise, press enter.\nYour Input: ');
338
+ if (value !== 'y') {
339
+ (0, utils_log_1.fatal)('Prompt not confirmed with `y`');
340
+ }
341
+ }
342
+ const provider = this.provider;
343
+ const variables = [
344
+ ...(0, exports.getVariablesFromHCL)({ ...deployment, ...deployment.configuration }),
345
+ ];
346
+ (0, terraformCli_1.tf)(`workspace select ${args[0]}`, { provider, version });
347
+ (0, terraformCli_1.tf)('init', {
348
+ provider,
349
+ backendConfig,
350
+ options: ['-reconfigure'],
351
+ version,
352
+ });
353
+ (0, terraformCli_1.tf)('plan', {
354
+ provider,
355
+ variables,
356
+ version,
357
+ workspace: args[0],
358
+ options: ['-input=false', '-out tfplan'],
359
+ });
360
+ (0, terraformCli_1.tf)('destroy', {
361
+ provider,
362
+ variables,
363
+ version,
364
+ options: ['-input=false', '-auto-approve'],
365
+ });
366
+ }
367
+ finally {
368
+ (0, utils_sh_1.cd)('../..');
369
+ }
370
+ };
371
+ this.performUpgrade = (deploymentName, targetVersion) => {
372
+ const provider = this.provider;
373
+ const version = this.getTfVersion([deploymentName]);
374
+ (0, utils_sh_1.cd)('./infra/aws');
375
+ const currentWorkspace = (0, terraformCli_1.tf)('workspace show', {
376
+ provider: provider,
377
+ version: version,
378
+ silent: true,
379
+ }).trim();
380
+ if (currentWorkspace !== deploymentName) {
381
+ const workspaces = (0, terraformCli_1.tf)('workspace list', {
382
+ provider,
383
+ version,
384
+ silent: true,
385
+ });
386
+ const workspaceExists = workspaces.split('\n').find((line) => {
387
+ return line.indexOf(deploymentName) >= 0;
388
+ });
389
+ if (workspaceExists) {
390
+ (0, terraformCli_1.tf)(`workspace select ${deploymentName}`, {
391
+ provider,
392
+ version,
393
+ });
394
+ }
395
+ else {
396
+ // Sometimes it seems that Terraform forgets/destroys a workspace when upgrading the version of
397
+ // another deployment.
398
+ throw new Error(`Please initialise the deployment to be upgraded first with 'yarn infra init ${deploymentName}'. Please note that the 'init' command may fail with the error 'Error: Invalid legacy provider address' (if you have upgraded another deployment before this). In that case, run the 'upgrade' command regardless after the 'init' command has completed.`);
399
+ }
400
+ }
401
+ if (targetVersion === '0.13') {
402
+ const upgradeRes = (0, terraformCli_1.tf)(`${targetVersion}upgrade`, {
403
+ version: targetVersion,
404
+ provider,
405
+ options: ['-yes'],
406
+ });
407
+ if (upgradeRes.indexOf('Upgrade complete!') === -1) {
408
+ throw new Error('Upgrade of Terraform version not successful.');
409
+ }
410
+ }
411
+ (0, utils_sh_1.cd)('../..');
412
+ const packageConfig = (0, utils_package_1.readPackageConfig)();
413
+ const deploymentInConfig = packageConfig.deployments.find((e) => e.name === deploymentName);
414
+ (0, assert_1.default)(deploymentInConfig);
415
+ deploymentInConfig.tfVersion = targetVersion;
416
+ (0, utils_package_1.writePackageConfig)(packageConfig);
417
+ this.init([deploymentName]);
418
+ console.log(`Version upgraded to ${targetVersion}. Please run deployment to upgrade remote state before further upgrades.`);
419
+ };
420
+ this.upgrade = (args) => {
421
+ const deployment = getDeployment(args);
422
+ const version = deployment.tfVersion || '0.12';
423
+ const newVersion = args[1];
424
+ if (version === newVersion) {
425
+ console.log('Already on version', newVersion);
426
+ return;
427
+ }
428
+ if (version === '0.12' && newVersion === '0.13') {
429
+ this.performUpgrade(args[0], '0.13');
430
+ return;
431
+ }
432
+ if (version === '0.13' && newVersion === '0.14') {
433
+ this.performUpgrade(args[0], '0.14');
434
+ return;
435
+ }
436
+ if (version === '0.14' && newVersion === '0.15') {
437
+ this.performUpgrade(args[0], '0.15');
438
+ return;
439
+ }
440
+ if (version === '0.15' && newVersion === '1.0') {
441
+ this.performUpgrade(args[0], '1.0');
442
+ return;
443
+ }
444
+ if (version === '1.0' && newVersion === '1.1') {
445
+ this.performUpgrade(args[0], '1.1');
446
+ return;
447
+ }
448
+ throw new Error(`Version upgrade not supported: from [${version}] to [${newVersion}]. Currently only 0.12 -> 0.13 is supported.`);
449
+ };
450
+ (0, assert_1.default)(provider);
451
+ this.provider = provider;
452
+ }
453
+ terraform(opArgs) {
454
+ const provider = this.provider;
455
+ const deployment = getDeployment(opArgs);
456
+ const version = this.getTfVersion(opArgs);
457
+ let backendConfig;
458
+ if (opArgs.find((arg) => arg === '--inject-backend-config')) {
459
+ backendConfig = this.getTfStateVariables(deployment);
460
+ }
461
+ let variables;
462
+ (0, utils_sh_1.cd)('./infra/aws');
463
+ try {
464
+ if (opArgs.find((arg) => arg === '--inject-variables')) {
465
+ variables = [
466
+ ...(0, exports.getVariablesFromHCL)({
467
+ ...deployment,
468
+ ...deployment.configuration,
469
+ }),
470
+ ];
471
+ }
472
+ (0, terraformCli_1.tf)(`workspace select ${opArgs[0]}`, { provider, version });
473
+ const remainingArgs = opArgs
474
+ .slice(1)
475
+ .filter((arg) => arg !== '--inject-backend-config' && arg !== '--inject-variables');
476
+ (0, terraformCli_1.tf)(remainingArgs.join(' '), {
477
+ provider,
478
+ backendConfig,
479
+ version,
480
+ variables,
481
+ });
482
+ }
483
+ finally {
484
+ (0, utils_sh_1.cd)('../..');
485
+ }
486
+ }
487
+ }
488
+ exports.TerraformBuild = TerraformBuild;
489
489
  //# sourceMappingURL=terraformBuild.js.map