Skip to content

Commit c43918f

Browse files
angelcaamaliennae
andauthored
refactor(functions/v2/imagemagick): migrate from gm to sharp (#4293)
* refactor: replace deprecated gm library with sharp in v2 sample * chore: update node version in package.json * fix: handle condition where bucket is the same as blurred bucket * fix: remove ImageMagick comments and resolve linting issues * docs: remove gm library references from README * docs: remove gm library references from README --------- Co-authored-by: Jennifer Davis <sigje@google.com>
1 parent 6edfeda commit c43918f

5 files changed

Lines changed: 39 additions & 40 deletions

File tree

functions/v2/imagemagick/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<img src="https://avatars2.githubusercontent.com/u/2810941?v=3&s=96" alt="Google Cloud Platform logo" title="Google Cloud Platform" align="right" height="96" width="96"/>
22

3-
# Google Cloud Functions ImageMagick sample
3+
# Google Cloud Functions imagemagick sample
44

5-
This sample shows you how to blur an image using ImageMagick in a
5+
This sample shows you how to blur an image using sharp in a
66
Storage-triggered Cloud Function.
77

88
View the [source code][code].

functions/v2/imagemagick/index.js

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
// [START functions_imagemagick_setup]
1818
const functions = require('@google-cloud/functions-framework');
19-
const gm = require('gm').subClass({imageMagick: true});
19+
const sharp = require('sharp');
2020
const fs = require('fs').promises;
2121
const path = require('path');
2222
const vision = require('@google-cloud/vision');
@@ -34,6 +34,14 @@ functions.cloudEvent('blurOffensiveImages', async cloudEvent => {
3434
// This event represents the triggering Cloud Storage object.
3535
const bucket = cloudEvent.data.bucket;
3636
const name = cloudEvent.data.name;
37+
38+
if (bucket === BLURRED_BUCKET_NAME) {
39+
console.log(
40+
'Event triggered by the blurred bucket; skip to avoid recursion'
41+
);
42+
return;
43+
}
44+
3745
const file = storage.bucket(bucket).file(name);
3846
const filePath = `gs://${bucket}/${name}`;
3947

@@ -61,9 +69,10 @@ functions.cloudEvent('blurOffensiveImages', async cloudEvent => {
6169
// [END functions_imagemagick_analyze]
6270

6371
// [START functions_imagemagick_blur]
64-
// Blurs the given file using ImageMagick, and uploads it to another bucket.
72+
// Blurs the given file using sharp, and uploads it to another bucket.
6573
const blurImage = async (file, blurredBucketName) => {
6674
const tempLocalPath = `/tmp/${path.parse(file.name).base}`;
75+
const tempLocalBlurredPath = `/tmp/blurred-${path.parse(file.name).base}`;
6776

6877
// Download file from bucket.
6978
try {
@@ -74,33 +83,31 @@ const blurImage = async (file, blurredBucketName) => {
7483
throw new Error(`File download failed: ${err}`);
7584
}
7685

77-
await new Promise((resolve, reject) => {
78-
gm(tempLocalPath)
79-
.blur(0, 16)
80-
.write(tempLocalPath, (err, stdout) => {
81-
if (err) {
82-
console.error('Failed to blur image.', err);
83-
reject(err);
84-
} else {
85-
console.log(`Blurred image: ${file.name}`);
86-
resolve(stdout);
87-
}
88-
});
89-
});
86+
try {
87+
await sharp(tempLocalPath).blur(16).toFile(tempLocalBlurredPath);
88+
89+
console.log(`Blurred image: ${file.name}`);
90+
} catch (err) {
91+
console.error('Failed to blur image.', err);
92+
throw err;
93+
}
9094

9195
// Upload result to a different bucket, to avoid re-triggering this function.
9296
const blurredBucket = storage.bucket(blurredBucketName);
9397

9498
// Upload the Blurred image back into the bucket.
9599
const gcsPath = `gs://${blurredBucketName}/${file.name}`;
96100
try {
97-
await blurredBucket.upload(tempLocalPath, {destination: file.name});
101+
await blurredBucket.upload(tempLocalBlurredPath, {destination: file.name});
98102
console.log(`Uploaded blurred image to: ${gcsPath}`);
99103
} catch (err) {
100104
throw new Error(`Unable to upload blurred image to ${gcsPath}: ${err}`);
105+
} finally {
106+
// Delete the temporary file.
107+
await Promise.allSettled([
108+
fs.unlink(tempLocalPath),
109+
fs.unlink(tempLocalBlurredPath),
110+
]);
101111
}
102-
103-
// Delete the temporary file.
104-
return fs.unlink(tempLocalPath);
105112
};
106113
// [END functions_imagemagick_blur]

functions/v2/imagemagick/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
1010
},
1111
"engines": {
12-
"node": ">=16.0.0"
12+
"node": ">=18.17.0"
1313
},
1414
"scripts": {
1515
"test": "c8 mocha -p -j 2 test/*.test.js --timeout=20000 --exit"
@@ -18,7 +18,7 @@
1818
"@google-cloud/functions-framework": "^3.1.0",
1919
"@google-cloud/storage": "^7.0.0",
2020
"@google-cloud/vision": "^4.0.0",
21-
"gm": "^1.23.1"
21+
"sharp": "^0.34.5"
2222
},
2323
"devDependencies": {
2424
"c8": "^10.0.0",
@@ -27,4 +27,4 @@
2727
"sinon": "^18.0.0",
2828
"supertest": "^7.0.0"
2929
}
30-
}
30+
}

functions/v2/imagemagick/test/integration.test.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
'use strict';
1616

1717
const assert = require('assert');
18-
const {execSync} = require('child_process');
1918
const {Storage} = require('@google-cloud/storage');
2019
const sinon = require('sinon');
2120
const supertest = require('supertest');
@@ -34,11 +33,6 @@ const testFiles = {
3433

3534
require('../index');
3635

37-
// ImageMagick is available by default in Cloud Run Functions environments
38-
// https://cloud.google.com/functions/1stgendocs/tutorials/imagemagick-1st-gen.md#importing_dependencies
39-
// Manually install it for testing only.
40-
execSync('sudo apt-get install imagemagick -y');
41-
4236
describe('functions/imagemagick tests', () => {
4337
before(async () => {
4438
let exists;

functions/v2/imagemagick/test/unit.test.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,31 +38,29 @@ const loadSample = (adultResult, fileName) => {
3838
return {
3939
bucket: sinon.stub().returnsThis(),
4040
file: sinon.stub().returnsThis(),
41-
upload: sinon.stub().returnsThis(),
42-
download: sinon.stub().returnsThis(),
41+
upload: sinon.stub().resolves(),
42+
download: sinon.stub().resolves(),
4343
name: fileName,
4444
};
4545
},
4646
};
4747

48-
const gm = () => {
49-
return {
50-
blur: sinon.stub().returnsThis(),
51-
write: sinon.stub().yields(),
52-
};
48+
const sharpInstance = {
49+
blur: sinon.stub().returnsThis(),
50+
toFile: sinon.stub().resolves(),
5351
};
54-
gm.subClass = sinon.stub().returnsThis();
52+
const sharpMock = sinon.stub().returns(sharpInstance);
5553

5654
const fs = {
5755
promises: {
58-
unlink: sinon.stub(),
56+
unlink: sinon.stub().resolves(),
5957
},
6058
};
6159

6260
return proxyquire('..', {
6361
'@google-cloud/vision': vision,
6462
'@google-cloud/storage': storage,
65-
gm: gm,
63+
sharp: sharpMock,
6664
fs: fs,
6765
});
6866
};

0 commit comments

Comments
 (0)