Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
dfad2cf
feat: add storage batch operations samples
thiyaguk09 Sep 11, 2025
8b9d2a2
bug fix
thiyaguk09 Sep 11, 2025
3ea4225
added test case
thiyaguk09 Sep 12, 2025
7b85f34
lint fix
thiyaguk09 Sep 12, 2025
7dceecc
feat(storage-batch): Improve Batch Operation API samples (Documentati…
thiyaguk09 Oct 17, 2025
a2f6598
Incorporated feedback from review comments
thiyaguk09 Oct 24, 2025
07bc988
Merge branch 'main' into storagebatchoperations-samples
ddelgrosso1 Oct 27, 2025
fcf02df
Merge branch 'main' into storagebatchoperations-samples
thiyaguk09 Oct 30, 2025
bf44b8e
Merge branch 'main' into storagebatchoperations-samples
thiyaguk09 Nov 18, 2025
8cd034b
Merge branch 'main' into storagebatchoperations-samples
thiyaguk09 Dec 10, 2025
affdd1f
Merge branch 'main' into storagebatchoperations-samples
chandra-siri Mar 12, 2026
6bf5c54
Merge branch 'main' into storagebatchoperations-samples
thiyaguk09 Mar 30, 2026
f664754
Merge branch 'main' into storagebatchoperations-samples
pearigee Apr 3, 2026
7011508
Merge branch 'main' into storagebatchoperations-samples
thiyaguk09 Apr 7, 2026
cf020ea
feat(spanner): migrate batch 3 core samples and tests (#4278)
angelcaamal Apr 8, 2026
c715cf1
fix(samples): Update NodeJs comment for rendering latest parameter ve…
sc-07 Apr 16, 2026
8941c63
feat: add deploy to cloud run button for helloworld app (#4277)
shruti-mantri Apr 21, 2026
6cf803c
fix(healthcare): correct typo 'occured' to 'occurred' in importDicomI…
mohammadmseet-hue Apr 21, 2026
6edfeda
refactor(run/image-processing): replace deprecated gm library with sh…
angelcaamal Apr 22, 2026
c43918f
refactor(functions/v2/imagemagick): migrate from gm to sharp (#4293)
angelcaamal Apr 22, 2026
6a530b5
fix(functions): migrate imagemagick sample to sharp and improve test …
angelcaamal Apr 22, 2026
0f66e3e
chore(billing): migrate deprecated slack package to @slack/web-api (#…
angelcaamal Apr 22, 2026
a930c35
Merge branch 'main' into storagebatchoperations-samples
cpriti-os Apr 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions functions/billing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ const PROJECT_NAME = `projects/${PROJECT_ID}`;
// [END functions_billing_limit]

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

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

exports.notifySlack = async pubsubEvent => {
const pubsubAttrs = pubsubEvent.attributes;
Expand All @@ -37,8 +38,7 @@ exports.notifySlack = async pubsubEvent => {
pubsubAttrs
)}, ${pubsubData}`;

await slack.chat.postMessage({
token: BOT_ACCESS_TOKEN,
await slackClient.chat.postMessage({
channel: CHANNEL,
text: budgetNotificationText,
});
Expand Down
6 changes: 3 additions & 3 deletions functions/billing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "Examples of integrating Cloud Functions with billing",
"main": "index.js",
"engines": {
"node": ">=16.0.0"
"node": ">=18.0.0"
},
"scripts": {
"compute-test": "c8 mocha -p -j 2 test/periodic.test.js --timeout=600000",
Expand All @@ -16,9 +16,9 @@
"dependencies": {
"@google-cloud/billing": "^4.0.0",
"@google-cloud/compute": "^4.0.0",
"@slack/web-api": "^7.15.0",
"google-auth-library": "^9.0.0",
"googleapis": "^143.0.0",
"slack": "^11.0.1"
"googleapis": "^143.0.0"
},
"devDependencies": {
"@google-cloud/functions-framework": "^3.0.0",
Expand Down
4 changes: 2 additions & 2 deletions functions/imagemagick/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<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"/>

# Google Cloud Functions ImageMagick sample
# Google Cloud Functions imagemagick sample

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

View the [source code][code].
Expand Down
42 changes: 23 additions & 19 deletions functions/imagemagick/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
'use strict';

// [START functions_imagemagick_setup]
const gm = require('gm').subClass({imageMagick: true});
const sharp = require('sharp');
const fs = require('fs').promises;
const path = require('path');
const vision = require('@google-cloud/vision');
Expand All @@ -32,6 +32,12 @@ const {BLURRED_BUCKET_NAME} = process.env;
exports.blurOffensiveImages = async event => {
// This event represents the triggering Cloud Storage object.
const object = event;
if (object.bucket === BLURRED_BUCKET_NAME) {
console.log(
'Event triggered by the blurred bucket; skip to avoid recursion'
);
return;
}

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

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

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

await new Promise((resolve, reject) => {
gm(tempLocalPath)
.blur(0, 16)
.write(tempLocalPath, (err, stdout) => {
if (err) {
console.error('Failed to blur image.', err);
reject(err);
} else {
console.log(`Blurred image: ${file.name}`);
resolve(stdout);
}
});
});
console.log(`Blurred image: ${file.name}`);
} catch (err) {
console.error('Failed to blur image.', err);
throw err;
}

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

// Upload the Blurred image back into the bucket.
const gcsPath = `gs://${blurredBucketName}/${file.name}`;
try {
await blurredBucket.upload(tempLocalPath, {destination: file.name});
await blurredBucket.upload(tempLocalBlurredPath, {destination: file.name});
console.log(`Uploaded blurred image to: ${gcsPath}`);
} catch (err) {
throw new Error(`Unable to upload blurred image to ${gcsPath}: ${err}`);
} finally {
// Delete the temporary file.
await Promise.allSettled([
fs.unlink(tempLocalPath),
fs.unlink(tempLocalBlurredPath),
]);
}

// Delete the temporary file.
return fs.unlink(tempLocalPath);
};
// [END functions_imagemagick_blur]
4 changes: 2 additions & 2 deletions functions/imagemagick/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
"url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
},
"engines": {
"node": ">=12.0.0"
"node": ">=18.17.0"
},
"scripts": {
"test": "c8 mocha -p -j 2 test/*.test.js --timeout=30000 --exit"
},
"dependencies": {
"@google-cloud/storage": "^7.0.0",
"@google-cloud/vision": "^4.0.0",
"gm": "^1.23.1"
"sharp": "^0.34.5"
},
"devDependencies": {
"@google-cloud/functions-framework": "^3.0.0",
Expand Down
69 changes: 39 additions & 30 deletions functions/imagemagick/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
'use strict';

const assert = require('assert');
const {execSync, spawn} = require('child_process');
const {spawn} = require('child_process');
const {Storage} = require('@google-cloud/storage');
const sinon = require('sinon');
const {request} = require('gaxios');
Expand Down Expand Up @@ -62,11 +62,6 @@ async function startFF(port) {
return {ffProc, ffProcHandler};
}

// ImageMagick is available by default in Cloud Run Functions environments
// https://cloud.google.com/functions/1stgendocs/tutorials/imagemagick-1st-gen.md#importing_dependencies
// Manually install it for testing only.
execSync('sudo apt-get install imagemagick -y');

describe('functions/imagemagick tests', () => {
before(async () => {
let exists;
Expand All @@ -92,40 +87,54 @@ describe('functions/imagemagick tests', () => {
it('blurOffensiveImages detects safe images using Cloud Vision', async () => {
const PORT = 8080;
const {ffProc, ffProcHandler} = await startFF(PORT);

await request({
url: `http://localhost:${PORT}/blurOffensiveImages`,
method: 'POST',
data: {
let stdout;
try {
await request({
url: `http://localhost:${PORT}/blurOffensiveImages`,
method: 'POST',
data: {
bucket: BUCKET_NAME,
name: testFiles.safe,
data: {
bucket: BUCKET_NAME,
name: testFiles.safe,
},
},
},
});
ffProc.kill();
const stdout = await ffProcHandler;
});
} catch (err) {
console.error(
`Cloud Function Error: ${err.response?.data || err.message}`
);
throw err;
} finally {
ffProc.kill();
stdout = await ffProcHandler;
}
assert.ok(stdout.includes(`Detected ${testFiles.safe} as OK.`));
});

it('blurOffensiveImages successfully blurs offensive images', async () => {
const PORT = 8081;
const {ffProc, ffProcHandler} = await startFF(PORT);

await request({
url: `http://localhost:${PORT}/blurOffensiveImages`,
method: 'POST',
data: {
let stdout;
try {
await request({
url: `http://localhost:${PORT}/blurOffensiveImages`,
method: 'POST',
data: {
bucket: BUCKET_NAME,
name: testFiles.offensive,
data: {
bucket: BUCKET_NAME,
name: testFiles.offensive,
},
},
},
});

ffProc.kill();
const stdout = await ffProcHandler;

});
} catch (err) {
console.error(
`Cloud Function Error: ${err.response?.data || err.message}`
);
throw err;
} finally {
ffProc.kill();
stdout = await ffProcHandler;
}
assert.ok(stdout.includes(`Blurred image: ${testFiles.offensive}`));
assert.ok(
stdout.includes(
Expand Down
4 changes: 2 additions & 2 deletions functions/v2/imagemagick/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<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"/>

# Google Cloud Functions ImageMagick sample
# Google Cloud Functions imagemagick sample

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

View the [source code][code].
Expand Down
45 changes: 26 additions & 19 deletions functions/v2/imagemagick/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

// [START functions_imagemagick_setup]
const functions = require('@google-cloud/functions-framework');
const gm = require('gm').subClass({imageMagick: true});
const sharp = require('sharp');
const fs = require('fs').promises;
const path = require('path');
const vision = require('@google-cloud/vision');
Expand All @@ -34,6 +34,14 @@ functions.cloudEvent('blurOffensiveImages', async cloudEvent => {
// This event represents the triggering Cloud Storage object.
const bucket = cloudEvent.data.bucket;
const name = cloudEvent.data.name;

if (bucket === BLURRED_BUCKET_NAME) {
console.log(
'Event triggered by the blurred bucket; skip to avoid recursion'
);
return;
}

const file = storage.bucket(bucket).file(name);
const filePath = `gs://${bucket}/${name}`;

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

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

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

await new Promise((resolve, reject) => {
gm(tempLocalPath)
.blur(0, 16)
.write(tempLocalPath, (err, stdout) => {
if (err) {
console.error('Failed to blur image.', err);
reject(err);
} else {
console.log(`Blurred image: ${file.name}`);
resolve(stdout);
}
});
});
try {
await sharp(tempLocalPath).blur(16).toFile(tempLocalBlurredPath);

console.log(`Blurred image: ${file.name}`);
} catch (err) {
console.error('Failed to blur image.', err);
throw err;
}

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

// Upload the Blurred image back into the bucket.
const gcsPath = `gs://${blurredBucketName}/${file.name}`;
try {
await blurredBucket.upload(tempLocalPath, {destination: file.name});
await blurredBucket.upload(tempLocalBlurredPath, {destination: file.name});
console.log(`Uploaded blurred image to: ${gcsPath}`);
} catch (err) {
throw new Error(`Unable to upload blurred image to ${gcsPath}: ${err}`);
} finally {
// Delete the temporary file.
await Promise.allSettled([
fs.unlink(tempLocalPath),
fs.unlink(tempLocalBlurredPath),
]);
}

// Delete the temporary file.
return fs.unlink(tempLocalPath);
};
// [END functions_imagemagick_blur]
6 changes: 3 additions & 3 deletions functions/v2/imagemagick/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
},
"engines": {
"node": ">=16.0.0"
"node": ">=18.17.0"
},
"scripts": {
"test": "c8 mocha -p -j 2 test/*.test.js --timeout=20000 --exit"
Expand All @@ -18,7 +18,7 @@
"@google-cloud/functions-framework": "^3.1.0",
"@google-cloud/storage": "^7.0.0",
"@google-cloud/vision": "^4.0.0",
"gm": "^1.23.1"
"sharp": "^0.34.5"
},
"devDependencies": {
"c8": "^10.0.0",
Expand All @@ -27,4 +27,4 @@
"sinon": "^18.0.0",
"supertest": "^7.0.0"
}
}
}
6 changes: 0 additions & 6 deletions functions/v2/imagemagick/test/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
'use strict';

const assert = require('assert');
const {execSync} = require('child_process');
const {Storage} = require('@google-cloud/storage');
const sinon = require('sinon');
const supertest = require('supertest');
Expand All @@ -34,11 +33,6 @@ const testFiles = {

require('../index');

// ImageMagick is available by default in Cloud Run Functions environments
// https://cloud.google.com/functions/1stgendocs/tutorials/imagemagick-1st-gen.md#importing_dependencies
// Manually install it for testing only.
execSync('sudo apt-get install imagemagick -y');

describe('functions/imagemagick tests', () => {
before(async () => {
let exists;
Expand Down
Loading