Skip to content

Commit 35d7ef3

Browse files
authored
Merge pull request #26 from aws-solutions/release/v1.0.6-final
Updated to version v1.0.6
2 parents 1e847bb + 5737b3f commit 35d7ef3

File tree

13 files changed

+528
-16
lines changed

13 files changed

+528
-16
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.6] - 2026-02-24
9+
10+
### Security
11+
12+
- Update dependencies to mitigate [CVE-2026-1669](https://nvd.nist.gov/vuln/detail/CVE-2026-1669) and [CVE-2026-26278](https://nvd.nist.gov/vuln/detail/CVE-2026-26278).
13+
814
## [1.0.5] - 2026-02-18
915

1016
### Security
@@ -32,7 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3238
### Fixed
3339

3440
- Optimize GetAssetUrl Lambda function memory for new AWS account quota
35-
- Date pickers being disabled in non-PST timezones in race creation form ([#6](https://github.com/aws-solutions/deepracer-on-aws/issues/6) - Issue 2 and 3)
41+
- Date pickers being disabled in non-PST timezones in race creation form ([#6](https://github.com/aws-solutions/deepracer-on-aws/issues/6) - Issue 2 and 3, [PR #7](https://github.com/aws-solutions/deepracer-on-aws/pull/7)) - contributed by ([@Iarsll](https://github.com/larsll))
3642

3743
## [1.0.1] - 2026-02-02
3844

solution-manifest.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
id: SO0310
22
name: deepracer-on-aws
3-
version: v1.0.5
3+
version: v1.0.6
44
cloudformation_templates:
55
- template: deepracer-on-aws.template
66
main_template: true
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { App, Stack } from 'aws-cdk-lib';
5+
import { Template } from 'aws-cdk-lib/assertions';
6+
import { describe, it, expect, afterEach } from 'vitest';
7+
8+
import { TEST_NAMESPACE } from '../../../constants/testConstants.js';
9+
import { DynamoDBTable } from '../../storage/dynamoDB.js';
10+
import { getDeploymentMode, isDevMode } from '../deploymentModeHelper.js';
11+
import { LogGroupCategory, LogGroupsHelper } from '../logGroupsHelper.js';
12+
13+
// Mock the KmsHelper to avoid having the static KMS key shared between stacks
14+
vi.mock('../kmsHelper.js', async (importOriginal) => {
15+
const actual = await importOriginal<typeof import('../kmsHelper.js')>();
16+
const { Key } = await import('aws-cdk-lib/aws-kms');
17+
return {
18+
...actual,
19+
KmsHelper: {
20+
...actual.KmsHelper,
21+
get: vi.fn().mockImplementation((scope, namespace) => {
22+
return new Key(scope, `MockKmsKey${namespace}${Date.now()}`, {
23+
description: `Mock KMS key for testing ${namespace}`,
24+
});
25+
}),
26+
},
27+
};
28+
});
29+
30+
type LogGroupsHelperWithReset = typeof LogGroupsHelper & {
31+
reset: () => void;
32+
};
33+
34+
type LogGroupsModuleWithReset = typeof import('../logGroupsHelper.js') & {
35+
LogGroupsHelper: LogGroupsHelperWithReset;
36+
};
37+
38+
const asLogGroupsHelperWithReset = (): LogGroupsHelperWithReset => {
39+
return LogGroupsHelper as LogGroupsHelperWithReset;
40+
};
41+
42+
vi.mock('../logGroupsHelper.js', async () => {
43+
const actual = await vi.importActual('../logGroupsHelper.js');
44+
45+
const mockedActual = actual as LogGroupsModuleWithReset;
46+
mockedActual.LogGroupsHelper.reset = () => {
47+
// @ts-expect-error - accessing private static property for testing
48+
(mockedActual.LogGroupsHelper as LogGroupsHelper).logGroups = [];
49+
// @ts-expect-error - accessing private static property for testing
50+
(mockedActual.LogGroupsHelper as LogGroupsHelper).logGroupsByCategory = new Map();
51+
};
52+
53+
return mockedActual;
54+
});
55+
56+
describe('deploymentModeHelper', () => {
57+
afterEach(() => {
58+
asLogGroupsHelperWithReset().reset();
59+
});
60+
61+
describe('getDeploymentMode', () => {
62+
it('should return empty string when DEPLOYMENT_MODE context is not set', () => {
63+
const app = new App();
64+
const stack = new Stack(app, 'TestStack');
65+
66+
expect(getDeploymentMode(stack)).toBe('');
67+
});
68+
69+
it('should return the DEPLOYMENT_MODE context value when set', () => {
70+
const app = new App({
71+
context: {
72+
DEPLOYMENT_MODE: 'dev',
73+
},
74+
});
75+
const stack = new Stack(app, 'TestStack');
76+
77+
expect(getDeploymentMode(stack)).toBe('dev');
78+
});
79+
80+
it('should return the exact value including case', () => {
81+
const app = new App({
82+
context: {
83+
DEPLOYMENT_MODE: 'PROD',
84+
},
85+
});
86+
const stack = new Stack(app, 'TestStack');
87+
88+
expect(getDeploymentMode(stack)).toBe('PROD');
89+
});
90+
});
91+
92+
describe('isDevMode', () => {
93+
it('should return false when DEPLOYMENT_MODE is not set', () => {
94+
const app = new App();
95+
const stack = new Stack(app, 'TestStack');
96+
97+
expect(isDevMode(stack)).toBe(false);
98+
});
99+
100+
it('should return true when DEPLOYMENT_MODE is "dev"', () => {
101+
const app = new App({
102+
context: {
103+
DEPLOYMENT_MODE: 'dev',
104+
},
105+
});
106+
const stack = new Stack(app, 'TestStack');
107+
108+
expect(isDevMode(stack)).toBe(true);
109+
});
110+
111+
it('should return true when DEPLOYMENT_MODE is "DEV" (case insensitive)', () => {
112+
const app = new App({
113+
context: {
114+
DEPLOYMENT_MODE: 'DEV',
115+
},
116+
});
117+
const stack = new Stack(app, 'TestStack');
118+
119+
expect(isDevMode(stack)).toBe(true);
120+
});
121+
122+
it('should return true when DEPLOYMENT_MODE is "Dev" (case insensitive)', () => {
123+
const app = new App({
124+
context: {
125+
DEPLOYMENT_MODE: 'Dev',
126+
},
127+
});
128+
const stack = new Stack(app, 'TestStack');
129+
130+
expect(isDevMode(stack)).toBe(true);
131+
});
132+
133+
it('should return false when DEPLOYMENT_MODE is "prod"', () => {
134+
const app = new App({
135+
context: {
136+
DEPLOYMENT_MODE: 'prod',
137+
},
138+
});
139+
const stack = new Stack(app, 'TestStack');
140+
141+
expect(isDevMode(stack)).toBe(false);
142+
});
143+
144+
it('should return false when DEPLOYMENT_MODE is empty string', () => {
145+
const app = new App({
146+
context: {
147+
DEPLOYMENT_MODE: '',
148+
},
149+
});
150+
const stack = new Stack(app, 'TestStack');
151+
152+
expect(isDevMode(stack)).toBe(false);
153+
});
154+
});
155+
156+
describe('DynamoDB table removal policy based on deployment mode', () => {
157+
it('should set RemovalPolicy.RETAIN when DEPLOYMENT_MODE is not set', () => {
158+
const app = new App();
159+
const stack = new Stack(app, 'TestStack');
160+
161+
new DynamoDBTable(stack, 'TestDynamoDB', {
162+
namespace: TEST_NAMESPACE,
163+
});
164+
165+
const template = Template.fromStack(stack);
166+
expect(() => {
167+
template.hasResource('AWS::DynamoDB::GlobalTable', {
168+
DeletionPolicy: 'Retain',
169+
UpdateReplacePolicy: 'Retain',
170+
});
171+
}).not.toThrow();
172+
});
173+
174+
it('should set RemovalPolicy.DESTROY when DEPLOYMENT_MODE is "dev"', () => {
175+
const app = new App({
176+
context: {
177+
DEPLOYMENT_MODE: 'dev',
178+
},
179+
});
180+
const stack = new Stack(app, 'TestStack');
181+
182+
new DynamoDBTable(stack, 'TestDynamoDB', {
183+
namespace: TEST_NAMESPACE,
184+
});
185+
186+
const template = Template.fromStack(stack);
187+
expect(() => {
188+
template.hasResource('AWS::DynamoDB::GlobalTable', {
189+
DeletionPolicy: 'Delete',
190+
UpdateReplacePolicy: 'Delete',
191+
});
192+
}).not.toThrow();
193+
});
194+
195+
it('should set RemovalPolicy.RETAIN when DEPLOYMENT_MODE is "prod"', () => {
196+
const app = new App({
197+
context: {
198+
DEPLOYMENT_MODE: 'prod',
199+
},
200+
});
201+
const stack = new Stack(app, 'TestStack');
202+
203+
new DynamoDBTable(stack, 'TestDynamoDB', {
204+
namespace: TEST_NAMESPACE,
205+
});
206+
207+
const template = Template.fromStack(stack);
208+
expect(() => {
209+
template.hasResource('AWS::DynamoDB::GlobalTable', {
210+
DeletionPolicy: 'Retain',
211+
UpdateReplacePolicy: 'Retain',
212+
});
213+
}).not.toThrow();
214+
});
215+
});
216+
217+
describe('LogGroup removal policy based on deployment mode', () => {
218+
it('should set RemovalPolicy.RETAIN when DEPLOYMENT_MODE is not set', () => {
219+
const app = new App();
220+
const stack = new Stack(app, 'TestStack');
221+
222+
LogGroupsHelper.getOrCreateLogGroup(stack, 'TestLogGroup', {
223+
logGroupCategory: LogGroupCategory.DEFAULT,
224+
namespace: TEST_NAMESPACE,
225+
});
226+
227+
const template = Template.fromStack(stack);
228+
expect(() => {
229+
template.hasResource('AWS::Logs::LogGroup', {
230+
DeletionPolicy: 'Retain',
231+
UpdateReplacePolicy: 'Retain',
232+
});
233+
}).not.toThrow();
234+
});
235+
236+
it('should set RemovalPolicy.DESTROY when DEPLOYMENT_MODE is "dev"', () => {
237+
const app = new App({
238+
context: {
239+
DEPLOYMENT_MODE: 'dev',
240+
},
241+
});
242+
const stack = new Stack(app, 'TestStack');
243+
244+
LogGroupsHelper.getOrCreateLogGroup(stack, 'TestLogGroup', {
245+
logGroupCategory: LogGroupCategory.WORKFLOW,
246+
namespace: TEST_NAMESPACE,
247+
});
248+
249+
const template = Template.fromStack(stack);
250+
expect(() => {
251+
template.hasResource('AWS::Logs::LogGroup', {
252+
DeletionPolicy: 'Delete',
253+
UpdateReplacePolicy: 'Delete',
254+
});
255+
}).not.toThrow();
256+
});
257+
258+
it('should set RemovalPolicy.RETAIN when DEPLOYMENT_MODE is "prod"', () => {
259+
const app = new App({
260+
context: {
261+
DEPLOYMENT_MODE: 'prod',
262+
},
263+
});
264+
const stack = new Stack(app, 'TestStack');
265+
266+
LogGroupsHelper.getOrCreateLogGroup(stack, 'TestLogGroup', {
267+
logGroupCategory: LogGroupCategory.API,
268+
namespace: TEST_NAMESPACE,
269+
});
270+
271+
const template = Template.fromStack(stack);
272+
expect(() => {
273+
template.hasResource('AWS::Logs::LogGroup', {
274+
DeletionPolicy: 'Retain',
275+
UpdateReplacePolicy: 'Retain',
276+
});
277+
}).not.toThrow();
278+
});
279+
});
280+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { Construct } from 'constructs';
5+
6+
export function getDeploymentMode(scope: Construct): string {
7+
return scope.node.tryGetContext('DEPLOYMENT_MODE') ?? '';
8+
}
9+
10+
export function isDevMode(scope: Construct): boolean {
11+
return getDeploymentMode(scope).toLowerCase() === 'dev';
12+
}

source/apps/infra/lib/constructs/common/logGroupsHelper.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { RemovalPolicy, Stack } from 'aws-cdk-lib';
66
import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs';
77
import { Construct } from 'constructs';
88

9+
import { isDevMode } from './deploymentModeHelper';
910
import { KmsHelper } from './kmsHelper';
1011

1112
interface CustomLogGroupProps {
@@ -63,7 +64,7 @@ export class LogGroupsHelper {
6364
const newLogGroup = new LogGroup(Stack.of(scope), `${category}LogGroup`, {
6465
logGroupName: logGroupName,
6566
retention: props.retention ?? defaultLogRetention,
66-
removalPolicy: DefaultLogRemovalPolicy,
67+
removalPolicy: isDevMode(scope) ? RemovalPolicy.DESTROY : RemovalPolicy.RETAIN,
6768
encryptionKey: KmsHelper.get(scope, props.namespace ?? DEFAULT_NAMESPACE),
6869
});
6970

source/apps/infra/lib/constructs/common/nodeLambdaFunction.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { LogGroup } from 'aws-cdk-lib/aws-logs';
1111
import { Construct } from 'constructs';
1212

1313
import { addCfnGuardSuppression } from './cfnGuardHelper.js';
14+
import { getDeploymentMode, isDevMode } from './deploymentModeHelper.js';
1415
import { LogGroupCategory, LogGroupsHelper } from './logGroupsHelper.js';
1516
import { getCustomUserAgent } from './manifestReader.js';
1617

@@ -44,7 +45,7 @@ export class NodeLambdaFunction extends NodejsFunction {
4445
tracing: Tracing.ACTIVE,
4546
timeout: Duration.seconds(30),
4647
loggingFormat: LoggingFormat.JSON,
47-
applicationLogLevelV2: ApplicationLogLevel.INFO,
48+
applicationLogLevelV2: isDevMode(scope) ? ApplicationLogLevel.DEBUG : ApplicationLogLevel.INFO,
4849
logGroup:
4950
logGroup ??
5051
LogGroupsHelper.getOrCreateLogGroup(scope, id, {
@@ -58,9 +59,9 @@ export class NodeLambdaFunction extends NodejsFunction {
5859
NODE_OPTIONS: '--enable-source-maps',
5960
POWERTOOLS_SERVICE_NAME: 'DeepRacerIndy',
6061
POWERTOOLS_METRICS_NAMESPACE: 'DeepRacerIndy',
61-
POWERTOOLS_LOG_LEVEL: 'INFO', // Change to DEBUG to see trace details in logs
6262
NAMESPACE: namespace,
6363
CUSTOM_USER_AGENT: getCustomUserAgent(),
64+
DEPLOYMENT_MODE: getDeploymentMode(scope),
6465
...environmentProps,
6566
},
6667
bundling: {

source/apps/infra/lib/constructs/storage/dynamoDB.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33

44
import { BASE_TABLE_NAME } from '@deepracer-indy/config/src/defaults/dynamoDBDefaults.js';
55
import { DynamoDBItemAttribute, GlobalSecondaryIndex, LocalSecondaryIndex } from '@deepracer-indy/database';
6+
import { RemovalPolicy } from 'aws-cdk-lib';
67
import { AttributeType, TableV2 } from 'aws-cdk-lib/aws-dynamodb';
78
import { Construct } from 'constructs';
89

10+
import { isDevMode } from '../common/deploymentModeHelper';
11+
912
export interface DynamoDBTableProps {
1013
namespace: string;
1114
}
@@ -25,6 +28,7 @@ export class DynamoDBTable extends Construct {
2528
tableName,
2629
partitionKey: { name: DynamoDBItemAttribute.PK, type: AttributeType.STRING },
2730
sortKey: { name: DynamoDBItemAttribute.SK, type: AttributeType.STRING },
31+
removalPolicy: isDevMode(this) ? RemovalPolicy.DESTROY : RemovalPolicy.RETAIN,
2832
globalSecondaryIndexes: [
2933
{
3034
indexName: GlobalSecondaryIndex.GSI1,

0 commit comments

Comments
 (0)