aws-architect 6.7.51 → 6.7.52
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/CHANGELOG.md +2 -0
- package/index.d.ts +6 -0
- package/index.js +22 -0
- package/lib/CloudFormationDeployer.js +92 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,8 @@ This is the changelog for [AWS Architect](readme.md).
|
|
|
3
3
|
|
|
4
4
|
## 6.7 ##
|
|
5
5
|
* Fix handlers for /route and /route/ so that the index.html is always duplicated.
|
|
6
|
+
* Add support for organizational stack set deployment
|
|
7
|
+
|
|
6
8
|
## 6.6 ##
|
|
7
9
|
* Add support to `deleteWebsiteVersion(version)`
|
|
8
10
|
* Fix `The function must be in an Active state. The current state for function arn:aws:lambda:Function:514 is Pending`.
|
package/index.d.ts
CHANGED
|
@@ -38,6 +38,11 @@ interface StackSetConfiguration {
|
|
|
38
38
|
regions: string[];
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
interface OrganizationalStackSetConfiguration {
|
|
42
|
+
changeSetName: string;
|
|
43
|
+
stackSetName: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
|
|
42
47
|
interface StageDeploymentOptions {
|
|
43
48
|
stage: string;
|
|
@@ -64,6 +69,7 @@ declare class AwsArchitect {
|
|
|
64
69
|
validateTemplate(stackTemplate: object): Promise<object>;
|
|
65
70
|
deployTemplate(stackTemplate: object, stackConfiguration: StackConfiguration, parameters: object): Promise<object>;
|
|
66
71
|
deployStackSetTemplate(stackTemplate: object, stackSetConfiguration: StackSetConfiguration, parameters: object): Promise<object>;
|
|
72
|
+
configureStackSetForAwsOrganization(stackTemplate: object, stackSetConfiguration: OrganizationalStackSetConfiguration, parameters: object): Promise<object>;
|
|
67
73
|
deployStagePromise(stage: string, lambdaVersion: string): Promise<object>;
|
|
68
74
|
removeStagePromise(stage: string, functionName: string): Promise<object>;
|
|
69
75
|
cleanupPreviousFunctionVersions(functionName: string, forceRemovalOfAliases: string): Promise<object>;
|
package/index.js
CHANGED
|
@@ -151,6 +151,28 @@ AwsArchitect.prototype.deployStackSetTemplate = async function(stackTemplate, st
|
|
|
151
151
|
return this.CloudFormationDeployer.deployStackSetTemplate(accountId, stackTemplate, stackConfiguration, parameters, `${this.PackageMetadata.name}/${this.PackageMetadata.version}`);
|
|
152
152
|
};
|
|
153
153
|
|
|
154
|
+
AwsArchitect.prototype.configureStackSetForAwsOrganization = async function(stackTemplate, stackConfiguration, parameters) {
|
|
155
|
+
try {
|
|
156
|
+
await new aws.IAM().getRole({ RoleName: 'AWSCloudFormationStackSetExecutionRole' }).promise();
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (error.code === 'NoSuchEntity') {
|
|
159
|
+
throw { title: 'Role "AWSCloudFormationStackSetExecutionRole" must exist. See prerequisite for cloudformation: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs-self-managed.html' };
|
|
160
|
+
}
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
await new aws.IAM().getRole({ RoleName: 'AWSCloudFormationStackSetAdministrationRole' }).promise();
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (error.code === 'NoSuchEntity') {
|
|
168
|
+
throw { title: 'Role "AWSCloudFormationStackSetAdministrationRole" must exist. See prerequisite for cloudformation: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs-self-managed.html' };
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return this.CloudFormationDeployer.configureStackSetForAwsOrganization(stackTemplate, stackConfiguration, parameters);
|
|
174
|
+
};
|
|
175
|
+
|
|
154
176
|
AwsArchitect.prototype.deployStagePromise = AwsArchitect.prototype.DeployStagePromise = function(stage, lambdaVersion) {
|
|
155
177
|
if (!stage) { throw new Error('Deployment stage is not defined.'); }
|
|
156
178
|
if (!lambdaVersion) { throw new Error('Deployment lambdaVersion is not defined.'); }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { CloudFormation } = require('aws-sdk');
|
|
1
|
+
const { CloudFormation, Organizations, EC2 } = require('aws-sdk');
|
|
2
2
|
const Tmp = require('tmp');
|
|
3
3
|
Tmp.setGracefulCleanup();
|
|
4
4
|
const fs = require('fs-extra');
|
|
@@ -360,20 +360,10 @@ class CloudFormationDeployer {
|
|
|
360
360
|
|
|
361
361
|
const existingStacks = await this.cloudFormationClient.listStackInstances({ StackSetName: options.stackSetName }).promise().then(data => data.Summaries);
|
|
362
362
|
const existingRegions = existingStacks.map(s => s.Region);
|
|
363
|
-
const
|
|
364
|
-
StackSetName: options.stackSetName,
|
|
365
|
-
Accounts: [accountId],
|
|
366
|
-
Regions: options.regions.filter(r => !existingRegions.some(e => e === r)),
|
|
367
|
-
OperationId: options.changeSetName,
|
|
368
|
-
OperationPreferences: {
|
|
369
|
-
FailureToleranceCount: 20,
|
|
370
|
-
MaxConcurrentCount: 20,
|
|
371
|
-
RegionConcurrencyType: 'PARALLEL'
|
|
372
|
-
}
|
|
373
|
-
};
|
|
363
|
+
const newRegions = options.regions.filter(r => !existingRegions.some(e => e === r));
|
|
374
364
|
|
|
375
365
|
// If the stack already existed, and there are no new regions, all the stacks are updated then check to see if the template matches the new template
|
|
376
|
-
if (stackExists && !
|
|
366
|
+
if (stackExists && !newRegions && existingStacks.every(s => s.Status === 'CURRENT')) {
|
|
377
367
|
const regionStacks = await this.cloudFormationClient.listStacks({ StackStatusFilter: ['CREATE_COMPLETE', 'UPDATE_COMPLETE', 'UPDATE_ROLLBACK_COMPLETE'] }).promise()
|
|
378
368
|
.then(r => r.StackSummaries);
|
|
379
369
|
|
|
@@ -421,6 +411,17 @@ class CloudFormationDeployer {
|
|
|
421
411
|
}
|
|
422
412
|
}
|
|
423
413
|
|
|
414
|
+
const createParams = {
|
|
415
|
+
StackSetName: options.stackSetName,
|
|
416
|
+
Accounts: [accountId],
|
|
417
|
+
Regions: newRegions,
|
|
418
|
+
OperationId: options.changeSetName,
|
|
419
|
+
OperationPreferences: {
|
|
420
|
+
FailureToleranceCount: 20,
|
|
421
|
+
MaxConcurrentCount: 20,
|
|
422
|
+
RegionConcurrencyType: 'PARALLEL'
|
|
423
|
+
}
|
|
424
|
+
};
|
|
424
425
|
if (createParams.Regions.length) {
|
|
425
426
|
console.log(`Create a stack instance in regions: ${createParams.Regions.join(', ')}`);
|
|
426
427
|
await this.cloudFormationClient.createStackInstances(createParams).promise();
|
|
@@ -446,6 +447,84 @@ class CloudFormationDeployer {
|
|
|
446
447
|
|
|
447
448
|
return { title: 'Success' };
|
|
448
449
|
}
|
|
450
|
+
|
|
451
|
+
async configureStackSetForAwsOrganization(template, options = {}, parameters = {}) {
|
|
452
|
+
if (template === null) { throw { error: '{template} object must be defined.' }; }
|
|
453
|
+
if (options.stackSetName === null) { throw { error: '{options.stackSetName} is a required property.' }; }
|
|
454
|
+
|
|
455
|
+
console.log(`Starting Configuration of the StackSet: ${options.stackSetName}`);
|
|
456
|
+
|
|
457
|
+
const templateString = this.getTemplateBody(template);
|
|
458
|
+
const stackParameters = {
|
|
459
|
+
StackSetName: options.stackSetName,
|
|
460
|
+
AutoDeployment: {
|
|
461
|
+
Enabled: true,
|
|
462
|
+
RetainStacksOnAccountRemoval: false
|
|
463
|
+
},
|
|
464
|
+
ManagedExecution: {
|
|
465
|
+
Active: true
|
|
466
|
+
},
|
|
467
|
+
Description: 'Deployed as an Organizational stack set',
|
|
468
|
+
PermissionModel: 'SERVICE_MANAGED',
|
|
469
|
+
Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'],
|
|
470
|
+
Tags: options.tags ? Object.keys(options.tags).map(t => ({ Key: t, Value: options.tags[t] })) : undefined,
|
|
471
|
+
TemplateBody: templateString
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const stackExists = await this.stackSetExists(options.stackSetName);
|
|
475
|
+
if (!stackExists) {
|
|
476
|
+
console.log('Create stack set...');
|
|
477
|
+
await this.cloudFormationClient.createStackSet(stackParameters).promise();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const currentStackData = await this.cloudFormationClient.describeStackSet({ StackSetName: options.stackSetName }).promise().then(data => data.StackSet);
|
|
481
|
+
const rawRegions = await new EC2().describeRegions().promise().then(data => data.Regions.map(r => r.RegionName));
|
|
482
|
+
const newRegions = rawRegions.filter(r => !currentStackData.Regions || !currentStackData.Regions.some(e => e === r))
|
|
483
|
+
.filter(r => r !== 'eu-central-2');
|
|
484
|
+
|
|
485
|
+
// If the stack already existed, and there are no new regions, all the stacks are updated then check to see if the template matches the new template
|
|
486
|
+
if (stackExists && !newRegions.length) {
|
|
487
|
+
if (isEqual(tryParseJson(currentStackData.TemplateBody), tryParseJson(templateString))
|
|
488
|
+
&& Object.keys(parameters).every(key => currentStackData.Parameters.find(p => p.ParameterKey === key && p.ParameterValue === parameters[key]))) {
|
|
489
|
+
console.log('Skipping deployment of stackset because template matches existing CF stack template');
|
|
490
|
+
return { title: 'Change set skipped, no changes detected.', code: 'SKIPPED' };
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (stackExists) {
|
|
495
|
+
console.log('Updating organizational stack set, stack will be updated asynchronously');
|
|
496
|
+
stackParameters.OperationId = `${options.changeSetName}-update`;
|
|
497
|
+
stackParameters.OperationPreferences = {
|
|
498
|
+
FailureToleranceCount: 20,
|
|
499
|
+
MaxConcurrentCount: 20,
|
|
500
|
+
RegionConcurrencyType: 'PARALLEL'
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
await this.cloudFormationClient.updateStackSet(stackParameters).promise();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const rootOrgsInfo = await new Organizations({ region: 'us-east-1' }).listRoots({}).promise();
|
|
507
|
+
|
|
508
|
+
const deployToAdditionalRegionsParams = {
|
|
509
|
+
StackSetName: options.stackSetName,
|
|
510
|
+
DeploymentTargets: {
|
|
511
|
+
OrganizationalUnitIds: rootOrgsInfo.Roots.map(org => org.Id)
|
|
512
|
+
},
|
|
513
|
+
Regions: newRegions,
|
|
514
|
+
OperationId: options.changeSetName,
|
|
515
|
+
OperationPreferences: {
|
|
516
|
+
FailureToleranceCount: 20,
|
|
517
|
+
MaxConcurrentCount: 20,
|
|
518
|
+
RegionConcurrencyType: 'PARALLEL'
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
if (deployToAdditionalRegionsParams.Regions.length || deployToAdditionalRegionsParams.DeploymentTargets.OrganizationalUnitIds.length) {
|
|
522
|
+
console.log(`Tracking StackSet accounts in Orgs: ${deployToAdditionalRegionsParams.DeploymentTargets.OrganizationalUnitIds.join(', ')}`);
|
|
523
|
+
await this.cloudFormationClient.createStackInstances(deployToAdditionalRegionsParams).promise();
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return { title: 'Success' };
|
|
527
|
+
}
|
|
449
528
|
}
|
|
450
529
|
|
|
451
530
|
module.exports = CloudFormationDeployer;
|