Skip to content

Commit 9381576

Browse files
authored
Merge branch 'main' into feat-retail-search-samples
2 parents 5396bbb + 0f66e3e commit 9381576

16 files changed

Lines changed: 143 additions & 125 deletions

File tree

functions/billing/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@ const PROJECT_NAME = `projects/${PROJECT_ID}`;
2323
// [END functions_billing_limit]
2424

2525
// [START functions_billing_slack]
26-
const slack = require('slack');
26+
const {WebClient} = require('@slack/web-api');
2727

2828
// TODO(developer) replace these with your own values
2929
const BOT_ACCESS_TOKEN =
3030
process.env.BOT_ACCESS_TOKEN || 'xxxx-111111111111-abcdefghidklmnopq';
3131
const CHANNEL = process.env.SLACK_CHANNEL || 'general';
32+
const slackClient = new WebClient(BOT_ACCESS_TOKEN);
3233

3334
exports.notifySlack = async pubsubEvent => {
3435
const pubsubAttrs = pubsubEvent.attributes;
@@ -37,8 +38,7 @@ exports.notifySlack = async pubsubEvent => {
3738
pubsubAttrs
3839
)}, ${pubsubData}`;
3940

40-
await slack.chat.postMessage({
41-
token: BOT_ACCESS_TOKEN,
41+
await slackClient.chat.postMessage({
4242
channel: CHANNEL,
4343
text: budgetNotificationText,
4444
});

functions/billing/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"description": "Examples of integrating Cloud Functions with billing",
66
"main": "index.js",
77
"engines": {
8-
"node": ">=16.0.0"
8+
"node": ">=18.0.0"
99
},
1010
"scripts": {
1111
"compute-test": "c8 mocha -p -j 2 test/periodic.test.js --timeout=600000",
@@ -16,9 +16,9 @@
1616
"dependencies": {
1717
"@google-cloud/billing": "^4.0.0",
1818
"@google-cloud/compute": "^4.0.0",
19+
"@slack/web-api": "^7.15.0",
1920
"google-auth-library": "^9.0.0",
20-
"googleapis": "^143.0.0",
21-
"slack": "^11.0.1"
21+
"googleapis": "^143.0.0"
2222
},
2323
"devDependencies": {
2424
"@google-cloud/functions-framework": "^3.0.0",

functions/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/imagemagick/index.js

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

1717
// [START functions_imagemagick_setup]
18-
const gm = require('gm').subClass({imageMagick: true});
18+
const sharp = require('sharp');
1919
const fs = require('fs').promises;
2020
const path = require('path');
2121
const vision = require('@google-cloud/vision');
@@ -32,6 +32,12 @@ const {BLURRED_BUCKET_NAME} = process.env;
3232
exports.blurOffensiveImages = async event => {
3333
// This event represents the triggering Cloud Storage object.
3434
const object = event;
35+
if (object.bucket === BLURRED_BUCKET_NAME) {
36+
console.log(
37+
'Event triggered by the blurred bucket; skip to avoid recursion'
38+
);
39+
return;
40+
}
3541

3642
const file = storage.bucket(object.bucket).file(object.name);
3743
const filePath = `gs://${object.bucket}/${object.name}`;
@@ -60,9 +66,10 @@ exports.blurOffensiveImages = async event => {
6066
// [END functions_imagemagick_analyze]
6167

6268
// [START functions_imagemagick_blur]
63-
// Blurs the given file using ImageMagick, and uploads it to another bucket.
69+
// Blurs the given file using sharp, and uploads it to another bucket.
6470
const blurImage = async (file, blurredBucketName) => {
6571
const tempLocalPath = `/tmp/${path.parse(file.name).base}`;
72+
const tempLocalBlurredPath = `/tmp/blurred-${path.parse(file.name).base}`;
6673

6774
// Download file from bucket.
6875
try {
@@ -72,34 +79,31 @@ const blurImage = async (file, blurredBucketName) => {
7279
} catch (err) {
7380
throw new Error(`File download failed: ${err}`);
7481
}
82+
try {
83+
await sharp(tempLocalPath).blur(16).toFile(tempLocalBlurredPath);
7584

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

9091
// Upload result to a different bucket, to avoid re-triggering this function.
9192
const blurredBucket = storage.bucket(blurredBucketName);
9293

9394
// Upload the Blurred image back into the bucket.
9495
const gcsPath = `gs://${blurredBucketName}/${file.name}`;
9596
try {
96-
await blurredBucket.upload(tempLocalPath, {destination: file.name});
97+
await blurredBucket.upload(tempLocalBlurredPath, {destination: file.name});
9798
console.log(`Uploaded blurred image to: ${gcsPath}`);
9899
} catch (err) {
99100
throw new Error(`Unable to upload blurred image to ${gcsPath}: ${err}`);
101+
} finally {
102+
// Delete the temporary file.
103+
await Promise.allSettled([
104+
fs.unlink(tempLocalPath),
105+
fs.unlink(tempLocalBlurredPath),
106+
]);
100107
}
101-
102-
// Delete the temporary file.
103-
return fs.unlink(tempLocalPath);
104108
};
105109
// [END functions_imagemagick_blur]

functions/imagemagick/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
"url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
1010
},
1111
"engines": {
12-
"node": ">=12.0.0"
12+
"node": ">=18.17.0"
1313
},
1414
"scripts": {
1515
"test": "c8 mocha -p -j 2 test/*.test.js --timeout=30000 --exit"
1616
},
1717
"dependencies": {
1818
"@google-cloud/storage": "^7.0.0",
1919
"@google-cloud/vision": "^4.0.0",
20-
"gm": "^1.23.1"
20+
"sharp": "^0.34.5"
2121
},
2222
"devDependencies": {
2323
"@google-cloud/functions-framework": "^3.0.0",

functions/imagemagick/test/index.test.js

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

1717
const assert = require('assert');
18-
const {execSync, spawn} = require('child_process');
18+
const {spawn} = require('child_process');
1919
const {Storage} = require('@google-cloud/storage');
2020
const sinon = require('sinon');
2121
const {request} = require('gaxios');
@@ -62,11 +62,6 @@ async function startFF(port) {
6262
return {ffProc, ffProcHandler};
6363
}
6464

65-
// ImageMagick is available by default in Cloud Run Functions environments
66-
// https://cloud.google.com/functions/1stgendocs/tutorials/imagemagick-1st-gen.md#importing_dependencies
67-
// Manually install it for testing only.
68-
execSync('sudo apt-get install imagemagick -y');
69-
7065
describe('functions/imagemagick tests', () => {
7166
before(async () => {
7267
let exists;
@@ -92,40 +87,54 @@ describe('functions/imagemagick tests', () => {
9287
it('blurOffensiveImages detects safe images using Cloud Vision', async () => {
9388
const PORT = 8080;
9489
const {ffProc, ffProcHandler} = await startFF(PORT);
95-
96-
await request({
97-
url: `http://localhost:${PORT}/blurOffensiveImages`,
98-
method: 'POST',
99-
data: {
90+
let stdout;
91+
try {
92+
await request({
93+
url: `http://localhost:${PORT}/blurOffensiveImages`,
94+
method: 'POST',
10095
data: {
101-
bucket: BUCKET_NAME,
102-
name: testFiles.safe,
96+
data: {
97+
bucket: BUCKET_NAME,
98+
name: testFiles.safe,
99+
},
103100
},
104-
},
105-
});
106-
ffProc.kill();
107-
const stdout = await ffProcHandler;
101+
});
102+
} catch (err) {
103+
console.error(
104+
`Cloud Function Error: ${err.response?.data || err.message}`
105+
);
106+
throw err;
107+
} finally {
108+
ffProc.kill();
109+
stdout = await ffProcHandler;
110+
}
108111
assert.ok(stdout.includes(`Detected ${testFiles.safe} as OK.`));
109112
});
110113

111114
it('blurOffensiveImages successfully blurs offensive images', async () => {
112115
const PORT = 8081;
113116
const {ffProc, ffProcHandler} = await startFF(PORT);
114-
115-
await request({
116-
url: `http://localhost:${PORT}/blurOffensiveImages`,
117-
method: 'POST',
118-
data: {
117+
let stdout;
118+
try {
119+
await request({
120+
url: `http://localhost:${PORT}/blurOffensiveImages`,
121+
method: 'POST',
119122
data: {
120-
bucket: BUCKET_NAME,
121-
name: testFiles.offensive,
123+
data: {
124+
bucket: BUCKET_NAME,
125+
name: testFiles.offensive,
126+
},
122127
},
123-
},
124-
});
125-
126-
ffProc.kill();
127-
const stdout = await ffProcHandler;
128-
128+
});
129+
} catch (err) {
130+
console.error(
131+
`Cloud Function Error: ${err.response?.data || err.message}`
132+
);
133+
throw err;
134+
} finally {
135+
ffProc.kill();
136+
stdout = await ffProcHandler;
137+
}
129138
assert.ok(stdout.includes(`Blurred image: ${testFiles.offensive}`));
130139
assert.ok(
131140
stdout.includes(

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;

0 commit comments

Comments
 (0)