Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: detect deploymentType from Stack Tags #2116

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/purple-dodos-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@aws-amplify/deployed-backend-client': patch
---

fix: detect deploymentType from Stack Tags
31 changes: 7 additions & 24 deletions packages/deployed-backend-client/src/deployed_backend_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ import {
ListBackendsResponse,
} from './deployed_backend_client_factory.js';
import { BackendIdentifierConversions } from '@aws-amplify/platform-core';
import {
BackendOutputClient,
BackendOutputClientError,
BackendOutputClientErrorType,
} from './backend_output_client_factory.js';
import { BackendOutputClient } from './backend_output_client_factory.js';
import {
CloudFormationClient,
DeleteStackCommand,
Expand Down Expand Up @@ -158,26 +154,13 @@ export class DefaultDeployedBackendClient implements DeployedBackendClient {
private tryGetDeploymentType = async (
stackSummary: StackSummary
): Promise<DeploymentType | undefined> => {
const backendIdentifier = {
stackName: stackSummary.StackName as string,
};
const stackDescription = await this.cfnClient.send(
new DescribeStacksCommand({ StackName: stackSummary.StackName })
);

try {
const backendOutput: BackendOutput =
await this.backendOutputClient.getOutput(backendIdentifier);

return backendOutput[platformOutputKey].payload
.deploymentType as DeploymentType;
} catch (error) {
if (
(error as BackendOutputClientError).code ===
BackendOutputClientErrorType.METADATA_RETRIEVAL_ERROR
) {
// Ignore stacks where metadata cannot be retrieved. These are not Amplify stacks, or not compatible with this library.
return;
}
throw error;
}
return stackDescription.Stacks?.[0].Tags?.find(
(tag) => tag.Key === 'amplify:deployment-type'
)?.Value as DeploymentType;
Comment on lines +157 to +163
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only functional change, rest are all test changes.

};

private listStacks = async (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,9 @@ import {
ListStacksCommand,
StackStatus,
} from '@aws-sdk/client-cloudformation';
import { platformOutputKey } from '@aws-amplify/backend-output-schemas';
import { DefaultBackendOutputClient } from './backend_output_client.js';
import { DefaultDeployedBackendClient } from './deployed_backend_client.js';
import { BackendStatus } from './deployed_backend_client_factory.js';
import {
BackendOutputClientError,
BackendOutputClientErrorType,
StackIdentifier,
} from './index.js';
import { AmplifyClient } from '@aws-sdk/client-amplify';
import { S3 } from '@aws-sdk/client-s3';
import { DeployedResourcesEnumerator } from './deployed-backend-client/deployed_resources_enumerator.js';
Expand All @@ -34,14 +28,6 @@ const listStacksMock = {
],
};

const getOutputMockResponse = {
[platformOutputKey]: {
payload: {
deploymentType: 'branch',
},
},
};

void describe('Deployed Backend Client list delete failed stacks', () => {
const mockCfnClient = new CloudFormation();
const mockS3Client = new S3();
Expand All @@ -56,9 +42,19 @@ void describe('Deployed Backend Client list delete failed stacks', () => {
const matchingStack = listStacksMock.StackSummaries.find((stack) => {
return stack.StackName === request.input.StackName;
});
const stack = matchingStack;
// Add tags that are used to detect deployment type
return {
Stacks: [stack],
Stacks: [
{
...matchingStack,
Tags: [
{
Key: 'amplify:deployment-type',
Value: 'branch',
},
],
},
],
};
}
throw request;
Expand All @@ -83,23 +79,6 @@ void describe('Deployed Backend Client list delete failed stacks', () => {
mockCfnClient,
new AmplifyClient()
);
const getOutputMock = mock.method(
mockBackendOutputClient,
'getOutput',
(backendIdentifier: StackIdentifier) => {
if (backendIdentifier.stackName === 'amplify-test-not-a-sandbox') {
return {
...getOutputMockResponse,
[platformOutputKey]: {
payload: {
deploymentType: 'branch',
},
},
};
}
return getOutputMockResponse;
}
);
const returnedDeleteFailedStacks = [
{
deploymentType: 'branch',
Expand All @@ -116,7 +95,6 @@ void describe('Deployed Backend Client list delete failed stacks', () => {
];

beforeEach(() => {
getOutputMock.mock.resetCalls();
listStacksMockFn.mock.resetCalls();
cfnClientSendMock.mock.resetCalls();
const deployedResourcesEnumerator = new DeployedResourcesEnumerator(
Expand Down Expand Up @@ -171,98 +149,4 @@ void describe('Deployed Backend Client list delete failed stacks', () => {

assert.equal(listStacksMockFn.mock.callCount(), 2);
});

void it('paginates listBackends when one page contains stacks, but it gets filtered due to not deleted failed status', async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test was duplicate of the one above.

listStacksMockFn.mock.mockImplementationOnce(() => {
return {
StackSummaries: [],
NextToken: 'abc',
};
});
const failedStacks = deployedBackendClient.listBackends({
deploymentType: 'branch',
backendStatusFilters: [BackendStatus.DELETE_FAILED],
});
assert.deepEqual(
(await failedStacks.getBackendSummaryByPage().next()).value,
returnedDeleteFailedStacks
);

assert.equal(listStacksMockFn.mock.callCount(), 2);
});

void it('paginates listBackends when one page contains stacks, but it gets filtered due to sandbox deploymentType', async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, this test was duplicate of the one above.

listStacksMockFn.mock.mockImplementationOnce(() => {
return {
StackSummaries: [],
NextToken: 'abc',
};
});
const failedStacks = deployedBackendClient.listBackends({
deploymentType: 'branch',
backendStatusFilters: [BackendStatus.DELETE_FAILED],
});
assert.deepEqual(
(await failedStacks.getBackendSummaryByPage().next()).value,
returnedDeleteFailedStacks
);

assert.equal(listStacksMockFn.mock.callCount(), 2);
});

void it('paginates listBackends when one page contains a stack, but it gets filtered due to not having gen2 outputs', async () => {
getOutputMock.mock.mockImplementationOnce(() => {
throw new BackendOutputClientError(
BackendOutputClientErrorType.METADATA_RETRIEVAL_ERROR,
'Test metadata retrieval error'
);
});
listStacksMockFn.mock.mockImplementationOnce(() => {
return {
StackSummaries: [
{
StackName: 'amplify-123-name-branch-testHash',
StackStatus: StackStatus.DELETE_FAILED,
CreationTime: new Date(0),
LastUpdatedTime: new Date(1),
},
],
NextToken: 'abc',
};
});
const failedStacks = deployedBackendClient.listBackends({
deploymentType: 'branch',
backendStatusFilters: [BackendStatus.DELETE_FAILED],
});
assert.deepEqual(
(await failedStacks.getBackendSummaryByPage().next()).value,
returnedDeleteFailedStacks
);

assert.equal(listStacksMockFn.mock.callCount(), 2);
});

void it('does not paginate listBackends when one page throws an unexpected error fetching gen2 outputs', async () => {
getOutputMock.mock.mockImplementationOnce(() => {
throw new Error('Unexpected Error!');
});
listStacksMockFn.mock.mockImplementationOnce(() => {
return {
StackSummaries: [
{
StackName: 'amplify-123-name-branch-testHash',
StackStatus: StackStatus.DELETE_FAILED,
CreationTime: new Date(0),
LastUpdatedTime: new Date(1),
},
],
NextToken: 'abc',
};
});
const listBackendsPromise = deployedBackendClient.listBackends({
deploymentType: 'branch',
backendStatusFilters: [BackendStatus.DELETE_FAILED],
});
await assert.rejects(listBackendsPromise.getBackendSummaryByPage().next());
});
});
Loading
Loading