【问题标题】:How to write to a cloud storage bucket with a firebase cloud function triggered from firestore?如何使用从firestore触发的firebase云功能写入云存储桶?
【发布时间】:2018-12-24 08:10:45
【问题描述】:

为了确保它尽可能干净和隔离,我创建了一个新的测试项目,除了这些步骤之外什么都没有。

然后我在控制台中启用了 Cloud Firestore。我创建了一个名为“blarg”的新系列(好名字,嗯!)。我向其中添加了一个文档,并使用了自动 ID,然后添加了一个名为“humpty”的字段和字符串 dumpty。在控制台中是这样的:

我在 Firebase 控制台的存储部分创建了一个名为 signatures 的目录。当我的函数(如下)触发时,我想在这个目录中写入一个文件。

然后我使用以下代码添加了一个云功能。它在函数部分正确显示(我假设),名称为testItOut,并在/blarg/{eventId} 的事件document.update 上触发。:

const functions = require('firebase-functions');
const os = require('os');
const fs = require('fs');
const path = require('path');
require('firebase-admin').initializeApp();

exports.testItOut = functions.firestore
    .document('blarg/{docId}')
    .onUpdate((change, context) => {
    console.log( "Inside #testItOut" );
    const projectId = 'testing-60477';
    const Storage = require('@google-cloud/storage');
    const storage = new Storage({
        projectId,
    });
    let bucketName = 'signatures'; 
    let fileName = 'temp.txt';
    const tempFilePath = path.join(os.tmpdir(), fileName);
    console.log( `Writing out to ${tempFilePath}` );
    fs.writeFileSync(tempFilePath, "something!" );

    return storage
        .bucket( bucketName )
        .upload( tempFilePath )
        .then( () => fs.unlinkSync(tempFilePath) )
        .catch(err => console.error('ERROR inside upload: ', err) );
    });

package.json 看起来像这样:

{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "lint": "eslint .",
    "serve": "firebase serve --only functions",
    "shell": "firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "dependencies": {
    "@google-cloud/storage": "^1.7.0",
    "firebase-admin": "~5.12.1",
    "firebase-functions": "^1.0.3"
  },
  "devDependencies": {
    "eslint": "^4.12.0",
    "eslint-plugin-promise": "^3.6.0"
  },
  "private": true
}

如果我更改键“humpty”的值,我会看到函数调用。但是,我在日志中得到了错误。

ERROR inside upload:  { ApiError: testing-60477@appspot.gserviceaccount.com does not have storage.objects.create access to signatures/temp.txt.
    at Object.parseHttpRespBody (/user_code/node_modules/@google-cloud/storage/node_modules/@google-cloud/common/src/util.js:193:30)
    at Object.handleResp (/user_code/node_modules/@google-cloud/storage/node_modules/@google-cloud/common/src/util.js:131:18)
...

我认为这很简单。我究竟做错了什么?我以为调用initializeApp() 会授予我的函数权限以自动写入帐户的存储桶?

我看到的唯一奇怪的错误是未设置结算。我认为只有在您使用外部 API 时才有必要这样做。 Google Storage 是“外部 API”吗?日志消息并未表明这是问题所在。

【问题讨论】:

    标签: firebase google-cloud-storage google-cloud-firestore google-cloud-functions


    【解决方案1】:

    问题是我错误地认为对bucket 的调用是在存储桶中设置子目录。而不是bucket('signatures'),我应该将其设为像bucket() 这样的空调用,然后为upload 调用提供一个选项参数(如{ destination: '/signatures/temp.txt' }):

    const functions = require('firebase-functions');
    const os = require('os');
    const fs = require('fs');
    const path = require('path');
    const admin = require('firebase-admin');
    admin.initializeApp();
    
    exports.testItOut = functions.firestore
        .document('blarg/{docId}')
        .onUpdate((change, context) => {
        console.log( "Inside #testItOut" );
        const storage = admin.storage()
        let fileName = 'temp.txt';
        let destination = '/signatures/temp.txt';
        const tempFilePath = path.join(os.tmpdir(), fileName);
        console.log( `Writing out to ${tempFilePath}` );
        fs.writeFileSync(tempFilePath, "something!" );
    
        return storage
            .bucket()
            .upload( tempFilePath, { destination } )
            .then( () => fs.unlinkSync(tempFilePath) )
            .catch(err => console.error('ERROR inside upload: ', err) );
        });
    

    我很困惑,因为我在 firebase 文档中看到了很多调用 bucket("albums") 的示例(例如:https://cloud.google.com/nodejs/docs/reference/storage/1.6.x/Bucket#getFiles),并假设这意味着存储桶可用于设置 子目录 用于上传。现在对我来说很明显不同了,但我被抛弃了,因为我在 firebase 存储控制台中看到的对存储桶的调用看起来不像 bucketname-12345.appspot.com

    【讨论】:

      【解决方案2】:

      不要费心尝试像这样使用 Google Cloud Storage SDK:

      const Storage = require('@google-cloud/storage');
          const storage = new Storage({
              projectId,
          });
      

      相反,只需在初始化后使用 Admin SDK。 Admin SDK 封装了 Google Cloud Storage SDK。当你初始化 admin 时,你也在隐式初始化它包装的所有组件。

      const admin = require('firebase-admin')
      admin.initializeApp()
      const storage = admin.storage()
      // Now use storage just like you would use @google-cloud/storage
      

      在您初始化 admin 时使用的默认服务帐户没有此类参数,应该对您项目的默认存储桶具有写入权限。

      【讨论】:

      • 我希望是这样,但即使使用您提供的代码,我仍然会遇到同样的错误。我还尝试升级我的帐户以删除结算帐户错误消息,但这(如预期的那样)并不能解决问题。我相信代码与您指定的完全一样(这里的要点:gist.github.com/xrd/…)。
      • 您的项目中可能配置错误。我一直这样做。这是一个全新的项目吗?
      • 顺便说一句,您不应该在每次运行该函数时都初始化管理 SDK。只做一次。
      • 好的,但是您上面的代码不是暗示了这一点吗?您是在函数之外创建存储对象吗?
      • 我将 require 语句和 initializeApp() 移到了触发函数之外 (gist.github.com/xrd/…)。但是,仍然是同样的错误。
      猜你喜欢
      • 1970-01-01
      • 2019-01-10
      • 1970-01-01
      • 2021-04-14
      • 2021-09-14
      • 1970-01-01
      • 1970-01-01
      • 2018-05-09
      • 2018-06-30
      相关资源
      最近更新 更多