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

[Amplify gen2] Receive Amplify Data parameters as environment variables in a Amplify Function #1715

Open
antennix opened this issue Jul 4, 2024 · 12 comments
Labels
data Issue pertaining to Amplify Data feature-request New feature or request function Issue pertaining to Amplify Function

Comments

@antennix
Copy link

antennix commented Jul 4, 2024

Environment information

System:
  OS: macOS 14.5
  CPU: (8) x64 Apple M2
  Memory: 32.73 MB / 16.00 GB
  Shell: Unknown
Binaries:
  Node: 18.18.0 - ~/.nvm/versions/node/v18.18.0/bin/node
  Yarn: undefined - undefined
  npm: 9.8.1 - ~/.nvm/versions/node/v18.18.0/bin/npm
  pnpm: 8.15.4 - ~/.nvm/versions/node/v18.18.0/bin/pnpm
NPM Packages:
  @aws-amplify/backend: 1.0.4
  @aws-amplify/backend-cli: 1.1.0
  aws-amplify: 6.3.8
  aws-cdk: 2.147.3
  aws-cdk-lib: 2.147.3
  typescript: 5.5.3
AWS environment variables:
  AWS_STS_REGIONAL_ENDPOINTS = regional
  AWS_NODEJS_CONNECTION_REUSE_ENABLED = 1
  AWS_SDK_LOAD_CONFIG = 1
No CDK environment variables

Description

Is it possible for a Function to receive the Endpoint of Data,
or the name and ARN of Datasources like DynamoDB?

For example, with Storage, you can use the "access" property in the defineStorage function to grant read and write permissions to a Function.
https://docs.amplify.aws/react/build-a-backend/functions/grant-access-to-other-resources/

However, a similar approach cannot be used with defineData as it's not possible to specify properties.

It is possible to directly set values for an already created function in backend.ts using CDK, but
if you're also using defineFunction for resolvers against Data,
I think an error would occur due to circular reference, as the reference order would be resolverFunction → Data → thisFunction.

const l1CloneFunction = backend.cloneFunction.resources.lambda.node
.defaultChild as CfnFunction;

l1CloneFunction.addPropertyOverride(
 "Environment.Variables.TARGET_USER_TABLE",
 backend.data.resources.tables.User.tableName ?? "",
);

Is there a good solution?

@antennix antennix added the pending-triage Incoming issues that need categorization label Jul 4, 2024
@edwardfoyle
Copy link
Contributor

Hi @antennix, you can grant a function access to the Data API by following these docs: https://docs.amplify.aws/react/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/

We are also actively investigating making the experience of configuring the data client in the context of a lambda function a better experience, so any feedback you have there is welcome!

@edwardfoyle edwardfoyle added question Question or confusion about some aspect of the product data Issue pertaining to Amplify Data function Issue pertaining to Amplify Function and removed pending-triage Incoming issues that need categorization labels Jul 8, 2024
@ykethan ykethan added the pending-response Issue is pending response from author label Jul 8, 2024
@antennix
Copy link
Author

@edwardfoyle
Thank you. I understand the current approach to Data (AppSync).
I'm hoping that the process of generating GraphQL code will become simpler.

On the other hand, is there an approach for when we want to access the data source (Dynamo) directly?

For example, executing a daily batch via Function
(I believe the scheduling feature will be implemented soon: #1491), and performing specific processing on DynamoDB records.

We could update records through AppSync, but I don't think we can do so without users who are executing Subscriptions noticing. I want to update silently.

@github-actions github-actions bot removed the pending-response Issue is pending response from author label Jul 10, 2024
@MyNameIsTakenOMG
Copy link

Hi, @antennix , I bumped into the same issue a couple of days ago when trying to pass the dynamoDB table name to my lambda hander as an env variable, and I did exact the same thing as you mentioned in the description section, which caused a circular dependencies issue.

Then, I joined in a discussion in another similar open issue #2554, where we believe this could be some sort of bug. But fortunately, other great devs came up with some workarounds, which I already tested in my case and they worked just fine. And I hope they could work for you too. Good luck!

@antennix
Copy link
Author

@MyNameIsTakenOMG
Thank you. I think the Lambda custom resource that indirectly retrieves the resource name is a very good idea.

@josefaidt josefaidt added the pending-triage Incoming issues that need categorization label Jul 24, 2024
@ykethan
Copy link
Member

ykethan commented Jul 30, 2024

Marking as feature request to provide Data parameters such as table name and arn as environment variables.

@ykethan ykethan added feature-request New feature or request and removed question Question or confusion about some aspect of the product pending-triage Incoming issues that need categorization labels Jul 30, 2024
@rpostulart
Copy link

rpostulart commented Sep 4, 2024

I have solved it by adding this to my backend.ts where Event is one of the resources I want access to

// Get the DynamoDB table name
const tableName = backend.data.resources.tables["Event"].tableName;
new cdk.CfnOutput(eventBridgeStack, "EventTableName", {
  value: tableName,
  description: "The imported name of the Event DynamoDB table",
  exportName: "EventTableName",
});

In the stack where I create the lambda function I use:


    // Define Lambda function
    this.lambdaFunction = new lambda.Function(this, "CheckEventsNextWeek", {
      runtime: lambda.Runtime.NODEJS_20_X,
      code: lambda.Code.fromAsset(
        "amplify/custom/services/checkEventsNextWeek/src"
      ),
      handler: "index.handler",
      environment: {
        DB_EVENT_TABLE_NAME: cdk.Fn.importValue("EventTableName"),
      },
    });

Inside the lambda you can the use:
process.env.DB_EVENT_TABLE_NAME

This works like charm

@antennix
Copy link
Author

antennix commented Sep 4, 2024

I understand the mechanism of preventing circular references by creating Lambda without using DefineFunction.
Thank you.

On the other hand,
I'd like to create Lambda using defineFunction to make integration with the Amplify system easier.

@bogris
Copy link

bogris commented Sep 7, 2024

my workaround is to:

  1. add custom keys to outpus:
be.addOutput({
  custom: {
    webhookUrl: functionUrl.url,
    stateMachineArn: notificationsStack.stateMachine.stateMachineArn,
  },
});
  1. import the output in lambda and use the value

but now we have a chicken and egg issue because the outputs are artefacts of the build...

  1. I pregenerate the outputs before be build:
    amplify.yml
version: 1
backend:
  phases:
    preBuild:
      commands:
        - npx ampx generate outputs --branch $AWS_BRANCH --app-id $AWS_APP_ID
    build:
      commands:
        - npm ci --cache .npm --prefer-offline
        - npx ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_ID

some things to note...

  • you need 2 deployments to get updates in the code
  • you may need some //@ts-ignore in code where you refference new keys in outputs that are to be deployed, as they will not be there at first deployment..

as we do text refs we won't have any cloudformation dependencies...

idk... hope for an official solution.

I am now thinking of using @rpostulart solution now... :)

