From 7ad6b0378cc0493de2ce14cc5152f176aeaf0efb Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Tue, 4 Dec 2018 15:50:17 +0200 Subject: [PATCH] ci(docs-infra): manually trigger the preview server webhook (#27458) With this change, we no longer depend on CircleCI to trigger the webhook (which it sometimes does with considerable delay or not at all). This has the added benefit that other jobs will not unnecessarily trigger webhooks and spam the preview server logs. It is only the `aio_preview` job's webhook that we care about. Related to #27352. PR Close #27458 --- .circleci/config.yml | 5 +- .circleci/trigger-webhook.js | 107 ++++++++++++++++++++++++++++++++++ aio/scripts/create-preview.js | 37 ++++++++++++ 3 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 .circleci/trigger-webhook.js create mode 100644 aio/scripts/create-preview.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 00a2039e91..4f51ff9ffa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -275,6 +275,7 @@ jobs: # The `destination` needs to be kept in synch with the value of # `AIO_ARTIFACT_PATH` in `aio/aio-builds-setup/Dockerfile` destination: aio/dist/aio-snapshot.tgz + - run: node ./aio/scripts/create-preview $CIRCLE_BUILD_NUM # This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`. test_aio_preview: @@ -460,7 +461,3 @@ workflows: branches: only: - master - -notify: - webhooks: - - url: https://ngbuilds.io/circle-build diff --git a/.circleci/trigger-webhook.js b/.circleci/trigger-webhook.js new file mode 100644 index 0000000000..77860186bc --- /dev/null +++ b/.circleci/trigger-webhook.js @@ -0,0 +1,107 @@ +#!/usr/bin/env node + +/** + * Usage (cli): + * ``` + * node create-preview + * ``` + * + * Usage (JS): + * ```js + * require('./trigger-webhook'). + * triggerWebhook(buildNumber, jobName, webhookUrl). + * then(...); + * ``` + * + * Triggers a notification webhook with CircleCI specific info. + * + * It can be used for notifying external servers and trigger operations based on CircleCI job status + * (e.g. triggering the creation of a preview based on previously stored build atrifacts). + * + * The body of the sent payload is of the form: + * ```json + * { + * "payload": { + * "build_num": ${buildNumber} + * "build_parameters": { + * "CIRCLE_JOB": "${jobName}" + * } + * } + * } + * ``` + * + * When used from JS, it returns a promise which resolves to an object of the form: + * ```json + * { + * "statucCode": ${statusCode}, + * "responseText": "${responseText}" + * } + * ``` + * + * NOTE: + * - When used from the cli, the command will exit with an error code if the response's status code + * is outside the [200, 400) range. + * - When used from JS, the returned promise will be resolved, even if the response's status code is + * outside the [200, 400) range. It is up to the caller to decide how this should be handled. + */ + +// Imports +const {request} = require('https'); + +// Exports +module.exports = { + triggerWebhook, +}; + +// Run +if (require.resolve === module) { + _main(process.argv.slice(2)); +} + +// Helpers +function _main(args) { + triggerWebhook(...args). + then(({statusCode, responseText}) => (200 <= statusCode && statusCode < 400) ? + console.log(`Status: ${statusCode}\n${responseText}`) : + Promise.reject(new Error(`Request failed (status: ${statusCode}): ${responseText}`))). + catch(err => { + console.error(err); + process.exit(1); + }); +} + +function postJson(url, data) { + return new Promise((resolve, reject) => { + const opts = {method: 'post', headers: {'Content-Type': 'application/json'}}; + const onResponse = res => { + const statusCode = res.statusCode || -1; + let responseText = ''; + + res. + on('error', reject). + on('data', d => responseText += d). + on('end', () => resolve({statusCode, responseText})); + }; + + request(url, opts, onResponse). + on('error', reject). + end(JSON.stringify(data)); + }); +} + +async function triggerWebhook(buildNumber, jobName, webhookUrl) { + if (!buildNumber || !jobName || !webhookUrl || isNaN(buildNumber)) { + throw new Error( + 'Missing or invalid arguments.\n' + + 'Expected: buildNumber (number), jobName (string), webhookUrl (string)'); + } + + const data = { + payload: { + build_num: +buildNumber, + build_parameters: {CIRCLE_JOB: jobName}, + }, + }; + + return postJson(webhookUrl, data); +} diff --git a/aio/scripts/create-preview.js b/aio/scripts/create-preview.js new file mode 100644 index 0000000000..a9e26ce4eb --- /dev/null +++ b/aio/scripts/create-preview.js @@ -0,0 +1,37 @@ +#!/usr/bin/env node + +/** + * Usage: + * node create-preview + * + * Triggers the preview server to initiate the preview creation process for the specified CircleCI + * build number. It must be called _after_ the build artifacts have been created and stored on + * CircleCI. + */ + +// Imports +const {triggerWebhook} = require('../../.circleci/trigger-webhook'); + +// Constants +const JOB_NAME = 'aio_preview'; +const WEBHOOK_URL = 'https://ngbuilds.io/circle-build'; + +// Input +const buildNumber = process.argv[2]; + +// Run +triggerWebhook(buildNumber, JOB_NAME, WEBHOOK_URL). + then(({statusCode, responseText}) => isSuccess(statusCode) ? + console.log(`Status: ${statusCode}\n${responseText}`) : + Promise.reject(new Error(`Request failed (status: ${statusCode}): ${responseText}`))). + catch(err => { + console.error(err); + process.exit(1); + }); + +// Helpers +function isSuccess(statusCode) { + // Getting a 409 response from the preview server means that the preview has already been created + // for the corresponding PR/SHA, so our objective has been accomplished. + return (200 <= statusCode && statusCode < 400) || (statusCode === 409); +}