aws-amplify / amplify-backend

Home to all tools related to Amplify's code-first DX (Gen 2) for building fullstack apps on AWS
Apache License 2.0
173 stars 60 forks source link

Deploying an EdgeFunction bound to a CloudFront distribution with a Storage bucket as origin (Pass props for root Stack) #1663

Open renevatium opened 4 months ago

renevatium commented 4 months ago

Environment information

System:
  OS: Linux 5.15 Ubuntu 20.04.5 LTS (Focal Fossa)
  CPU: (16) x64 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz
  Memory: 12.83 GB / 15.51 GB
  Shell: /bin/bash
Binaries:
  Node: 18.20.3 - ~/.nvm/versions/node/v18.20.3/bin/node
  Yarn: undefined - undefined
  npm: 10.7.0 - ~/.nvm/versions/node/v18.20.3/bin/npm
  pnpm: undefined - undefined
NPM Packages:
  @aws-amplify/backend: 1.0.0
  @aws-amplify/backend-cli: 1.0.1
  aws-amplify: 6.3.5
  aws-cdk: 2.140.0
  aws-cdk-lib: 2.140.0
  typescript: 5.4.5
AWS environment variables:
  AWS_STS_REGIONAL_ENDPOINTS = regional
  AWS_NODEJS_CONNECTION_REUSE_ENABLED = 1
  AWS_SDK_LOAD_CONFIG = 1
No CDK environment variables

Description

As the title says, I am trying to create and bind an EdgeFunction to a CloudFront distribution with a Storage S3 bucket (from defineStorage) as origin, through the Amplify backend (from defineBackend).

I tried to use the root node (App) from one of the generated backend resources as the root Construct on a secondary Stack. This is necessary because the us-east-1 region must be explicitly set on the Stack for CDK to accept the EdgeFunction:

Caused By: Error: stacks which use EdgeFunctions must have an explicitly set region
    at EdgeFunction.createCrossRegionFunction (/home/path-to-project/node_modules/aws-cdk-lib/aws-cloudfront/lib/experimental/edge-function.js:1:7079)

Note: The above error would go away with something like aws-cdk/issues/15620, but I don't think that would actually solve the underlying problem. Unless the EdgeFunction deployment would work without the explicit us-east-1 region set, and this is just a semantic check.

Anyway, the EdgeFunction deploys correctly but I can't grant IAM Resource permissions to the function from graphqlApi:

Caused By: Error: Stack "uniqueStackName" cannot reference {sandbox-path/data/amplifyData/GraphQLAPI/Resource[ApiId]} in stack "sandbox-stack/data". Cross stack references are only supported for stacks deployed to the same environment or between nested stacks and their parent stack. Set crossRegionReferences=true to enable cross region references
    at resolveValue (/home/path-to-project/node_modules/aws-cdk-lib/core/lib/private/refs.js:1:2612)

...or set the origin of the CloudFront distribution to the Storage bucket:

Caused By: Error: Resolution error: Resolution error: Resolution error: Resolution error: Cannot use resource 'sandbox-path/storage/appEdgeFunctionStorageCloudFrontOrigin' in a cross-environment fashion, the resource's physical name must be explicit set or use `PhysicalName.GENERATE_IF_NEEDED`.

And if I try to set crossRegionReferences on my secondary Stack:

Cross stack/region references are only supported for stacks with an explicit region defined.
    at resolveValue (/home/path-to-project/node_modules/aws-cdk-lib/core/lib/private/refs.js:1:3420)

Which is a strange error message but I think refers to the fact that the root Stack and therefore all NestedStacks (auth, data, etc) have an undefined region.

There doesn't seem to be a way to pass props to the BackendFactory from defineBackend so I can't explicitly set the region at the top level. I could be missing something but the ability to pass custom props to the root Stack would be a possible solution. Currently the whole tree seems to be initialized as the default value of the app arg in createDefaultStack. Explicitly setting the root Stack region to us-east-1 would allow for the EdgeFunction etc to be deployed in a NestedStack (from backend.createStack) - NestedStacks inherit their region from their parent Stack.

I know that the NextJS version leverages EdgeFunctions for SSR so maybe that deployment backend could be exposed, as an alternate solution, but that's a big maybe as I can't find public code associated with that process.

I'm posting this now in case I've completely missed something obvious and someone can point me in the right direction, but in the meantime I will also start working on a contribution that allows for custom props to be passed to the root Stack. That said, there might be a good reason we can't pass props like region to the root stack due to how Amplify handles the deployment 🤔

For reference this is the relevant slice from backend.ts (immediately after defineBackend):

const apiStack = new Stack(
  backend.data.node.root,
  'uniqueStackName',
  {
    env: {
      region: 'us-east-1'
    },
    //crossRegionReferences: true,
  }
)

const appEdgeFunctionRole = new Role(apiStack, 'appEdgeFunctionRole', {
  roleName: PhysicalName.GENERATE_IF_NEEDED,
  assumedBy: new CompositePrincipal(
    new ServicePrincipal('lambda.amazonaws.com'),
    new ServicePrincipal('edgelambda.amazonaws.com'),
  ),
  managedPolicies: [
    ManagedPolicy.fromAwsManagedPolicyName('AmazonS3FullAccess'),
    ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLogsFullAccess'),
  ],
})

const handlerPath = url.fileURLToPath(new URL('./custom/edge/', import.meta.url))

const appEdgeFunction = new cloudfront.experimental.EdgeFunction(apiStack, 'appEdgeFunction', {
  code: lambda.Code.fromAsset(handlerPath),
  handler: 'index.handler',
  runtime: lambda.Runtime.NODEJS_18_X,
  role: appEdgeFunctionRole,
})

backend.data.resources.graphqlApi.grant(appEdgeFunctionRole, appsync.IamResource.all(), 'appsync:graphql')

new cloudfront.Distribution(apiStack, 'appEdgeFunctionStorageCloudFront', {
  defaultBehavior: {
    origin: new S3Origin(backend.storage.resources.bucket),
    edgeLambdas: [
      {
        functionVersion: appEdgeFunction.currentVersion,
        eventType: cloudfront.LambdaEdgeEventType.VIEWER_REQUEST,
      }
    ],
  },
})
ykethan commented 3 months ago

Marking this feature request to expand the backend props