hoping for an official stand :)

@pridapablo
Copy link

@rpostulart's solution works, but I encountered a significant issue with that approach after pushing to my main branch and attempting to start up my sandbox again:

Error:

Export with name EXPORT is already exported by stack ...

I resolved it by using a different approach: casting the imported function resource in backend.ts as aws_lambda.Function to enable the addEnvironment method, as suggested by Jesse Pangburn in this thread.

Here’s a code snippet:

const customResourceStack = backend.createStack("ProcessingStack");

export const embeddingQueue = new sqs.Queue(
  customResourceStack,
  "EmbeddingQueue"
);

// Add IAM and environment variables to the function
const createEmbeddingsFunction =
  backend.createEmbeddingsForLaws.resources.lambda as aws_lambda.Function; // CAST HERE!
embeddingQueue.grantSendMessages(createEmbeddingsFunction); // IAM
createEmbeddingsFunction.addEnvironment("SQS_QUEUE_URL", embeddingQueue.queueUrl); // ENV

Note: This may cause circular dependency issues, as explained here, but this applies primarily to DynamoDB, not SQS/SNS.

Also, when accessing the environment variable in the Lambda function, do not import the env from the .amplify/generated/env folder, as it won’t work. Instead, use process.env; the variable should be available there. Although it's not type-safe, it works.

@bogris
Copy link

bogris commented Sep 25, 2024

@pridapablo I got into a similar error but in my scenario I was trying to deploy 2 branches.

This is caused by the fact that cloudformation exports space si scoped to the account level

For me, the current workaround is to define a secret at the app level and put it in there manually... and before init a local sandbox, ... and after I deploy a new branch.
This gives you the option to have diff values per env.

My mentioned solution with importing outputs is also bad, as it only works with a previous deployment. as in: we can't do a new branch from scratch...

@zxkane
Copy link

zxkane commented Nov 29, 2024

@rpostulart How did you use the native CDK's Function managed out of defineFunction as a mutation's handler function?

// Define Lambda function
this.lambdaFunction = new lambda.Function(this, "CheckEventsNextWeek", {
  runtime: lambda.Runtime.NODEJS_20_X,
  code: lambda.Code.fromAsset(
    "amplify/custom/services/checkEventsNextWeek/src"
  ),
  handler: "index.handler",
  environment: {
    DB_EVENT_TABLE_NAME: cdk.Fn.importValue("EventTableName"),
  },
});

Inside the lambda you can the use: **process.env.DB_EVENT_TABLE_NAME**

This works like charm

@zxkane
Copy link

zxkane commented Nov 30, 2024

Without the Lambda resolver's built-in access to the table, the experience becomes much more cumbersome and time-consuming. I now manage the table manually in CDK as follows:

const externalTableStack = backend.createStack('ExternalTableStack');

const leagueTable = new Table(externalTableStack, 'League', {
  partitionKey: {
    name: 'id',
    type: AttributeType.STRING
  },
  billingMode: BillingMode.PAY_PER_REQUEST,
  removalPolicy: RemovalPolicy.DESTROY,
});

backend.data.addDynamoDbDataSource(
  "ExternalLeagueTableDataSource",
  leagueTable
);

leagueTable.grantReadWriteData(backend.leagueHandler.resources.lambda);
(backend.leagueHandler.resources.lambda.node.defaultChild as CfnFunction).addPropertyOverride('LoggingConfig', {
  LogFormat: 'JSON',
  ApplicationLogLevel: process.env.PRODUCTION ? 'WARN' : 'TRACE',
  SystemLogLevel: 'INFO',
});
(backend.leagueHandler.resources.lambda as NodejsFunction).addEnvironment('LEAGUE_TABLE_NAME', leagueTable.tableName);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
data Issue pertaining to Amplify Data feature-request New feature or request function Issue pertaining to Amplify Function
Projects
None yet
Development

No branches or pull requests

9 participants