Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

functions/imagemagick: system tests + Node 8 upgrade #1387

Merged
merged 29 commits into from
Jul 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6701429
System tests + Node 8 upgrade
Jun 21, 2019
927f214
Fix test failure
Jun 27, 2019
cf24c57
Fix tests, take 2
Jun 28, 2019
0d0ab0b
DEBUG commit
Jun 28, 2019
99a29b3
Use separate ports for FF instances
Jun 28, 2019
b8064e7
Add DEBUGs back + increase timeouts
Jun 28, 2019
c70e2ba
https -> http
Jun 28, 2019
ddd3917
Merge branch 'master' into gcf-imagick-node8
fhinkel Jul 1, 2019
636ded9
Fix stdout bug
Jul 2, 2019
548da6e
Merge branch 'gcf-imagick-node8' of https://github.com/GoogleCloudPla…
Jul 2, 2019
cc66c33
Another DEBUG commit
Jul 2, 2019
65b8fa2
Update client library version
Jul 3, 2019
a28d1a6
Merge branch 'master' into gcf-imagick-node8
fhinkel Jul 8, 2019
be6893c
DEBUG: limit retry delay + add stderr log
Jul 11, 2019
bdf699d
Merge branch 'gcf-imagick-node8' of https://github.com/GoogleCloudPla…
Jul 11, 2019
796ed8c
Merge branch 'master' into gcf-imagick-node8
Jul 11, 2019
b48a34a
Fix safeSearchAnnotation being null on safe images
Jul 11, 2019
dc126bb
Merge branch 'gcf-imagick-node8' of https://github.com/GoogleCloudPla…
Jul 11, 2019
91ad2ba
Merge branch 'master' into gcf-imagick-node8
fhinkel Jul 15, 2019
729fc25
Remove DEBUG statements
Jul 15, 2019
d4234ec
Merge branch 'master' into gcf-imagick-node8
Jul 16, 2019
4a5a985
Rework logs
Jul 17, 2019
7aa4dfd
Merge branch 'gcf-imagick-node8' of https://github.com/GoogleCloudPla…
Jul 17, 2019
12c8c69
Address comments, pt 2
Jul 18, 2019
3f450bb
Merge branch 'master' into gcf-imagick-node8
fhinkel Jul 22, 2019
a2e8dbd
Merge branch 'master' into gcf-imagick-node8
fhinkel Jul 23, 2019
3b000f3
Fix lint typo
Jul 23, 2019
be2e537
Remove util.promisify, as gm doesn't support it
Jul 24, 2019
92f9558
Merge branch 'master' into gcf-imagick-node8
Jul 24, 2019
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
5 changes: 5 additions & 0 deletions .kokoro/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export SENDGRID_API_KEY=$(cat $KOKORO_GFILE_DIR/secrets-sendgrid-api-key.txt)
# Configure GCF variables
export FUNCTIONS_TOPIC=integration-tests-instance
export FUNCTIONS_BUCKET=$GCLOUD_PROJECT

# functions/speech-to-speech
export OUTPUT_BUCKET=$FUNCTIONS_BUCKET

# functions/translate
Expand All @@ -52,6 +54,9 @@ export TRANSLATE_TOPIC=$FUNCTIONS_TOPIC
export RESULT_TOPIC=$FUNCTIONS_TOPIC
export RESULT_BUCKET=$FUNCTIONS_BUCKET

# functions/imagemagick
export BLURRED_BUCKET_NAME=$GCLOUD_PROJECT-functions

# Configure IoT variables
export NODEJS_IOT_EC_PUBLIC_KEY=${KOKORO_GFILE_DIR}/ec_public.pem
export NODEJS_IOT_RSA_PRIVATE_KEY=${KOKORO_GFILE_DIR}/rsa_private.pem
Expand Down
157 changes: 61 additions & 96 deletions functions/imagemagick/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
// [START functions_imagemagick_setup]
const gm = require('gm').subClass({imageMagick: true});
const fs = require('fs');
const {promisify} = require('util');
const path = require('path');
const {Storage} = require('@google-cloud/storage');
const storage = new Storage();
Expand All @@ -30,115 +31,79 @@ const {BLURRED_BUCKET_NAME} = process.env;

