From 24c9a3a26d0743242033ae4320d793ad068dd0ce Mon Sep 17 00:00:00 2001 From: "dgandhi62 (AI)" Date: Fri, 1 May 2026 12:30:23 -0400 Subject: [PATCH] fix(cli-internal): handle secrets in function definitions (#14517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a Gen1 function is configured with a secret, the CLI stores the secret value as an SSM SecureString parameter and sets the SSM path as the environment variable value. The generate command now detects env vars whose values match the SSM secret path pattern (/amplify///...) and converts them to Gen2's secret() API in the defineFunction() call. Previously, these env vars were emitted as literal strings containing the SSM path. Now they produce: import { defineFunction, secret } from '@aws-amplify/backend'; ... environment: { MY_SECRET: secret('MY_SECRET') } The existing API_KEY secret handling was a special case of this pattern and is now covered by the generalized detection. Updated the product-catalog golden snapshot which contains a PRODUCT_CATALOG_SECRET env var that triggers this code path. --- Prompt: Implement issue #14517 — generate command should handle secrets in function definitions by converting SSM SecureString parameter paths to Gen2 secret() API calls. --- .../function/lowstockproducts/resource.ts | 5 +- .../function/function.generator.test.ts | 60 +++++++++++++++++++ .../amplify/function/function.renderer.ts | 6 +- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/amplify-migration-apps/product-catalog/_snapshot.post.generate/amplify/function/lowstockproducts/resource.ts b/amplify-migration-apps/product-catalog/_snapshot.post.generate/amplify/function/lowstockproducts/resource.ts index 1f940607d11..45abb77d8bc 100644 --- a/amplify-migration-apps/product-catalog/_snapshot.post.generate/amplify/function/lowstockproducts/resource.ts +++ b/amplify-migration-apps/product-catalog/_snapshot.post.generate/amplify/function/lowstockproducts/resource.ts @@ -1,4 +1,4 @@ -import { defineFunction } from '@aws-amplify/backend'; +import { defineFunction, secret } from '@aws-amplify/backend'; import type { Backend } from '../../backend'; const branchName = process.env.AWS_BRANCH ?? 'sandbox'; @@ -12,8 +12,7 @@ export const lowstockproducts = defineFunction({ ENV: `${branchName}`, REGION: 'us-east-1', LOW_STOCK_THRESHOLD: '5', - PRODUCT_CATALOG_SECRET: - '/amplify/productcatalog/x/AMPLIFY_lowstockproducts_PRODUCT_CATALOG_SECRET', + PRODUCT_CATALOG_SECRET: secret('PRODUCT_CATALOG_SECRET'), }, runtime: 22, }); diff --git a/packages/amplify-cli/src/__tests__/commands/gen2-migration/generate/amplify/function/function.generator.test.ts b/packages/amplify-cli/src/__tests__/commands/gen2-migration/generate/amplify/function/function.generator.test.ts index 46f70701cf8..f14813939b2 100644 --- a/packages/amplify-cli/src/__tests__/commands/gen2-migration/generate/amplify/function/function.generator.test.ts +++ b/packages/amplify-cli/src/__tests__/commands/gen2-migration/generate/amplify/function/function.generator.test.ts @@ -445,6 +445,66 @@ describe('FunctionGenerator', () => { `); }); + it('renders SSM secret env vars as secret() calls', async () => { + const gen1App = await createGen1App({ + providers: { awscloudformation: { StackName: 'amplify-test-main-123456', Region: 'us-east-1' } }, + function: { + myFunc: { + service: 'Lambda', + output: { Name: 'myFunc-main-abc', Arn: 'arn:aws:lambda:us-east-1:123:function:myFunc-main-abc' }, + }, + }, + }); + jest.spyOn(gen1App, 'resourceMetaOutput').mockReturnValue('myFunc-main-abc'); + jest.spyOn(gen1App, 'json').mockReturnValue({ Resources: {} }); + jest.spyOn(gen1App, 'file').mockReturnValue('{}'); + jest.spyOn(gen1App, 'fileExists').mockReturnValue(false); + jest.spyOn(gen1App.aws, 'fetchFunctionConfig').mockResolvedValue({ + FunctionName: 'myFunc-main-abc', + Handler: 'index.handler', + Timeout: 3, + MemorySize: 128, + Runtime: 'nodejs18.x', + Environment: { + Variables: { + MY_SECRET: '/amplify/test-app-id/main/AMPLIFY_myFunc_MY_SECRET', + ANOTHER_SECRET: '/amplify/test-app-id/main/AMPLIFY_myFunc_ANOTHER_SECRET', + DB_HOST: 'localhost', + }, + }, + }); + jest.spyOn(gen1App.aws, 'fetchFunctionSchedule').mockResolvedValue(undefined); + + const generator = createFunctionGenerator({ gen1App, backendGenerator, packageJsonGenerator, outputDir }); + const ops = await generator.plan(); + await ops[0].execute(); + + expect(writtenFile('resource.ts')).toMatchInlineSnapshot(` + "import { defineFunction, secret } from '@aws-amplify/backend'; + import type { Backend } from '../../backend'; + + const branchName = process.env.AWS_BRANCH ?? 'sandbox'; + + export const myFunc = defineFunction({ + entry: './index.js', + name: \`myFunc-\${branchName}\`, + timeoutSeconds: 3, + memoryMB: 128, + environment: { + MY_SECRET: secret('MY_SECRET'), + ANOTHER_SECRET: secret('ANOTHER_SECRET'), + DB_HOST: 'localhost', + }, + runtime: 18, + }); + + export function applyEscapeHatches(backend: Backend) { + backend.myFunc.resources.cfnResources.cfnFunction.functionName = \`myFunc-\${branchName}\`; + } + " + `); + }); + it('renders environment variables', async () => { const gen1App = await createGen1App({ providers: { awscloudformation: { StackName: 'amplify-test-main-123456', Region: 'us-east-1' } }, diff --git a/packages/amplify-cli/src/commands/gen2-migration/generate/amplify/function/function.renderer.ts b/packages/amplify-cli/src/commands/gen2-migration/generate/amplify/function/function.renderer.ts index 7388512f5c9..c018c72e268 100644 --- a/packages/amplify-cli/src/commands/gen2-migration/generate/amplify/function/function.renderer.ts +++ b/packages/amplify-cli/src/commands/gen2-migration/generate/amplify/function/function.renderer.ts @@ -308,12 +308,14 @@ export class FunctionRenderer { ): void { if (!opts.literalEnvVars || Object.keys(opts.literalEnvVars).length === 0) return; + const ssmSecretPrefix = `/amplify/${this.appId}/${this.backendEnvironmentName}/`; + const envProps = Object.entries(opts.literalEnvVars).map(([key, value]) => { - if (key === 'API_KEY' && value.startsWith(`/amplify/${this.appId}/${this.backendEnvironmentName}`)) { + if (value.startsWith(ssmSecretPrefix)) { namedImports['@aws-amplify/backend'].add('secret'); return factory.createPropertyAssignment( key, - factory.createCallExpression(factory.createIdentifier('secret'), undefined, [factory.createStringLiteral('API_KEY')]), + factory.createCallExpression(factory.createIdentifier('secret'), undefined, [factory.createStringLiteral(key)]), ); }