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

How to download S3 data in Lambda function #526

Open
mgreiner79 opened this issue Feb 12, 2025 · 0 comments
Open

How to download S3 data in Lambda function #526

mgreiner79 opened this issue Feb 12, 2025 · 0 comments
Assignees
Labels
question Further information is requested

Comments

@mgreiner79
Copy link

mgreiner79 commented Feb 12, 2025

Amplify CLI Version

12.14.2

Amplify Version

^6.12.3

Description of use case

I have a lambda function. That lambda function should be able to read data from an S3 bucket. How do I pass the bucket name to the lambda function?

I have a lambda function defined like this

// amplify/functions/myFunction/resource.ts
import { defineFunction } from '@aws-amplify/backend';

export const myFunction = defineFunction({
  name: 'my-function',
  entry: './handler.ts',
  environment: {
      API_URL: 'http://some_url.com'
   }
});
// amplify/data/resource.ts
import { type ClientSchema, a, defineData } from '@aws-amplify/backend';
import { myFunction } from '../functions/myFunction/resource';

const schema = a.schema({
  myFunction: a
    .query()
    .arguments({
      objectKey: a.string(),
    })
    .returns(a.string())
    .handler(a.handler.function(myFunction)),
});

export type Schema = ClientSchema<typeof schema>;

export const data = defineData({
  schema,
  authorizationModes: {
    defaultAuthorizationMode: 'userPool',
    apiKeyAuthorizationMode: {
      expiresInDays: 30,
    },
  },
});

I have my storage defined like this

// amplify/storage/resource.ts

import { defineStorage } from '@aws-amplify/backend';
import { myFunction } from '../functions/myFunction/resource'

export const storage = defineStorage({
  name: 'my-bucket',
  access: (allow) => ({
    'media/*': [
      allow.resource(myFunction).to(['read', 'write'])
    ],
  })
});

Note, that in order to give my lambda function permissions to access the S3 bucket, I need to import my function into my storage definition. This means that I cannot import storage into my function definition to pass the bucket name as an environment variable, because that would be a cyclic dependency.

So instead, I try adding the environement variable to the lamdba function in the backend definition, like this:

// amplify/backend.ts
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data } from './data/resource';
import { storage } from './storage/resource';
import { myFunction } from './functions/myFunction/resource';

const backend = defineBackend({
  auth,
  data,
  myFunction,
  storage,
});

backend.myFunction.addEnvironment("BUCKET_NAME", backend.storage.resources.bucket.bucketName)
backend.myFunction.addEnvironment("BUCKET_REGION", backend.storage.stack.region)

I can see after doing this, in the AWS lambda console, the environment variable BUCKET_NAME is set.
However, when I try to use it in my handler, by passing it to the downloadData function, like this:

// amplify/function/myFunction/handler.ts
import { Amplify } from 'aws-amplify';
import { generateClient } from 'aws-amplify/data';
import { downloadData } from 'aws-amplify/storage';
import type { Schema } from '../../data/resource';
import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime';
import { env } from '$amplify/env/my-function'

const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig(env);

Amplify.configure(resourceConfig, libraryOptions);

const apiUrl = env.API_URL; // This works
const bucketName = process.env.BUCKET_NAME ?? ''
const bucketRegion = process.env.BUCKET_REGION ?? ''

export const handler: Schema['myFunction']['functionHandler'] = async (
  event,
) => {
    const { objectKey } = event.arguments;

    const s3Result = await downloadData({
          path: objectKey,
          options: {
            bucket: {
                bucketName: bucketName,
                region: bucketRegion
          }
        }).result;
...

Note that I cannot use env.BUCKET_NAME to access the parameter, because it does not exist on env (presumably because it was not provided under environment property during the defineFunction call).

While this works, I don't think it is how the Amplify Team intended it to work.
If I do not provide the buckeName to the downloadData function, then I get this error:
NoBucket: Missing bucket name while accessing object
According to the (Amplify docs)[https://docs.amplify.aws/javascript/build-a-backend/storage/download-files/] some default bucket should be used if I do tno provide one. But it is unclear how the config for this default bucket is to be passed into the lambda handler.

How am I supposed to be importing my bucket configuration into the lamdba handler?

@chrisbonifacio chrisbonifacio self-assigned this Feb 12, 2025
@chrisbonifacio chrisbonifacio added the question Further information is requested label Feb 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants