Skip to content

Commit cf4ff6a

Browse files
committed
wip
1 parent e45388a commit cf4ff6a

File tree

9 files changed

+526
-86
lines changed

9 files changed

+526
-86
lines changed

packages/@aws-cdk/aws-elasticache-alpha/lib/no-password-user.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ export class NoPasswordUser extends UserBase {
3535
/**
3636
* Return whether the given object is a `NoPasswordUser`.
3737
*/
38-
public static isNoPasswordUser(x: any) : x is NoPasswordUser {
39-
return x !== null && typeof(x) === 'object' && ELASTICACHE_NOPASSWORDUSER_SYMBOL in x;
38+
public static isNoPasswordUser(x: any): x is NoPasswordUser {
39+
return x !== null && typeof (x) === 'object' && ELASTICACHE_NOPASSWORDUSER_SYMBOL in x;
4040
}
4141

4242
/**
@@ -81,12 +81,12 @@ export class NoPasswordUser extends UserBase {
8181
// Enhanced CDK Analytics Telemetry
8282
addConstructMetadata(this, props);
8383

84-
this.engine = props.engine ?? UserEngine.VALKEY;
84+
this.engine = props.engine ?? UserEngine.REDIS;
8585
this.userId = props.userId;
8686
this.userName = props.userName ?? props.userId;
8787
this.accessString = props.accessControl.accessString;
8888

89-
if (this.engine === UserEngine.VALKEY ) {
89+
if (this.engine === UserEngine.VALKEY) {
9090
throw new ValidationError('Valkey engine does not support no-password authentication.', this);
9191
}
9292

packages/@aws-cdk/aws-elasticache-alpha/lib/serverless-cache-base.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,29 @@ import { IResource, Resource, Duration } from 'aws-cdk-lib/core';
1111
export enum CacheEngine {
1212
/**
1313
* Valkey engine, latest version available
14+
* For more information about the features related to this version check: https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/engine-versions.html
1415
*/
15-
VALKEY_DEFAULT = 'valkey',
16+
VALKEY_LATEST = 'valkey',
1617
/**
1718
* Valkey engine, version 7
19+
* For more information about the features related to this version check: https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/engine-versions.html
1820
*/
1921
VALKEY_7 = 'valkey_7',
2022
/**
2123
* Valkey engine, version 8
24+
* For more information about the features related to this version check: https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/engine-versions.html
2225
*/
2326
VALKEY_8 = 'valkey_8',
2427
/**
2528
* Redis engine, latest version available
29+
* For more information about the features related to this version check: https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/engine-versions.html
2630
*/
27-
REDIS_DEFAULT = 'redis',
31+
REDIS_LATEST = 'redis',
2832
/**
2933
* Memcached engine, latest version available
34+
* For more information about the features related to this version check: https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/engine-versions.html
3035
*/
31-
MEMCACHED_DEFAULT = 'memcached',
36+
MEMCACHED_LATEST = 'memcached',
3237
}
3338

3439
/**
@@ -108,7 +113,7 @@ export interface IServerlessCache extends IResource, ec2.IConnectable {
108113
/**
109114
* Metric for ECPUs consumed
110115
*/
111-
metricECPUsConsumed(props?: cloudwatch.MetricOptions): cloudwatch.Metric;
116+
metricProcessingUnitsConsumed(props?: cloudwatch.MetricOptions): cloudwatch.Metric;
112117
/**
113118
* Metric for network bytes in
114119
*/
@@ -196,10 +201,10 @@ export abstract class ServerlessCacheBase extends Resource implements IServerles
196201
* Metric for cache hit count
197202
*
198203
* @param props Additional properties which will be merged with the default metric
199-
* @default Average over 5 minutes
204+
* @default Sum over 5 minutes
200205
*/
201206
public metricCacheHitCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric {
202-
return this.metric('CacheHits', props);
207+
return this.metric('CacheHits', { statistic: 'Sum', ...props });
203208
}
204209
/**
205210
* Metric for cache miss count
@@ -234,7 +239,7 @@ export abstract class ServerlessCacheBase extends Resource implements IServerles
234239
* @param props Additional properties which will be merged with the default metric
235240
* @default Average over 5 minutes
236241
*/
237-
public metricECPUsConsumed(props?: cloudwatch.MetricOptions): cloudwatch.Metric {
242+
public metricProcessingUnitsConsumed(props?: cloudwatch.MetricOptions): cloudwatch.Metric {
238243
return this.metric('ElastiCacheProcessingUnits', props);
239244
}
240245
/**

packages/@aws-cdk/aws-elasticache-alpha/lib/serverless-cache.ts

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { IUserGroup } from './user-group';
66
import * as ec2 from 'aws-cdk-lib/aws-ec2';
77
import * as events from 'aws-cdk-lib/aws-events';
88
import * as kms from 'aws-cdk-lib/aws-kms';
9-
import { ArnFormat, Stack, Size, Lazy, ValidationError } from 'aws-cdk-lib/core';
9+
import { ArnFormat, Stack, Size, Lazy, ValidationError, Names, Token } from 'aws-cdk-lib/core';
1010
import { addConstructMetadata } from 'aws-cdk-lib/core/lib/metadata-resource';
1111
import { propertyInjectable } from 'aws-cdk-lib/core/lib/prop-injectable';
1212

@@ -244,8 +244,8 @@ export class ServerlessCache extends ServerlessCacheBase {
244244
/**
245245
* Return whether the given object is a `ServerlessCache`
246246
*/
247-
public static isServerlessCache(x: any) : x is ServerlessCache {
248-
return x !== null && typeof(x) === 'object' && ELASTICACHE_SERVERLESSCACHE_SYMBOL in x;
247+
public static isServerlessCache(x: any): x is ServerlessCache {
248+
return x !== null && typeof (x) === 'object' && ELASTICACHE_SERVERLESSCACHE_SYMBOL in x;
249249
}
250250

251251
/**
@@ -325,13 +325,13 @@ export class ServerlessCache extends ServerlessCacheBase {
325325
if (this.engine) {
326326
let defaultPort: ec2.Port;
327327
switch (this.engine) {
328-
case CacheEngine.VALKEY_DEFAULT:
328+
case CacheEngine.VALKEY_LATEST:
329329
case CacheEngine.VALKEY_7:
330330
case CacheEngine.VALKEY_8:
331-
case CacheEngine.REDIS_DEFAULT:
331+
case CacheEngine.REDIS_LATEST:
332332
defaultPort = ec2.Port.tcp(6379);
333333
break;
334-
case CacheEngine.MEMCACHED_DEFAULT:
334+
case CacheEngine.MEMCACHED_LATEST:
335335
defaultPort = ec2.Port.tcp(11211);
336336
break;
337337
default:
@@ -407,13 +407,13 @@ export class ServerlessCache extends ServerlessCacheBase {
407407

408408
constructor(scope: Construct, id: string, props: ServerlessCacheProps) {
409409
super(scope, id, {
410-
physicalName: props.serverlessCacheName,
410+
physicalName: props.serverlessCacheName ?? Lazy.string({ produce: () => Names.uniqueId(this) }),
411411
});
412412

413413
// Enhanced CDK Analytics Telemetry
414414
addConstructMetadata(this, props);
415415

416-
this.engine = props.engine ?? CacheEngine.VALKEY_DEFAULT;
416+
this.engine = props.engine ?? CacheEngine.VALKEY_LATEST;
417417
this.serverlessCacheName = this.physicalName;
418418
this.kmsKey = props.kmsKey;
419419
this.vpc = props.vpc;
@@ -433,7 +433,7 @@ export class ServerlessCache extends ServerlessCacheBase {
433433
const securityGroupIds = securityGroupConfig.securityGroupIds;
434434
this.securityGroups = securityGroupConfig.securityGroups;
435435

436-
const { engine, version } = this.parseEngine(this.engine);
436+
const [engine, version] = this.engine.split('_');
437437

438438
const resource = new CfnServerlessCache(this, 'Resource', {
439439
engine: engine,
@@ -498,17 +498,17 @@ export class ServerlessCache extends ServerlessCacheBase {
498498
if (!limits) return;
499499

500500
if (limits.dataStorageMinimumSize && !limits.dataStorageMinimumSize.isUnresolved() &&
501-
(limits.dataStorageMinimumSize.toGibibytes() < DATA_STORAGE_MIN_GB || limits.dataStorageMinimumSize.toGibibytes() > DATA_STORAGE_MAX_GB)) {
501+
(limits.dataStorageMinimumSize.toGibibytes() < DATA_STORAGE_MIN_GB || limits.dataStorageMinimumSize.toGibibytes() > DATA_STORAGE_MAX_GB)) {
502502
throw new ValidationError('Data storage minimum must be between 1 and 5000 GB.', this);
503503
}
504504
if (limits.dataStorageMaximumSize && !limits.dataStorageMaximumSize.isUnresolved() &&
505-
(limits.dataStorageMaximumSize.toGibibytes() < DATA_STORAGE_MIN_GB || limits.dataStorageMaximumSize.toGibibytes() > DATA_STORAGE_MAX_GB)) {
505+
(limits.dataStorageMaximumSize.toGibibytes() < DATA_STORAGE_MIN_GB || limits.dataStorageMaximumSize.toGibibytes() > DATA_STORAGE_MAX_GB)) {
506506
throw new ValidationError('Data storage maximum must be between 1 and 5000 GB.', this);
507507
}
508508

509509
if (limits.dataStorageMinimumSize && limits.dataStorageMaximumSize &&
510-
!limits.dataStorageMinimumSize.isUnresolved() && !limits.dataStorageMaximumSize.isUnresolved() &&
511-
limits.dataStorageMinimumSize.toGibibytes() > limits.dataStorageMaximumSize.toGibibytes()) {
510+
!limits.dataStorageMinimumSize.isUnresolved() && !limits.dataStorageMaximumSize.isUnresolved() &&
511+
limits.dataStorageMinimumSize.toGibibytes() > limits.dataStorageMaximumSize.toGibibytes()) {
512512
throw new ValidationError('Data storage minimum cannot be greater than maximum', this);
513513
}
514514
}
@@ -522,16 +522,16 @@ export class ServerlessCache extends ServerlessCacheBase {
522522
if (!limits) return;
523523

524524
if (limits.requestRateLimitMinimum !== undefined &&
525-
(limits.requestRateLimitMinimum < REQUEST_RATE_MIN_ECPU || limits.requestRateLimitMinimum > REQUEST_RATE_MAX_ECPU)) {
525+
(limits.requestRateLimitMinimum < REQUEST_RATE_MIN_ECPU || limits.requestRateLimitMinimum > REQUEST_RATE_MAX_ECPU)) {
526526
throw new ValidationError('Request rate minimum must be between 1,000 and 15,000,000 ECPUs per second', this);
527527
}
528528
if (limits.requestRateLimitMaximum !== undefined &&
529-
(limits.requestRateLimitMaximum < REQUEST_RATE_MIN_ECPU || limits.requestRateLimitMaximum > REQUEST_RATE_MAX_ECPU)) {
529+
(limits.requestRateLimitMaximum < REQUEST_RATE_MIN_ECPU || limits.requestRateLimitMaximum > REQUEST_RATE_MAX_ECPU)) {
530530
throw new ValidationError('Request rate maximum must be between 1,000 and 15,000,000 ECPUs per second', this);
531531
}
532532

533533
if (limits.requestRateLimitMinimum !== undefined && limits.requestRateLimitMaximum !== undefined &&
534-
limits.requestRateLimitMinimum > limits.requestRateLimitMaximum) {
534+
limits.requestRateLimitMinimum > limits.requestRateLimitMaximum) {
535535
throw new ValidationError('Request rate minimum cannot be greater than maximum', this);
536536
}
537537
}
@@ -542,14 +542,14 @@ export class ServerlessCache extends ServerlessCacheBase {
542542
* @param backup The backup settings to validate
543543
*/
544544
private validateBackupSettings(backup?: BackupSettings): void {
545-
if (backup?.backupRetentionLimit !== undefined) {
545+
if (!Token.isUnresolved(backup?.backupRetentionLimit) && backup?.backupRetentionLimit !== undefined) {
546546
const limit = backup.backupRetentionLimit;
547547
if (limit < 1 || limit > 35) {
548548
throw new ValidationError('Backup retention limit must be between 1 and 35 days', this);
549549
}
550550
}
551551

552-
if (backup?.backupNameBeforeDeletion !== undefined) {
552+
if (!Token.isUnresolved(backup?.backupNameBeforeDeletion) && backup?.backupNameBeforeDeletion !== undefined) {
553553
const name = backup.backupNameBeforeDeletion;
554554

555555
if (!/^[a-zA-Z]/.test(name)) {
@@ -579,11 +579,11 @@ export class ServerlessCache extends ServerlessCacheBase {
579579
private validateUserGroupCompatibility(engine: CacheEngine, userGroup?: IUserGroup): void {
580580
if (!userGroup) return;
581581

582-
if (engine === CacheEngine.MEMCACHED_DEFAULT) {
582+
if (engine === CacheEngine.MEMCACHED_LATEST) {
583583
throw new ValidationError('User groups cannot be used with Memcached engines. Only Redis and Valkey engines support user groups.', this);
584584
}
585585

586-
if (engine === CacheEngine.REDIS_DEFAULT && userGroup.engine !== UserEngine.REDIS) {
586+
if (engine === CacheEngine.REDIS_LATEST && userGroup.engine !== UserEngine.REDIS) {
587587
throw new ValidationError('Redis cache can only use Redis user groups.', this);
588588
}
589589
}
@@ -682,18 +682,4 @@ export class ServerlessCache extends ServerlessCacheBase {
682682
}
683683
throw new ValidationError('Invalid schedule format for backup time', this);
684684
}
685-
686-
/**
687-
* Parse engine string to extract engine name and version
688-
*
689-
* @param eng The cache engine enum value
690-
* @returns Object with engine name and optional version
691-
*/
692-
private parseEngine(eng: CacheEngine): {engine: string; version?: string} {
693-
const parts = eng.split('_');
694-
const engine = parts[0];
695-
const version = parts.length > 1 ? parts[1] : undefined;
696-
697-
return { engine, version };
698-
}
699685
}

packages/@aws-cdk/aws-elasticache-alpha/lib/user-group.ts

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Construct } from 'constructs';
22
import { UserEngine } from './common';
33
import { CfnUser, CfnUserGroup } from 'aws-cdk-lib/aws-elasticache';
44
import { IUserBase } from './user-base';
5-
import { IResource, Resource, ArnFormat, Stack, Lazy, ValidationError, UnscopedValidationError } from 'aws-cdk-lib/core';
5+
import { IResource, Resource, ArnFormat, Stack, Lazy, ValidationError, UnscopedValidationError, Names } from 'aws-cdk-lib/core';
66
import { addConstructMetadata } from 'aws-cdk-lib/core/lib/metadata-resource';
77
import { propertyInjectable } from 'aws-cdk-lib/core/lib/prop-injectable';
88

@@ -146,8 +146,8 @@ export class UserGroup extends UserGroupBase {
146146
/**
147147
* Return whether the given object is a `UserGroup`
148148
*/
149-
public static isUserGroup(x: any) : x is UserGroup {
150-
return x !== null && typeof(x) === 'object' && ELASTICACHE_USERGROUP_SYMBOL in x;
149+
public static isUserGroup(x: any): x is UserGroup {
150+
return x !== null && typeof (x) === 'object' && ELASTICACHE_USERGROUP_SYMBOL in x;
151151
}
152152

153153
/**
@@ -247,7 +247,7 @@ export class UserGroup extends UserGroupBase {
247247

248248
constructor(scope: Construct, id: string, props: UserGroupProps = {}) {
249249
super(scope, id, {
250-
physicalName: props.userGroupName,
250+
physicalName: props.userGroupName ?? Lazy.string({ produce: () => Names.uniqueId(this).toLocaleLowerCase() }),
251251
});
252252

253253
// Enhanced CDK Analytics Telemetry
@@ -260,14 +260,6 @@ export class UserGroup extends UserGroupBase {
260260
this._users.push(...props.users);
261261
}
262262

263-
if (this.engine === UserEngine.REDIS) {
264-
this._users.forEach(user => {
265-
if (user.engine !== UserEngine.REDIS) {
266-
throw new ValidationError('Redis user group can only contain Redis users.', this);
267-
}
268-
});
269-
}
270-
271263
this.resource = new CfnUserGroup(this, 'Resource', {
272264
engine: this.engine,
273265
userGroupId: this.physicalName,
@@ -293,10 +285,7 @@ export class UserGroup extends UserGroupBase {
293285
* Add a CloudFormation dependency on the user resource to ensure proper creation order.
294286
*/
295287
private addUserDependency(user: IUserBase): void {
296-
const userResource = user.node.tryFindChild('Resource') as CfnUser;
297-
if (userResource) {
298-
this.resource.addDependency(userResource);
299-
}
288+
this.resource.node.addDependency(user);
300289
}
301290

302291
/**
@@ -320,6 +309,11 @@ export class UserGroup extends UserGroupBase {
320309
}
321310

322311
if (this.engine === UserEngine.REDIS) {
312+
this._users.forEach(user => {
313+
if (user.engine !== UserEngine.REDIS) {
314+
throw new ValidationError('Redis user group can only contain Redis users.', this);
315+
}
316+
});
323317
const hasDefaultUser = this._users.some(user => user.userName === 'default');
324318
if (!hasDefaultUser) {
325319
throw new ValidationError('Redis user groups need to contain a user with the user name "default".', this);
@@ -336,9 +330,6 @@ export class UserGroup extends UserGroupBase {
336330
if (this._users.find(u => u.userId === user.userId)) {
337331
return;
338332
}
339-
if (this.engine === UserEngine.REDIS && user.engine !== UserEngine.REDIS) {
340-
throw new ValidationError('Redis user group can only contain Redis users.', this);
341-
}
342333
this._users.push(user);
343334
this.addUserDependency(user);
344335
}

packages/@aws-cdk/aws-elasticache-alpha/test/iam-user.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Template, Match } from 'aws-cdk-lib/assertions';
22
import { Stack } from 'aws-cdk-lib';
3-
import * as iam from 'aws-cdk-lib/aws-iam';
3+
import { Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
44
import { IamUser, AccessControl, UserEngine } from '../lib';
55

66
describe('IamUser', () => {
@@ -170,16 +170,16 @@ describe('IamUser', () => {
170170
describe('IAM permissions', () => {
171171
let stack: Stack;
172172
let user: IamUser;
173-
let role: iam.Role;
173+
let role: Role;
174174

175175
beforeEach(() => {
176176
stack = new Stack();
177177
user = new IamUser(stack, 'TestUser', {
178178
userId: 'test-user',
179179
accessControl: AccessControl.fromAccessString('on ~* +@all'),
180180
});
181-
role = new iam.Role(stack, 'TestRole', {
182-
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
181+
role = new Role(stack, 'TestRole', {
182+
assumedBy: new ServicePrincipal('ec2.amazonaws.com'),
183183
});
184184
});
185185

packages/@aws-cdk/aws-elasticache-alpha/test/no-password-user.test.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,12 @@ describe('NoPasswordUser', () => {
99
stack = new Stack();
1010
});
1111

12-
test.each([
13-
{
14-
testDescription: 'when using Valkey engine throws validation error',
15-
engine: UserEngine.VALKEY,
16-
errorMessage: 'Valkey engine does not support no-password authentication.',
17-
},
18-
{
19-
testDescription: 'when engine defaults to Valkey throws validation error',
20-
engine: undefined,
21-
errorMessage: 'Valkey engine does not support no-password authentication.',
22-
},
23-
])('$testDescription', ({ engine, errorMessage }) => {
12+
test('when using Valkey engine throws validation error', () => {
2413
expect(() => new NoPasswordUser(stack, 'TestUser', {
2514
userId: 'test-user',
26-
engine,
15+
engine: UserEngine.VALKEY,
2716
accessControl: AccessControl.fromAccessString('on ~* +@all'),
28-
})).toThrow(errorMessage);
17+
})).toThrow('Valkey engine does not support no-password authentication.');
2918
});
3019

3120
test.each([

0 commit comments

Comments
 (0)