2
votes

With the introduction of Firebase Cloud Functions we are looking at moving some of our current node.js server side code to cloud functions. One issue I am having is with downloading a file from a GCS bucket to a temp file on disk, and then emailing that as an attachment (using mailgun-js).

The piece of code causing me grief is:

return mkdirp(tempLocalDir).then(() => {
    const bucket = gcs.bucket(gcsBucket);
    const tempFilePath = tempLocalDir + gcsFile;
    return bucket.file(gcsFile).download({
        destination: tempFilePath
    }).then(() => {
        console.log('File downloaded locally to', tempFilePath);
        var messageSubject = "Test";
        var messageBody = "Test with attach";

        var mailgunData = {
            from: ,
            to: agentEmail,
            subject: messageSubject,
            html: messageBody,
            attachment: tempFilePath,
        };
        mailgunAgent.messages().send(mailgunData, function (error, body) {
            console.log(body);
        });

    });

});

The error message I'm getting in the Functions Logs is:

ApiError: Forbidden
    at Object.parseHttpRespMessage (/user_code/node_modules/@google-cloud/storage/node_modules/@google-cloud/common/src/util.js:156:33)
    at Object.handleResp (/user_code/node_modules/@google-cloud/storage/node_modules/@google-cloud/common/src/util.js:131:18)
    at Duplexify.<anonymous> (/user_code/node_modules/@google-cloud/storage/src/file.js:724:21)
    at emitOne (events.js:96:13)
    at Duplexify.emit (events.js:188:7)
    at emitOne (events.js:96:13)
    at DestroyableTransform.emit (events.js:188:7)
    at emitOne (events.js:96:13)
    at Request.emit (events.js:188:7)
    at Request.<anonymous> (/user_code/node_modules/@google-cloud/storage/node_modules/request/request.js:1108:14)

I'm been able to download the file to the /tmp/ folder on disk using request, and this will be the fallback option, but I'd really like to use the GCS tools if possible. I "think" it's an auth error with GCS, but I'm not sure how to track that down. Do I need to have different auth parameters in the cloud functions .config() for GCS than for Firebase? If so, how do I enter them? Our GCS bucket and project pre-date the introduction of Firebase Storage, but we've successfully used it with the node functions running on our server.

Thanks in advance, Zach

3

3 Answers

3
votes

SOLVED: Finally solved the above. Issue was the @google-cloud/storage auth needed the GCS project ID as the ID, but the Firebase admin-sdk keyfile, NOT the GCS project keyfile. Using the Firebase project ID as the ID in the auth for GCS with the Firebase keyfile failed as well.

¯_(ツ)_/¯

Might be a strange behaviour just in our project setup, but thought it relevant to update with what solved the problem for us.

The config that worked using the format provided by grll above is:

const gcs = require('@google-cloud/storage');
const storageClient = gcs({
    projectId: "this is the GCS project ID,
    keyFilename: 'this is the firebase admin-sdk keyFilename.json'
});
const bucket = storageClient.bucket(gcsBucket);
3
votes

Another way to solve this without attaching projectId or keyFilename:

  1. Go to your project's Firebase console
  2. Settings -> Permissions
  3. IAM tab should be opened
  4. Now give your 'App Engine default service account' and 'Google APIs service account' permissions for 'Storage admin' role
  5. Should be fine now!
0
votes

You need to identify before being able to use the google cloud storage API. Or any other API by the way.

One way to do that would be to go to your API logins page on the google cloud platform: here and download the corresponding service account key as a JSON file. Then put this file in your functions folder inside your firebase Project.

Finally instead of using:
const bucket = gcs.bucket(gcsBucket);

You would call this:

const gcs = require('@google-cloud/storage');

const storageClient = gcs({ projectId: projectId, keyFilename: './keyFilename.json' }); const bucket = storageClient.bucket(gcsBucket);
With your projectId and keyFilename.