Skip to content

Commit cc91082

Browse files
authored
Merge branch 'master' into patch-1
2 parents b4adae7 + 07acac2 commit cc91082

File tree

11 files changed

+300
-135
lines changed

11 files changed

+300
-135
lines changed

CHANGELOG.md

+100-69
Large diffs are not rendered by default.

lerna.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@
1010
"tools/*"
1111
],
1212
"rejectCycles": "true",
13-
"version": "1.60.0"
13+
"version": "1.61.0"
1414
}

packages/@aws-cdk/aws-redshift/lib/cluster.ts

+20-8
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,11 @@ export interface ClusterProps {
183183
readonly parameterGroup?: IClusterParameterGroup;
184184

185185
/**
186-
* Number of compute nodes in the cluster
186+
* Number of compute nodes in the cluster. Only specify this property for multi-node clusters.
187187
*
188-
* Value must be at least 1 and no more than 100.
188+
* Value must be at least 2 and no more than 100.
189189
*
190-
* @default 1
190+
* @default - 2 if `clusterType` is ClusterType.MULTI_NODE, undefined otherwise
191191
*/
192192
readonly numberOfNodes?: number;
193193

@@ -425,11 +425,7 @@ export class Cluster extends ClusterBase {
425425
}
426426

427427
const clusterType = props.clusterType || ClusterType.MULTI_NODE;
428-
const nodeCount = props.numberOfNodes !== undefined ? props.numberOfNodes : (clusterType === ClusterType.MULTI_NODE ? 2 : 1);
429-
430-
if (clusterType === ClusterType.MULTI_NODE && nodeCount < 2) {
431-
throw new Error('Number of nodes for cluster type multi-node must be at least 2');
432-
}
428+
const nodeCount = this.validateNodeCount(clusterType, props.numberOfNodes);
433429

434430
if (props.encrypted === false && props.encryptionKey !== undefined) {
435431
throw new Error('Cannot set property encryptionKey without enabling encryption!');
@@ -537,4 +533,20 @@ export class Cluster extends ClusterBase {
537533
target: this,
538534
});
539535
}
536+
537+
private validateNodeCount(clusterType: ClusterType, numberOfNodes?: number): number | undefined {
538+
if (clusterType === ClusterType.SINGLE_NODE) {
539+
// This property must not be set for single-node clusters; be generous and treat a value of 1 node as undefined.
540+
if (numberOfNodes !== undefined && numberOfNodes !== 1) {
541+
throw new Error('Number of nodes must be not be supplied or be 1 for cluster type single-node');
542+
}
543+
return undefined;
544+
} else {
545+
const nodeCount = numberOfNodes ?? 2;
546+
if (nodeCount < 2 || nodeCount > 100) {
547+
throw new Error('Number of nodes for cluster type multi-node must be at least 2 and no more than 100');
548+
}
549+
return nodeCount;
550+
}
551+
}
540552
}

packages/@aws-cdk/aws-redshift/test/cluster.test.ts

+90-54
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
import { expect as cdkExpect, haveResource, ResourcePart } from '@aws-cdk/assert';
1+
import { ABSENT, expect as cdkExpect, haveResource, ResourcePart } from '@aws-cdk/assert';
22
import '@aws-cdk/assert/jest';
33
import * as ec2 from '@aws-cdk/aws-ec2';
44
import * as kms from '@aws-cdk/aws-kms';
55
import * as s3 from '@aws-cdk/aws-s3';
66
import * as cdk from '@aws-cdk/core';
7+
import { Cluster, ClusterParameterGroup, ClusterType } from '../lib';
78

8-
import { Cluster, ClusterParameterGroup, ClusterType, NodeType } from '../lib';
9+
let stack: cdk.Stack;
10+
let vpc: ec2.IVpc;
911

10-
test('check that instantiation works', () => {
11-
// GIVEN
12-
const stack = testStack();
13-
const vpc = new ec2.Vpc(stack, 'VPC');
12+
beforeEach(() => {
13+
stack = testStack();
14+
vpc = new ec2.Vpc(stack, 'VPC');
15+
});
1416

17+
test('check that instantiation works', () => {
1518
// WHEN
1619
new Cluster(stack, 'Redshift', {
1720
masterUser: {
@@ -57,8 +60,7 @@ test('check that instantiation works', () => {
5760

5861
test('can create a cluster with imported vpc and security group', () => {
5962
// GIVEN
60-
const stack = testStack();
61-
const vpc = ec2.Vpc.fromLookup(stack, 'VPC', {
63+
vpc = ec2.Vpc.fromLookup(stack, 'ImportedVPC', {
6264
vpcId: 'VPC12345',
6365
});
6466
const sg = ec2.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'SecurityGroupId12345');
@@ -83,10 +85,6 @@ test('can create a cluster with imported vpc and security group', () => {
8385
});
8486

8587
test('creates a secret when master credentials are not specified', () => {
86-
// GIVEN
87-
const stack = testStack();
88-
const vpc = new ec2.Vpc(stack, 'VPC');
89-
9088
// WHEN
9189
new Cluster(stack, 'Redshift', {
9290
masterUser: {
@@ -133,34 +131,88 @@ test('creates a secret when master credentials are not specified', () => {
133131
}));
134132
});
135133

136-
test('SIngle Node CLusters spawn only single node', () => {
137-
// GIVEN
138-
const stack = testStack();
139-
const vpc = new ec2.Vpc(stack, 'VPC');
134+
describe('node count', () => {
135+
136+
test('Single Node Clusters do not define node count', () => {
137+
// WHEN
138+
new Cluster(stack, 'Redshift', {
139+
masterUser: {
140+
masterUsername: 'admin',
141+
},
142+
vpc,
143+
clusterType: ClusterType.SINGLE_NODE,
144+
});
145+
146+
// THEN
147+
cdkExpect(stack).to(haveResource('AWS::Redshift::Cluster', {
148+
ClusterType: 'single-node',
149+
NumberOfNodes: ABSENT,
150+
}));
151+
});
140152

141-
// WHEN
142-
new Cluster(stack, 'Redshift', {
143-
masterUser: {
144-
masterUsername: 'admin',
145-
},
146-
vpc,
147-
nodeType: NodeType.DC1_8XLARGE,
148-
clusterType: ClusterType.SINGLE_NODE,
153+
test('Single Node Clusters treat 1 node as undefined', () => {
154+
// WHEN
155+
new Cluster(stack, 'Redshift', {
156+
masterUser: {
157+
masterUsername: 'admin',
158+
},
159+
vpc,
160+
clusterType: ClusterType.SINGLE_NODE,
161+
numberOfNodes: 1,
162+
});
163+
164+
// THEN
165+
cdkExpect(stack).to(haveResource('AWS::Redshift::Cluster', {
166+
ClusterType: 'single-node',
167+
NumberOfNodes: ABSENT,
168+
}));
149169
});
150170

151-
// THEN
152-
cdkExpect(stack).to(haveResource('AWS::Redshift::Cluster', {
153-
ClusterType: 'single-node',
154-
NodeType: 'dc1.8xlarge',
155-
NumberOfNodes: 1,
156-
}));
171+
test('Single Node Clusters throw if any other node count is specified', () => {
172+
expect(() => {
173+
new Cluster(stack, 'Redshift', {
174+
masterUser: {
175+
masterUsername: 'admin',
176+
},
177+
vpc,
178+
clusterType: ClusterType.SINGLE_NODE,
179+
numberOfNodes: 2,
180+
});
181+
}).toThrow(/Number of nodes must be not be supplied or be 1 for cluster type single-node/);
182+
});
183+
184+
test('Multi-Node Clusters default to 2 nodes', () => {
185+
// WHEN
186+
new Cluster(stack, 'Redshift', {
187+
masterUser: {
188+
masterUsername: 'admin',
189+
},
190+
vpc,
191+
clusterType: ClusterType.MULTI_NODE,
192+
});
193+
194+
// THEN
195+
cdkExpect(stack).to(haveResource('AWS::Redshift::Cluster', {
196+
ClusterType: 'multi-node',
197+
NumberOfNodes: 2,
198+
}));
199+
});
200+
201+
test.each([0, 1, -1, 101])('Multi-Node Clusters throw with %s nodes', (numberOfNodes: number) => {
202+
expect(() => {
203+
new Cluster(stack, 'Redshift', {
204+
masterUser: {
205+
masterUsername: 'admin',
206+
},
207+
vpc,
208+
clusterType: ClusterType.MULTI_NODE,
209+
numberOfNodes,
210+
});
211+
}).toThrow(/Number of nodes for cluster type multi-node must be at least 2 and no more than 100/);
212+
});
157213
});
158214

159215
test('create an encrypted cluster with custom KMS key', () => {
160-
// GIVEN
161-
const stack = testStack();
162-
const vpc = new ec2.Vpc(stack, 'VPC');
163-
164216
// WHEN
165217
new Cluster(stack, 'Redshift', {
166218
masterUser: {
@@ -182,10 +234,6 @@ test('create an encrypted cluster with custom KMS key', () => {
182234
});
183235

184236
test('cluster with parameter group', () => {
185-
// GIVEN
186-
const stack = testStack();
187-
const vpc = new ec2.Vpc(stack, 'VPC');
188-
189237
// WHEN
190238
const group = new ClusterParameterGroup(stack, 'Params', {
191239
description: 'bye',
@@ -211,8 +259,6 @@ test('cluster with parameter group', () => {
211259

212260
test('imported cluster with imported security group honors allowAllOutbound', () => {
213261
// GIVEN
214-
const stack = testStack();
215-
216262
const cluster = Cluster.fromClusterAttributes(stack, 'Database', {
217263
clusterEndpointAddress: 'addr',
218264
clusterName: 'identifier',
@@ -235,8 +281,6 @@ test('imported cluster with imported security group honors allowAllOutbound', ()
235281

236282
test('can create a cluster with logging enabled', () => {
237283
// GIVEN
238-
const stack = testStack();
239-
const vpc = new ec2.Vpc(stack, 'VPC');
240284
const bucket = s3.Bucket.fromBucketName(stack, 'bucket', 'logging-bucket');
241285

242286
// WHEN
@@ -259,10 +303,6 @@ test('can create a cluster with logging enabled', () => {
259303
});
260304

261305
test('throws when trying to add rotation to a cluster without secret', () => {
262-
// GIVEN
263-
const stack = new cdk.Stack();
264-
const vpc = new ec2.Vpc(stack, 'VPC');
265-
266306
// WHEN
267307
const cluster = new Cluster(stack, 'Redshift', {
268308
masterUser: {
@@ -281,8 +321,6 @@ test('throws when trying to add rotation to a cluster without secret', () => {
281321

282322
test('throws validation error when trying to set encryptionKey without enabling encryption', () => {
283323
// GIVEN
284-
const stack = new cdk.Stack();
285-
const vpc = new ec2.Vpc(stack, 'VPC');
286324
const key = new kms.Key(stack, 'kms-key');
287325

288326
// WHEN
@@ -304,8 +342,6 @@ test('throws validation error when trying to set encryptionKey without enabling
304342

305343
test('throws when trying to add single user rotation multiple times', () => {
306344
// GIVEN
307-
const stack = new cdk.Stack();
308-
const vpc = new ec2.Vpc(stack, 'VPC');
309345
const cluster = new Cluster(stack, 'Redshift', {
310346
masterUser: {
311347
masterUsername: 'admin',
@@ -323,7 +359,7 @@ test('throws when trying to add single user rotation multiple times', () => {
323359
});
324360

325361
function testStack() {
326-
const stack = new cdk.Stack(undefined, undefined, { env: { account: '12345', region: 'us-test-1' } });
327-
stack.node.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']);
328-
return stack;
329-
}
362+
const newTestStack = new cdk.Stack(undefined, undefined, { env: { account: '12345', region: 'us-test-1' } });
363+
newTestStack.node.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']);
364+
return newTestStack;
365+
}

packages/aws-cdk/README.md

+27
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,33 @@ Example `outputs.json` after deployment of multiple stacks
227227
}
228228
```
229229

230+
##### Deployment Progress
231+
232+
By default, stack deployment events are displayed as a progress bar with the events for the resource
233+
currently being deployed.
234+
235+
Set the `--progress` flag to request the complete history which includes all CloudFormation events
236+
```console
237+
$ cdk deploy --progress events
238+
```
239+
240+
Alternatively, the `progress` key can be specified in the project config (`cdk.json`).
241+
242+
The following shows a sample `cdk.json` where the `progress` key is set to *events*.
243+
When `cdk deploy` is executed, deployment events will include the complete history.
244+
```
245+
{
246+
"app": "npx ts-node bin/myproject.ts",
247+
"context": {
248+
"@aws-cdk/core:enableStackNameDuplicates": "true",
249+
"aws-cdk:enableDiffNoFail": "true",
250+
"@aws-cdk/core:stackRelativeExports": "true"
251+
},
252+
"progress": "events"
253+
}
254+
```
255+
The `progress` key can also be specified as a user setting (`~/.cdk.json`)
256+
230257
#### `cdk destroy`
231258
Deletes a stack from it's environment. This will cause the resources in the stack to be destroyed (unless they were
232259
configured with a `DeletionPolicy` of `Retain`). During the stack destruction, the command will output progress

packages/aws-cdk/bin/cdk.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { SdkProvider } from '../lib/api/aws-auth';
1010
import { CloudFormationDeployments } from '../lib/api/cloudformation-deployments';
1111
import { CloudExecutable } from '../lib/api/cxapp/cloud-executable';
1212
import { execProgram } from '../lib/api/cxapp/exec';
13+
import { StackActivityProgress } from '../lib/api/util/cloudformation/stack-activity-monitor';
1314
import { CdkToolkit } from '../lib/cdk-toolkit';
1415
import { RequireApproval } from '../lib/diff';
1516
import { availableInitLanguages, cliInit, printAvailableTemplates } from '../lib/init';
@@ -89,7 +90,8 @@ async function parseCommandLineArguments() {
8990
.option('force', { alias: 'f', type: 'boolean', desc: 'Always deploy stack even if templates are identical', default: false })
9091
.option('parameters', { type: 'array', desc: 'Additional parameters passed to CloudFormation at deploy time (STACK:KEY=VALUE)', nargs: 1, requiresArg: true, default: {} })
9192
.option('outputs-file', { type: 'string', alias: 'O', desc: 'Path to file where stack outputs will be written as JSON', requiresArg: true })
92-
.option('previous-parameters', { type: 'boolean', default: true, desc: 'Use previous values for existing parameters (you must specify all parameters on every deployment if this is disabled)' }),
93+
.option('previous-parameters', { type: 'boolean', default: true, desc: 'Use previous values for existing parameters (you must specify all parameters on every deployment if this is disabled)' })
94+
.option('progress', { type: 'string', choices: [StackActivityProgress.BAR, StackActivityProgress.EVENTS], desc: 'Display mode for stack activity events.' }),
9395
)
9496
.command('destroy [STACKS..]', 'Destroy the stack(s) named STACKS', yargs => yargs
9597
.option('exclusively', { type: 'boolean', alias: 'e', desc: 'Only destroy requested stacks, don\'t include dependees' })
@@ -279,6 +281,7 @@ async function initCommandLine() {
279281
parameters: parameterMap,
280282
usePreviousParameters: args['previous-parameters'],
281283
outputsFile: args.outputsFile,
284+
progress: configuration.settings.get(['progress']),
282285
ci: args.ci,
283286
});
284287

packages/aws-cdk/lib/api/cloudformation-deployments.ts

+10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Mode, SdkProvider } from './aws-auth';
77
import { deployStack, DeployStackResult, destroyStack } from './deploy-stack';
88
import { ToolkitInfo } from './toolkit-info';
99
import { CloudFormationStack, Template } from './util/cloudformation';
10+
import { StackActivityProgress } from './util/cloudformation/stack-activity-monitor';
1011

1112
export interface DeployStackOptions {
1213
/**
@@ -89,6 +90,14 @@ export interface DeployStackOptions {
8990
*/
9091
usePreviousParameters?: boolean;
9192

93+
/**
94+
* Display mode for stack deployment progress.
95+
*
96+
* @default - StackActivityProgress.Bar - stack events will be displayed for
97+
* the resource currently being deployed.
98+
*/
99+
progress?: StackActivityProgress;
100+
92101
/**
93102
* Whether we are on a CI system
94103
*
@@ -163,6 +172,7 @@ export class CloudFormationDeployments {
163172
force: options.force,
164173
parameters: options.parameters,
165174
usePreviousParameters: options.usePreviousParameters,
175+
progress: options.progress,
166176
ci: options.ci,
167177
});
168178
}

0 commit comments

Comments
 (0)