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: remove parameters when resource is deleted or unlinked #12544

Merged
merged 6 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,22 @@ import {
getAppId,
getProjectMeta,
initJSProjectWithProfile,
removeFunction,
} from '@aws-amplify/amplify-e2e-core';
import { addEnvironmentCarryOverEnvVars, checkoutEnvironment, removeEnvironment } from '../environment/env';

describe('upload and delete parameters', () => {
let projRoot: string;
let deletionRequired = false;
beforeEach(async () => {
projRoot = await createNewProjectDir('upload-delete-parameters-test');
});

afterEach(async () => {
// In case test failed after init, but before delete
if (deletionRequired) {
await deleteProject(projRoot);
}
deleteProjectDir(projRoot);
});

Expand All @@ -28,7 +34,7 @@ describe('upload and delete parameters', () => {
const envVariableName = 'envVariableName';
const envVariableValue = 'envVariableValue';
await initJSProjectWithProfile(projRoot, { disableAmplifyAppCreation: false, envName: firstEnvName });

deletionRequired = true;
const meta = getProjectMeta(projRoot);
expect(meta).toBeDefined();
const appId = getAppId(projRoot);
Expand All @@ -37,6 +43,8 @@ describe('upload and delete parameters', () => {
expect(region).toBeDefined();

const fnName = `parameterstestfn${generateRandomShortId()}`;
const fnName2 = `parameterstestfn${generateRandomShortId()}`;

await addFunction(
projRoot,
{
Expand All @@ -49,23 +57,32 @@ describe('upload and delete parameters', () => {
},
'nodejs',
);

await amplifyPushAuth(projRoot);
const expectedParamsAfterAddFunc = [
{ name: 'deploymentBucketName' },
{ name: envVariableName, value: envVariableValue },
{ name: 's3Key' },
];
const deploymentBucketAndKey = [{ name: 'deploymentBucketName' }, { name: 's3Key' }];
const expectedParamsAfterAddFunc = [...deploymentBucketAndKey, { name: envVariableName, value: envVariableValue }];
await expectParametersOptionalValue(expectedParamsAfterAddFunc, [], region, appId, firstEnvName, 'function', fnName);

await addEnvironmentCarryOverEnvVars(projRoot, { envName: secondEnvName });
await addFunction(projRoot, { name: fnName2, functionTemplate: 'Hello World' }, 'nodejs');
await amplifyPushAuth(projRoot);
const expectedParamsAfterAddEnv = [
{ name: 'deploymentBucketName' },
{ name: envVariableName, value: envVariableValue },
{ name: 's3Key' },
];
const expectedParamsAfterAddEnv = [...deploymentBucketAndKey, { name: envVariableName, value: envVariableValue }];
await expectParametersOptionalValue(expectedParamsAfterAddFunc, [], region, appId, firstEnvName, 'function', fnName);
await expectParametersOptionalValue(expectedParamsAfterAddEnv, [], region, appId, secondEnvName, 'function', fnName);
await expectParametersOptionalValue(deploymentBucketAndKey, [], region, appId, secondEnvName, 'function', fnName2);

await removeFunction(projRoot, fnName2);
await amplifyPushAuth(projRoot);

await expectParametersOptionalValue(
[],
deploymentBucketAndKey.map((pair) => pair.name),
region,
appId,
secondEnvName,
'function',
fnName2,
);

await checkoutEnvironment(projRoot, { envName: firstEnvName });
await removeEnvironment(projRoot, { envName: secondEnvName });
Expand All @@ -82,6 +99,7 @@ describe('upload and delete parameters', () => {
);

await deleteProject(projRoot);
deletionRequired = false;
await expectParametersOptionalValue(
[],
expectedParamsAfterAddFunc.map((pair) => pair.name),
Expand Down
1 change: 0 additions & 1 deletion packages/amplify-provider-awscloudformation/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ import { prePushCfnTemplateModifier } from './pre-push-cfn-processor/pre-push-cf
import { getApiKeyConfig } from './utils/api-key-helpers';
import { deleteEnvironmentParametersFromService } from './utils/ssm-utils/delete-ssm-parameters';
export { deleteEnvironmentParametersFromService } from './utils/ssm-utils/delete-ssm-parameters';

import { getEnvParametersUploadHandler, getEnvParametersDownloadHandler } from './utils/ssm-utils/env-parameter-ssm-helpers';
export {
getEnvParametersUploadHandler,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ import { storeRootStackTemplate } from './initializer';
import { transformRootStack } from './override-manager';
import { prePushTemplateDescriptionHandler } from './template-description-utils';
import { buildOverridesEnabledResources } from './build-override-enabled-resources';

import { deleteEnvironmentParametersForResources } from './utils/ssm-utils/delete-ssm-parameters';
import { invokePostPushAnalyticsUpdate } from './plugin-client-api-analytics';
import { printCdkMigrationWarning } from './print-cdk-migration-warning';
import { minifyJSONFile } from './utils/minify-json';
Expand Down Expand Up @@ -480,7 +480,10 @@ export const run = async (context: $TSContext, resourceDefinition: $TSObject, re
.map(({ category, resourceName }) => context.amplify.removeDeploymentSecrets(context, category, resourceName));

await adminModelgen(context, resources);

await deleteEnvironmentParametersForResources(context, context.amplify.getEnvInfo().envName, [
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
await deleteEnvironmentParametersForResources(context, context.amplify.getEnvInfo().envName, [
await deleteEnvironmentParametersForResources(context, [

envInfo we can fetch inside the function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That would limit the use of the function to the current environment. I updated to use stateManager to resolve the current envName.

...resourcesToBeDeleted,
...resourcesToBeSynced.filter((r) => r.sync === 'unlink'),
]);
await displayHelpfulURLs(context, resources);
} catch (error) {
if (iterativeDeploymentWasInvoked) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { $TSContext, AmplifyFault } from '@aws-amplify/amplify-cli-core';
import { $TSContext, AmplifyFault, IAmplifyResource } from '@aws-amplify/amplify-cli-core';
import { printer } from '@aws-amplify/amplify-prompts';
import type { SSM as SSMType } from 'aws-sdk';
import { SSM } from '../../aws-utils/aws-ssm';
Expand All @@ -7,7 +7,7 @@ import { executeSdkPromisesWithExponentialBackOff } from './exp-backoff-executor
import { getSsmSdkParametersDeleteParameters, getSsmSdkParametersGetParametersByPath } from './get-ssm-sdk-parameters';

/**
* Delete CloudFormation parameters from the service
* Delete all CloudFormation parameters from the service for a given environment
*/
export const deleteEnvironmentParametersFromService = async (context: $TSContext, envName: string): Promise<void> => {
let appId;
Expand All @@ -18,16 +18,49 @@ export const deleteEnvironmentParametersFromService = async (context: $TSContext
return;
}
const { client } = await SSM.getInstance(context);
await deleteParametersFromParameterStore(appId, envName, client);
const envKeysInParameterStore = await getAllEnvParametersFromParameterStore(appId, envName, client);
await deleteParametersFromParameterStore(client, envKeysInParameterStore);
};

const deleteParametersFromParameterStore = async (appId: string, envName: string, ssmClient: SSMType): Promise<void> => {
/**
* Delete CloudFormation parameters from the service for an array of resources
*/
export const deleteEnvironmentParametersForResources = async (
context: $TSContext,
envName: string,
resources: IAmplifyResource[],
): Promise<void> => {
if (resources.length === 0) {
return;
}
let appId;
try {
const envKeysInParameterStore: Array<string> = await getAllEnvParametersFromParameterStore(appId, envName, ssmClient);
if (!envKeysInParameterStore.length) {
return;
appId = resolveAppId(context);
} catch {
printer.debug(`No AppId found when deleting parameters for environment ${envName}`);
return;
}
const { client } = await SSM.getInstance(context);
const envKeysInParameterStore = await getAllEnvParametersFromParameterStore(appId, envName, client);
const removedParameterKeys = envKeysInParameterStore.filter((key: string) => {
const keyAfterPaths = key.split('/').pop();
const [, categoryName, resourceName] = keyAfterPaths.split('_');
for (const resource of resources) {
if (resource.category === categoryName && resource.resourceName === resourceName) {
return true;
}
}
const chunkedKeys: Array<Array<string>> = chunkForParameterStore(envKeysInParameterStore);
return false;
});
await deleteParametersFromParameterStore(client, removedParameterKeys);
};

const deleteParametersFromParameterStore = async (ssmClient: SSMType, parameterKeys: string[]): Promise<void> => {
if (parameterKeys.length === 0) {
return;
}
try {
const chunkedKeys = chunkForParameterStore(parameterKeys);
const deleteKeysFromPSPromises = chunkedKeys.map((keys) => {
const ssmArgument = getSsmSdkParametersDeleteParameters(keys);
return () => ssmClient.deleteParameters(ssmArgument).promise();
Expand Down