diff --git a/packages/cli/src/commands/lambda/policies.test.ts b/packages/cli/src/commands/lambda/policies.test.ts index af99dd6a9..d67b1a2b0 100644 --- a/packages/cli/src/commands/lambda/policies.test.ts +++ b/packages/cli/src/commands/lambda/policies.test.ts @@ -32,6 +32,10 @@ describe("policies — required actions", () => { "lambda:CreateFunction", "states:StartExecution", "s3:PutObject", + // SAM's --resolve-s3 managed bucket is encrypted; the deploy user must + // be able to set/read that encryption or the first deploy rolls back. + "s3:PutEncryptionConfiguration", + "s3:GetEncryptionConfiguration", "iam:CreateRole", "logs:CreateLogGroup", "cloudwatch:PutMetricAlarm", diff --git a/packages/cli/src/commands/lambda/policies.ts b/packages/cli/src/commands/lambda/policies.ts index d2bbddb79..9514ff24d 100644 --- a/packages/cli/src/commands/lambda/policies.ts +++ b/packages/cli/src/commands/lambda/policies.ts @@ -123,12 +123,18 @@ const REQUIRED_ACTIONS = { "s3:GetBucketPolicy", "s3:GetBucketTagging", "s3:GetBucketVersioning", + // SAM's `--resolve-s3` managed bucket (aws-sam-cli-managed-default) sets + // default SSE encryption; CloudFormation reads it on update/drift. Without + // Get/PutEncryptionConfiguration the first `lambda deploy` 403s creating + // that bucket and the managed stack rolls back. + "s3:GetEncryptionConfiguration", "s3:GetLifecycleConfiguration", "s3:ListAllMyBuckets", "s3:ListBucket", "s3:PutBucketPolicy", "s3:PutBucketTagging", "s3:PutBucketVersioning", + "s3:PutEncryptionConfiguration", "s3:PutLifecycleConfiguration", "s3:PutPublicAccessBlock", ], diff --git a/packages/cli/src/commands/lambda/sam.ts b/packages/cli/src/commands/lambda/sam.ts index c31f18de4..e2b2af544 100644 --- a/packages/cli/src/commands/lambda/sam.ts +++ b/packages/cli/src/commands/lambda/sam.ts @@ -106,7 +106,14 @@ export function samDeploy(opts: DeployOptions): void { const samDir = join(opts.repoRoot, "examples", "aws-lambda"); const result = spawnSync("sam", args, { cwd: samDir, stdio: opts.stdio ?? "inherit" }); if (result.status !== 0) { - throw new Error(`[lambda] sam deploy exited with code ${result.status ?? "unknown"}`); + throw new Error( + `[lambda] sam deploy exited with code ${result.status ?? "unknown"}\n` + + `If a prior attempt left a stack in ROLLBACK_COMPLETE, CloudFormation can't reuse it. ` + + `Delete it before retrying:\n` + + ` aws cloudformation delete-stack --stack-name aws-sam-cli-managed-default --region ${opts.region}\n` + + ` aws cloudformation delete-stack --stack-name ${opts.stackName} --region ${opts.region}\n` + + `(the first is SAM's managed artifacts stack from --resolve-s3; the second is the render stack).`, + ); } }