3

I'm attempting to handle file uploads using a Google Cloud Function. This function uses Busboy to parse the multipart form data and then upload to Google Cloud Storage.

I keep receiving a ERROR: { Error: ENOENT: no such file or directory, open '/tmp/xxx.png' error when triggering the function.

The error seems to occur within the finish callback function when storage.bucket.upload(file) attempts to open the file path /tmp/xxx.png.

Example code

const path = require('path');
const os = require('os');
const fs = require('fs');
const Busboy = require('busboy');
const Storage = require('@google-cloud/storage');
const moment = require('moment');
const _ = require('lodash');

const projectId = 'xxx';
const bucketName = 'xxx';


const storage = new Storage({
  projectId: projectId,
});

exports.uploadFile = (req, res) => {
  if (req.method === 'POST') {
    const busboy = new Busboy({
      headers: req.headers
    });
    const uploads = []
    const tmpdir = os.tmpdir();

    busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
      const filepath = path.join(tmpdir, filename)
      var obj = {
        path: filepath,
        name: filename
      }
      uploads.push(obj);

      var writeStream = fs.createWriteStream(obj.path);
      file.pipe(writeStream);
    });

    busboy.on('finish', () => {
      _.forEach(uploads, function (file) {

        storage
          .bucket(bucketName)
          .upload(file.path, {
            name: moment().format('/YYYY/MM/DD/x') + '-' + file.name
          })
          .then(() => {
            console.log(`${file.name} uploaded to ${bucketName}.`);
          })
          .catch(err => {
            console.error('ERROR:', err);
          });


        fs.unlinkSync(file.path);
      })

      res.end()
    });

    busboy.end(req.rawBody);
  } else {
    res.status(405).end();
  }
}
Derek Wang
  • 10,098
  • 4
  • 18
  • 39
Lee Salminen
  • 900
  • 8
  • 18
  • I understand that this is an http triggered function, so you intend to upload the file from the end-user through the function into GCS. Why not use the function to generate an upload URL and serve it to the end-user so they can handle the upload directly? – Jofre Mar 21 '18 at 02:05
  • This was my fallback solution because I could not get signed URL uploads to work with Dropzone.js. I'll post a question about that issue as well. – Lee Salminen Mar 21 '18 at 02:58
  • 1
    Comment for the community: The workaround proposed by jofre is solved in this [question.](https://stackoverflow.com/questions/49398399/google-cloud-storage-signed-url-upload-dropzone-js) – Ggrimaldo Mar 28 '18 at 11:52
  • @LeeSalminen: having the same issue. It seems to be caused by a memory location tied to /tmp (i.e. it's not a physical drive). I am writing the file, can see it via fs.readdirSync but GCS upload is always failing with the "Upload failed error: ENOENT: no such file or directory, open '/tmp/XXX.txt'". I am building a simple prototype and would like to avoid signed URLs for now. I've tried various workarounds but they don't seem to work. – user1552175 Dec 27 '18 at 17:53

1 Answers1

0

Solved this with a stream instead of a temporary file. Only handles a single file at the moment though.

https://gist.github.com/PatrickHeneise/8f2c72c16c4e68e829e58ade64aba553#file-gcp-function-storage-file-stream-js

function asyncBusboy(req, res) {
  return new Promise((resolve, reject) => {
    const storage = new Storage()
    const bucket = storage.bucket(process.env.BUCKET)

    const fields = []
    const busboy = Busboy({
      headers: req.headers,
      limits: {
        fileSize: 10 * 1024 * 1024
      }
    })

    busboy.on('field', (key, value) => {
      fields[key] = value
    })

    busboy.on('file', (name, file, fileInfo) => {
      const { mimeType } = fileInfo
      const destFile = bucket.file(fileName)
      const writeStream = destFile.createWriteStream({
        metadata: {
          contentType: fileInfo.mimeType,
          metadata: {
            originalFileName: fileInfo.filename
          }
        }
      })
      file.pipe(writeStream)
    })

    busboy.on('close', function () {
      return resolve({ fields })
    })

    if (req.rawBody) {
      busboy.end(req.rawBody)
    } else {
      req.pipe(busboy)
    }
  })
}
Patrick
  • 7,903
  • 11
  • 52
  • 87