Skip to content

Commit 5b5c15c

Browse files
author
Kamil Sobol
authored
use ssm parameter to detect CDK boostrap (#1576)
* use ssm parameter to detect CDK boostrap * use ssm parameter to detect CDK boostrap * fix that * use default qualifier
1 parent f204baa commit 5b5c15c

File tree

4 files changed

+78
-84
lines changed

4 files changed

+78
-84
lines changed

.changeset/proud-clouds-cross.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@aws-amplify/sandbox': patch
3+
---
4+
5+
use ssm parameter to detect CDK boostrap

packages/sandbox/src/file_watching_sandbox.test.ts

Lines changed: 49 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { afterEach, beforeEach, describe, it, mock } from 'node:test';
22
import path from 'path';
33
import watcher from '@parcel/watcher';
44
import {
5-
CDK_BOOTSTRAP_STACK_NAME,
6-
CDK_BOOTSTRAP_VERSION_KEY,
5+
CDK_DEFAULT_BOOTSTRAP_VERSION_PARAMETER_NAME,
76
FileWatchingSandbox,
87
getBootstrapUrl,
98
} from './file_watching_sandbox.js';
@@ -15,7 +14,6 @@ import {
1514
} from '@aws-amplify/backend-deployer';
1615
import fs from 'fs';
1716
import parseGitIgnore from 'parse-gitignore';
18-
import { CloudFormationClient } from '@aws-sdk/client-cloudformation';
1917
import _open from 'open';
2018
import { SecretListItem, getSecretClient } from '@aws-amplify/backend-secret';
2119
import { Sandbox, SandboxOptions } from './sandbox.js';
@@ -29,6 +27,7 @@ import {
2927
import { fileURLToPath } from 'url';
3028
import { BackendIdentifier } from '@aws-amplify/plugin-types';
3129
import { AmplifyUserError } from '@aws-amplify/platform-core';
30+
import { SSMClient } from '@aws-sdk/client-ssm';
3231

3332
// Watcher mocks
3433
const unsubscribeMockFn = mock.fn();
@@ -85,24 +84,15 @@ const backendDeployerDestroyMock = mock.method(backendDeployer, 'destroy', () =>
8584
Promise.resolve()
8685
);
8786
const region = 'test-region';
88-
const cfnClientMock = new CloudFormationClient({ region });
89-
const cfnClientSendMock = mock.fn();
90-
mock.method(cfnClientMock, 'send', cfnClientSendMock);
91-
cfnClientSendMock.mock.mockImplementation(() =>
87+
const ssmClientMock = new SSMClient({ region });
88+
const ssmClientSendMock = mock.fn();
89+
mock.method(ssmClientMock, 'send', ssmClientSendMock);
90+
ssmClientSendMock.mock.mockImplementation(() =>
9291
Promise.resolve({
93-
Stacks: [
94-
{
95-
Name: CDK_BOOTSTRAP_STACK_NAME,
96-
Outputs: [
97-
{
98-
Description:
99-
'The version of the bootstrap resources that are currently mastered in this stack',
100-
OutputKey: CDK_BOOTSTRAP_VERSION_KEY,
101-
OutputValue: '18',
102-
},
103-
],
104-
},
105-
],
92+
Parameter: {
93+
Name: CDK_DEFAULT_BOOTSTRAP_VERSION_PARAMETER_NAME,
94+
Value: '18',
95+
},
10696
})
10797
);
10898
const openMock = mock.fn(_open, (url: string) => Promise.resolve(url));
@@ -140,19 +130,19 @@ void describe('Sandbox to check if region is bootstrapped', () => {
140130
sandboxInstance = new FileWatchingSandbox(
141131
async () => testSandboxBackendId,
142132
sandboxExecutor,
143-
cfnClientMock,
133+
ssmClientMock,
144134
printer as unknown as Printer,
145135
openMock as never
146136
);
147137

148-
cfnClientSendMock.mock.resetCalls();
138+
ssmClientSendMock.mock.resetCalls();
149139
openMock.mock.resetCalls();
150140
backendDeployerDestroyMock.mock.resetCalls();
151141
backendDeployerDeployMock.mock.resetCalls();
152142
});
153143

154144
afterEach(async () => {
155-
cfnClientSendMock.mock.resetCalls();
145+
ssmClientSendMock.mock.resetCalls();
156146
openMock.mock.resetCalls();
157147
backendDeployerDestroyMock.mock.resetCalls();
158148
backendDeployerDeployMock.mock.resetCalls();
@@ -164,7 +154,7 @@ void describe('Sandbox to check if region is bootstrapped', () => {
164154
});
165155

166156
void it('when region has not bootstrapped, then opens console to initiate bootstrap', async () => {
167-
cfnClientSendMock.mock.mockImplementationOnce(() => {
157+
ssmClientSendMock.mock.mockImplementationOnce(() => {
168158
throw new Error('Stack with id CDKToolkit does not exist');
169159
});
170160

@@ -173,7 +163,7 @@ void describe('Sandbox to check if region is bootstrapped', () => {
173163
exclude: ['exclude1', 'exclude2'],
174164
});
175165

176-
assert.strictEqual(cfnClientSendMock.mock.callCount(), 1);
166+
assert.strictEqual(ssmClientSendMock.mock.callCount(), 1);
177167
assert.strictEqual(openMock.mock.callCount(), 1);
178168
assert.strictEqual(
179169
openMock.mock.calls[0].arguments[0],
@@ -182,21 +172,12 @@ void describe('Sandbox to check if region is bootstrapped', () => {
182172
});
183173

184174
void it('when region has bootstrapped, but with a version lower than the minimum (6), then opens console to initiate bootstrap', async () => {
185-
cfnClientSendMock.mock.mockImplementationOnce(() =>
175+
ssmClientSendMock.mock.mockImplementationOnce(() =>
186176
Promise.resolve({
187-
Stacks: [
188-
{
189-
Name: CDK_BOOTSTRAP_STACK_NAME,
190-
Outputs: [
191-
{
192-
Description:
193-
'The version of the bootstrap resources that are currently mastered in this stack',
194-
OutputKey: CDK_BOOTSTRAP_VERSION_KEY,
195-
OutputValue: '5',
196-
},
197-
],
198-
},
199-
],
177+
Parameter: {
178+
Name: CDK_DEFAULT_BOOTSTRAP_VERSION_PARAMETER_NAME,
179+
Value: '5',
180+
},
200181
})
201182
);
202183

@@ -205,7 +186,7 @@ void describe('Sandbox to check if region is bootstrapped', () => {
205186
exclude: ['exclude1', 'exclude2'],
206187
});
207188

208-
assert.strictEqual(cfnClientSendMock.mock.callCount(), 1);
189+
assert.strictEqual(ssmClientSendMock.mock.callCount(), 1);
209190
assert.strictEqual(openMock.mock.callCount(), 1);
210191
assert.strictEqual(
211192
openMock.mock.calls[0].arguments[0],
@@ -219,7 +200,7 @@ void describe('Sandbox to check if region is bootstrapped', () => {
219200
exclude: ['exclude1', 'exclude2'],
220201
});
221202

222-
assert.strictEqual(cfnClientSendMock.mock.callCount(), 1);
203+
assert.strictEqual(ssmClientSendMock.mock.callCount(), 1);
223204
assert.strictEqual(openMock.mock.callCount(), 0);
224205
});
225206
});
@@ -244,7 +225,7 @@ void describe('Sandbox using local project name resolver', () => {
244225
backendDeployerDestroyMock.mock.resetCalls();
245226
backendDeployerDeployMock.mock.resetCalls();
246227
subscribeMock.mock.resetCalls();
247-
cfnClientSendMock.mock.resetCalls();
228+
ssmClientSendMock.mock.resetCalls();
248229
await sandboxInstance.stop();
249230

250231
// Printer mocks are reset after the sandbox stop to reset the "Shutting down" call as well.
@@ -256,7 +237,7 @@ void describe('Sandbox using local project name resolver', () => {
256237
({ sandboxInstance } = await setupAndStartSandbox(
257238
{
258239
executor: sandboxExecutor,
259-
cfnClient: cfnClientMock,
240+
ssmClient: ssmClientMock,
260241
},
261242
undefined,
262243
false
@@ -279,7 +260,7 @@ void describe('Sandbox using local project name resolver', () => {
279260
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox(
280261
{
281262
executor: sandboxExecutor,
282-
cfnClient: cfnClientMock,
263+
ssmClient: ssmClientMock,
283264
},
284265
{
285266
// imaginary dir does not have any ts files
@@ -309,7 +290,7 @@ void describe('Sandbox using local project name resolver', () => {
309290
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox(
310291
{
311292
executor: sandboxExecutor,
312-
cfnClient: cfnClientMock,
293+
ssmClient: ssmClientMock,
313294
},
314295
{
315296
dir: testDir,
@@ -335,7 +316,7 @@ void describe('Sandbox using local project name resolver', () => {
335316
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox(
336317
{
337318
executor: sandboxExecutor,
338-
cfnClient: cfnClientMock,
319+
ssmClient: ssmClientMock,
339320
},
340321
{
341322
dir: 'testDir',
@@ -364,13 +345,13 @@ void describe('Sandbox using local project name resolver', () => {
364345
validateAppSources: true,
365346
},
366347
]);
367-
assert.strictEqual(cfnClientSendMock.mock.callCount(), 0);
348+
assert.strictEqual(ssmClientSendMock.mock.callCount(), 0);
368349
});
369350

370351
void it('calls watcher subscribe with the default "./amplify" if no `dir` specified', async () => {
371352
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox({
372353
executor: sandboxExecutor,
373-
cfnClient: cfnClientMock,
354+
ssmClient: ssmClientMock,
374355
}));
375356
await fileChangeEventCallback(null, [
376357
{ type: 'update', path: 'foo/test1.ts' },
@@ -383,7 +364,7 @@ void describe('Sandbox using local project name resolver', () => {
383364
void it('calls BackendDeployer only once when multiple file changes are present', async () => {
384365
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox({
385366
executor: sandboxExecutor,
386-
cfnClient: cfnClientMock,
367+
ssmClient: ssmClientMock,
387368
}));
388369
await fileChangeEventCallback(null, [
389370
{ type: 'update', path: 'foo/test2.ts' },
@@ -403,7 +384,7 @@ void describe('Sandbox using local project name resolver', () => {
403384
void it('skips type checking if no typescript change is detected', async () => {
404385
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox({
405386
executor: sandboxExecutor,
406-
cfnClient: cfnClientMock,
387+
ssmClient: ssmClientMock,
407388
}));
408389
await fileChangeEventCallback(null, [
409390
{ type: 'update', path: 'foo/test2.txt' },
@@ -423,7 +404,7 @@ void describe('Sandbox using local project name resolver', () => {
423404
void it('calls BackendDeployer once when multiple file changes are within few milliseconds (debounce)', async () => {
424405
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox({
425406
executor: sandboxExecutor,
426-
cfnClient: cfnClientMock,
407+
ssmClient: ssmClientMock,
427408
}));
428409
// Not awaiting for this file event to be processed and submitting another one right away
429410
const firstFileChange = fileChangeEventCallback(null, [
@@ -449,7 +430,7 @@ void describe('Sandbox using local project name resolver', () => {
449430
void it('waits for file changes after completing a deployment and deploys again', async () => {
450431
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox({
451432
executor: sandboxExecutor,
452-
cfnClient: cfnClientMock,
433+
ssmClient: ssmClientMock,
453434
}));
454435
await fileChangeEventCallback(null, [
455436
{ type: 'update', path: 'foo/test5.ts' },
@@ -479,7 +460,7 @@ void describe('Sandbox using local project name resolver', () => {
479460
void it('queues deployment if a file change is detected during an ongoing deployment', async () => {
480461
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox({
481462
executor: sandboxExecutor,
482-
cfnClient: cfnClientMock,
463+
ssmClient: ssmClientMock,
483464
}));
484465
// Mimic BackendDeployer taking 200 ms.
485466
backendDeployerDeployMock.mock.mockImplementationOnce(async () => {
@@ -524,7 +505,7 @@ void describe('Sandbox using local project name resolver', () => {
524505
void it('calls BackendDeployer destroy when delete is called', async () => {
525506
({ sandboxInstance } = await setupAndStartSandbox({
526507
executor: sandboxExecutor,
527-
cfnClient: cfnClientMock,
508+
ssmClient: ssmClientMock,
528509
}));
529510
await sandboxInstance.delete({});
530511

@@ -541,7 +522,7 @@ void describe('Sandbox using local project name resolver', () => {
541522
const mockListener = mock.fn();
542523
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox({
543524
executor: sandboxExecutor,
544-
cfnClient: cfnClientMock,
525+
ssmClient: ssmClientMock,
545526
}));
546527
sandboxInstance.on('successfulDeployment', mockListener);
547528
const contextualBackendDeployerMock = contextual.mock.method(
@@ -578,7 +559,7 @@ void describe('Sandbox using local project name resolver', () => {
578559
void it('handles UpdateNotSupported error while deploying and offers to reset sandbox and customer says yes', async (contextual) => {
579560
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox({
580561
executor: sandboxExecutor,
581-
cfnClient: cfnClientMock,
562+
ssmClient: ssmClientMock,
582563
}));
583564
const contextualBackendDeployerMock = contextual.mock.method(
584565
backendDeployer,
@@ -621,7 +602,7 @@ void describe('Sandbox using local project name resolver', () => {
621602
void it('handles UpdateNotSupported error while deploying and offers to reset sandbox and customer says no, continues running sandbox', async (contextual) => {
622603
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox({
623604
executor: sandboxExecutor,
624-
cfnClient: cfnClientMock,
605+
ssmClient: ssmClientMock,
625606
}));
626607
const contextualBackendDeployerMock = contextual.mock.method(
627608
backendDeployer,
@@ -669,7 +650,7 @@ void describe('Sandbox using local project name resolver', () => {
669650
({ sandboxInstance } = await setupAndStartSandbox(
670651
{
671652
executor: sandboxExecutor,
672-
cfnClient: cfnClientMock,
653+
ssmClient: ssmClientMock,
673654
},
674655
{
675656
dir: 'testDir',
@@ -694,7 +675,7 @@ void describe('Sandbox using local project name resolver', () => {
694675
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox(
695676
{
696677
executor: sandboxExecutor,
697-
cfnClient: cfnClientMock,
678+
ssmClient: ssmClientMock,
698679
},
699680
{ identifier: 'customSandboxName' }
700681
));
@@ -724,7 +705,7 @@ void describe('Sandbox using local project name resolver', () => {
724705
({ sandboxInstance } = await setupAndStartSandbox(
725706
{
726707
executor: sandboxExecutor,
727-
cfnClient: cfnClientMock,
708+
ssmClient: ssmClientMock,
728709
},
729710
{ identifier: 'customSandboxName' }
730711
));
@@ -759,7 +740,7 @@ void describe('Sandbox using local project name resolver', () => {
759740
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox(
760741
{
761742
executor: sandboxExecutor,
762-
cfnClient: cfnClientMock,
743+
ssmClient: ssmClientMock,
763744
},
764745
{
765746
exclude: ['customer_exclude1', 'customer_exclude2'],
@@ -804,7 +785,7 @@ void describe('Sandbox using local project name resolver', () => {
804785
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox(
805786
{
806787
executor: sandboxExecutor,
807-
cfnClient: cfnClientMock,
788+
ssmClient: ssmClientMock,
808789
},
809790
{
810791
exclude: ['customer_exclude1', 'customer_exclude2'],
@@ -836,7 +817,7 @@ void describe('Sandbox using local project name resolver', () => {
836817
const mockListener = mock.fn();
837818
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox({
838819
executor: sandboxExecutor,
839-
cfnClient: cfnClientMock,
820+
ssmClient: ssmClientMock,
840821
}));
841822

842823
sandboxInstance.on('successfulDeployment', mockListener);
@@ -852,7 +833,7 @@ void describe('Sandbox using local project name resolver', () => {
852833
const mockListener = mock.fn();
853834
({ sandboxInstance, fileChangeEventCallback } = await setupAndStartSandbox({
854835
executor: sandboxExecutor,
855-
cfnClient: cfnClientMock,
836+
ssmClient: ssmClientMock,
856837
}));
857838

858839
sandboxInstance.on('successfulDeletion', mockListener);
@@ -866,7 +847,7 @@ void describe('Sandbox using local project name resolver', () => {
866847
await setupAndStartSandbox(
867848
{
868849
executor: sandboxExecutor,
869-
cfnClient: cfnClientMock,
850+
ssmClient: ssmClientMock,
870851
},
871852
{ watchForChanges: false }
872853
);
@@ -894,7 +875,7 @@ const setupAndStartSandbox = async (
894875
type: 'sandbox',
895876
}),
896877
testData.executor,
897-
testData.cfnClient,
878+
testData.ssmClient,
898879
printer as unknown as Printer,
899880
testData.open ?? _open
900881
);
@@ -909,7 +890,7 @@ const setupAndStartSandbox = async (
909890
// Reset all the calls to avoid extra startup call
910891
backendDeployerDestroyMock.mock.resetCalls();
911892
backendDeployerDeployMock.mock.resetCalls();
912-
cfnClientSendMock.mock.resetCalls();
893+
ssmClientSendMock.mock.resetCalls();
913894
listSecretMock.mock.resetCalls();
914895
}
915896

@@ -946,6 +927,6 @@ type SandboxTestData = {
946927
// To instantiate sandbox
947928
sandboxName?: string;
948929
executor: AmplifySandboxExecutor;
949-
cfnClient: CloudFormationClient;
930+
ssmClient: SSMClient;
950931
open?: typeof _open;
951932
};

0 commit comments

Comments
 (0)