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 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 createParams = {
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 && !createParams.Regions.length && existingStacks.every(s => s.Status === 'CURRENT')) {
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-architect",
3
- "version": "6.7.51",
3
+ "version": "6.7.52",
4
4
  "description": "AWS Architect is a node based tool to configure and deploy AWS-based microservices.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",