// [START functions_imagemagick_analyze]
// Blurs uploaded images that are flagged as Adult or Violence.
exports.blurOffensiveImages = event => {
const object = event.data || event; // Node 6: event.data === Node 8+: event

// Exit if this is a deletion or a deploy event.
if (object.resourceState === 'not_exists') {
console.log('This is a deletion event.');
return;
} else if (!object.name) {
console.log('This is a deploy event.');
return;
}
exports.blurOffensiveImages = async event => {
const object = event;

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

// Ignore already-blurred files (to prevent re-invoking this function)
if (file.name.startsWith('blurred-')) {
console.log(`The image ${file.name} is already blurred.`);
return;
}

console.log(`Analyzing ${file.name}.`);

return client
.safeSearchDetection(filePath)
.catch(err => {
console.error(`Failed to analyze ${file.name}.`, err);
return Promise.reject(err);
})
.then(([result]) => {
const detections = result.safeSearchAnnotation;

if (
detections.adult === 'VERY_LIKELY' ||
detections.violence === 'VERY_LIKELY'
) {
console.log(
`The image ${file.name} has been detected as inappropriate.`
);
return blurImage(file, BLURRED_BUCKET_NAME);
} else {
console.log(`The image ${file.name} has been detected as OK.`);
}
});
try {
const [result] = await client.safeSearchDetection(filePath);
const detections = result.safeSearchAnnotation || {};

if (
detections.adult === 'VERY_LIKELY' ||
detections.violence === 'VERY_LIKELY'
) {
console.log(`Detected ${file.name} as inappropriate.`);
return blurImage(file, BLURRED_BUCKET_NAME);
} else {
console.log(`Detected ${file.name} as OK.`);
}
} catch (err) {
console.error(`Failed to analyze ${file.name}.`, err);
ace-n marked this conversation as resolved.
Show resolved Hide resolved
return Promise.reject(err);
}
};
// [END functions_imagemagick_analyze]

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

// Download file from bucket.
return file
.download({destination: tempLocalPath})
.catch(err => {
console.error('Failed to download file.', err);
return Promise.reject(err);
})
.then(() => {
console.log(
`Image ${file.name} has been downloaded to ${tempLocalPath}.`
);

// Blur the image using ImageMagick.
return 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 {
resolve(stdout);
}
});
});
})
.then(() => {
console.log(`Image ${file.name} has been blurred.`);

// Upload result to a different bucket, to avoid re-triggering this function.
// You can also re-upload it to the same bucket + tell your Cloud Function to
// ignore files marked as blurred (e.g. those with a "blurred" prefix)
const blurredBucket = storage.bucket(blurredBucketName);

// Upload the Blurred image back into the bucket.
return blurredBucket
.upload(tempLocalPath, {destination: file.name})
.catch(err => {
console.error('Failed to upload blurred image.', err);
return Promise.reject(err);
});
})
.then(() => {
console.log(
`Blurred image has been uploaded to: gs://${blurredBucketName}/${file.name}`
);

// Delete the temporary file.
return new Promise((resolve, reject) => {
fs.unlink(tempLocalPath, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
try {
await file.download({destination: tempLocalPath});

console.log(`Downloaded ${file.name} to ${tempLocalPath}.`);
} catch (err) {
console.error('File download failed.', err);
return Promise.reject(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);
}
});
});
}
});

// Upload result to a different bucket, to avoid re-triggering this function.
// You can also re-upload it to the same bucket + tell your Cloud Function to
// ignore files marked as blurred (e.g. those with a "blurred" prefix)
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});
console.log(`Uploaded blurred image to: ${gcsPath}`);
} catch (err) {
console.error(`Unable to upload blurred image to ${gcsPath}:`, err);
return Promise.reject(err);
}

// Delete the temporary file.
return promisify(fs.unlink(tempLocalPath));
};
// [END functions_imagemagick_blur]
12 changes: 10 additions & 2 deletions functions/imagemagick/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,29 @@
"node": ">=8.0.0"
},
"scripts": {
"test": "mocha test/*.test.js --timeout=20000"
"test": "mocha test/*.test.js --timeout=20000 --exit"
},
"dependencies": {
"@google-cloud/storage": "^3.0.0",
"@google-cloud/vision": "^0.25.0",
"gm": "^1.23.1"
},
"devDependencies": {
"@google-cloud/functions-framework": "^1.1.1",
"@google-cloud/nodejs-repo-tools": "^3.3.0",
"child-process-promise": "^2.2.1",
"mocha": "^6.0.0",
"proxyquire": "^2.1.0",
"request": "^2.88.0",
"requestretry": "^4.0.0",
"sinon": "^7.0.0"
},
"cloud-repo-tools": {
"requiresKeyFile": true,
"requiresProjectId": true
"requiresProjectId": true,
"requiredEnvVars": [
"FUNCTIONS_BUCKET",
"BLURRED_BUCKET_NAME"
]
}
}
Loading