4

I'm trying to do a simple function that resizes a newly uploaded image in Storage. I use the following to help me do that:

import { tmpdir } from 'os';
import { join, dirname } from 'path';
import * as sharp from 'sharp';
import * as fs from 'fs-extra';

When this code executes:

await bucket.file(filePath).download({
    destination: tmpFilePath
});

I get the following error in the Google Cloud Function logs:

Error: ENOENT: no such file or directory, open '/tmp/images/1542144115815_Emperor_penguins.jpg' at Error (native)

Here is the full code [segment]:

const gcs = admin.storage();
const db = admin.firestore();

import { tmpdir } from 'os';
import { join, dirname } from 'path';

import * as sharp from 'sharp';
import * as fs from 'fs-extra';

export const imageResize = functions.storage
    .object()
    .onFinalize(async object => {
        console.log('> > > > > > > 1.3 < < < < < < <');
        const bucket = gcs.bucket(object.bucket);
        console.log(object.name);
        const filePath = object.name;
        const fileName = filePath.split('/').pop();
        const tmpFilePath = join(tmpdir(), object.name);

        const thumbFileName = 'thumb_' + fileName;
        const tmpThumbPath = join(tmpdir(), thumbFileName);

        console.log('step 1');
        // Resizing image
        if (fileName.includes('thumb_')) {
            console.log('exiting function');
            return false;
        }
        console.log('step 2');
        console.log(`filePath: ${filePath}`);
        console.log(`tmpFilePath: ${tmpFilePath}`);
        await bucket.file(filePath).download({
            destination: tmpFilePath
        });
        console.log('step 3');
        await sharp(tmpFilePath)
            .resize(200, 200)
            .toFile(tmpThumbPath);

        await bucket.upload(tmpThumbPath, {
            destination: join(dirname(filePath), thumbFileName)
        });

UPDATE 1: added await fs.ensureDir(tmpFilePath); to ensure the filepath exists. Now getting a new error:

Error: EINVAL: invalid argument, open '/tmp/images/1542146603970_mouse.png' at Error (native)

UPDATE 2 SOLVED: Added a solution as an Answer below.

Kenny
  • 2,124
  • 3
  • 33
  • 63

2 Answers2

4

I changed the following code

From

const bucket = gcs.bucket(object.bucket);
const filePath = object.name;
const fileName = filePath.split('/').pop();
const tmpFilePath = join(tmpdir(), object.name);

const thumbFileName = 'thumb_' + fileName;
const tmpThumbPath = join(tmpdir(), thumbFileName);

To

const bucket = gcs.bucket(object.bucket);
const filePath = object.name;
const fileName = filePath.split('/').pop();
const thumbFileName = 'thumb_' + fileName;

const workingDir = join(tmpdir(), `${object.name.split('/')[0]}/`);//new
const tmpFilePath = join(workingDir, fileName);
const tmpThumbPath = join(workingDir, thumbFileName);

await fs.ensureDir(workingDir);

As you can see, I created a workingDir that would be shared between the paths and then ran await fs.ensureDir(workingDir); to create the path. That solved my problem.

Kenny
  • 2,124
  • 3
  • 33
  • 63
2

I suspect you'd see that message because you tried to write to this path:

/tmp/images/1542144115815_Emperor_penguins.jpg

Without first creating the parent directory:

/tmp/images

You can't write a file to a local filesystem folder that doesn't exist, and it seems that the Cloud Storage SDK will not create it for you.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • 1
    But isn't that what this is doing, `const tmpFilePath = join(tmpdir(), object.name);` creating the directory? (Where object name is images/1542144115815_Emperor_penguins.jpg) – Kenny Nov 13 '18 at 21:48
  • 1
    No, that's just building a string. It has nothing to do with the filesystem yet. – Doug Stevenson Nov 13 '18 at 21:49
  • It's very similar to how it's done in this version, where the code is a little older: https://angularfirebase.com/lessons/image-thumbnail-resizer-cloud-function/ – Kenny Nov 13 '18 at 21:51
  • 1
    If you're only dealing with a single file in this function, why don't you just give it a static name/path, rather than a name derived from the path in Storage? – Doug Stevenson Nov 13 '18 at 21:51
  • 3
    The key thing you missing from that sample is the call to `await fs.ensureDir(workingDir);`. That creates the directory. – Doug Stevenson Nov 13 '18 at 21:54
  • Well, the function I think is downloading the original image into a "temp" directory on the server itself, not Storage, then doing the resize, then saving to that directory, then re-uploading the altered file back to Storage. – Kenny Nov 13 '18 at 21:55
  • Ah okay, you may be right! Let me throw that in and see how it performs. – Kenny Nov 13 '18 at 21:56
  • Well, the error has changed: `Error: EINVAL: invalid argument, open '/tmp/images/1542146603970_mouse.png' at Error (native)` – Kenny Nov 13 '18 at 22:04
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/183592/discussion-between-kenny-and-doug-stevenson). – Kenny Nov 13 '18 at 22:24
  • Sounds like you have a completely different problem now. – Doug Stevenson Nov 13 '18 at 22:43