Skip to content

Commit

Permalink
feat(2719): Scheduled job stop with notification if there's no admin (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
klu909 authored Aug 18, 2022
1 parent cf4d465 commit fb8b84d
Show file tree
Hide file tree
Showing 3 changed files with 331 additions and 161 deletions.
330 changes: 203 additions & 127 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,201 @@ const SCHEMA_BUILD_DATA = Joi.object().keys({
...schema.plugins.notifications.schemaBuildData,
settings: SCHEMA_SLACK_SETTINGS.required()
});
const SCHEMA_JOB_DATA = Joi.object().keys({
...schema.plugins.notifications.schemaJobData,
settings: SCHEMA_SLACK_SETTINGS.required()
});
const SCHEMA_SLACK_CONFIG = Joi.object().keys({
token: Joi.string().required()
});

/**
* Handle slack messaging for build status
* @method buildStatus
* @param {Object} buildData
* @param {String} buildData.status Build status
* @param {Object} buildData.pipeline Pipeline
* @param {String} buildData.jobName Job name
* @param {Object} buildData.build Build
* @param {Object} buildData.event Event
* @param {String} buildData.buildLink Build link
* @param {Object} buildData.settings Notification setting
* @param {Object} config Slack notifications config
*/
function buildStatus(buildData, config) {
try {
Joi.attempt(buildData, SCHEMA_BUILD_DATA, 'Invalid build data format');
} catch (e) {
return;
}

// Slack channel overwrite from meta data. Job specific only.
const metaReplaceVar = `build.meta.notification.slack.${buildData.jobName}.channels`;

const metaDataSlackChannel = hoek.reach(buildData, metaReplaceVar, { default: false });

let channelReplacement;

if (metaDataSlackChannel) {
channelReplacement = metaDataSlackChannel.split(',');
// Remove empty/blank items.
channelReplacement = channelReplacement.filter(x => x.trim() !== '');
}
// Slack channels from configuration
if (typeof buildData.settings.slack === 'string' || Array.isArray(buildData.settings.slack)) {
buildData.settings.slack =
typeof buildData.settings.slack === 'string' ? [buildData.settings.slack] : buildData.settings.slack;
buildData.settings.slack = {
channels: buildData.settings.slack,
statuses: DEFAULT_STATUSES,
minimized: false
};
}
if (channelReplacement) {
buildData.settings.slack.channels = channelReplacement;
}

if (buildData.settings.slack.statuses === undefined) {
buildData.settings.slack.statuses = DEFAULT_STATUSES;
}

// Add for fixed notification
if (buildData.isFixed) {
buildData.settings.slack.statuses.push('FIXED');
}

// Do not change the `buildData.status` directly
// It affects the behavior of other notification plugins
let notificationStatus = buildData.status;

if (buildData.settings.slack.statuses.includes('FAILURE')) {
if (notificationStatus === 'SUCCESS' && buildData.isFixed) {
notificationStatus = 'FIXED';
}
}

if (!buildData.settings.slack.statuses.includes(notificationStatus)) {
return;
}

const pipelineLink = buildData.buildLink.split('/builds')[0];
const truncatedSha = buildData.event.sha.slice(0, 6);
const cutOff = 150;
const commitMessage =
buildData.event.commit.message.length > cutOff
? `${buildData.event.commit.message.substring(0, cutOff)}...`
: buildData.event.commit.message;

// Slack channel overwrite from meta data. Job specific only.
const metaMinimizedReplaceVar = `build.meta.notification.slack.${buildData.jobName}.minimized`;
const isMinimized = hoek.reach(buildData, metaMinimizedReplaceVar, {
default: buildData.settings.slack.minimized
});

let message = isMinimized
? // eslint-disable-next-line max-len
`<${pipelineLink}|${buildData.pipeline.scmRepo.name}#${buildData.jobName}> *${notificationStatus}*`
: // eslint-disable-next-line max-len
`*${notificationStatus}* ${STATUSES_MAP[notificationStatus]} <${pipelineLink}|${buildData.pipeline.scmRepo.name} ${buildData.jobName}>`;

const rootDir = hoek.reach(buildData, 'pipeline.scmRepo.rootDir', { default: false });

if (rootDir) {
message = `${message}\n*Source Directory:* ${rootDir}`;
}

const metaMessage = hoek.reach(buildData, 'build.meta.notification.slack.message', { default: false });
const metaVar = `build.meta.notification.slack.${buildData.jobName}.message`;
const buildMessage = hoek.reach(buildData, metaVar, { default: false });

// Use job specific message over generic message.
if (buildMessage) {
message = `${message}\n${buildMessage}`;
} else if (metaMessage) {
message = `${message}\n${metaMessage}`;
}

const attachments = isMinimized
? [
{
fallback: '',
color: COLOR_MAP[notificationStatus],
fields: [
{
title: 'Build',
value: `<${buildData.buildLink}|#${buildData.build.id}>`,
short: true
}
]
}
]
: [
{
fallback: '',
color: COLOR_MAP[notificationStatus],
title: `#${buildData.build.id}`,
title_link: `${buildData.buildLink}`,
// eslint-disable-next-line max-len
text:
`${commitMessage} (<${buildData.event.commit.url}|${truncatedSha}>)` +
`\n${buildData.event.causeMessage}`
}
];

const slackMessage = {
message,
attachments
};

slacker(config.token, buildData.settings.slack.channels, slackMessage);
}

/**
* Handle slack messaging for job status
* @method jobStatus
* @param {Object} jobData
* @param {String} jobData.status Status
* @param {Object} jobData.pipeline Pipeline
* @param {String} jobData.jobName Job name
* @param {String} jobData.pipelineLink Pipeline link
* @param {String} jobData.message Message
* @param {Object} jobData.settings Notification setting
* @param {Object} config Slack notifications config
*/
function jobStatus(jobData, config) {
try {
Joi.attempt(jobData, SCHEMA_JOB_DATA, 'Invalid job data format');
} catch (e) {
return;
}

// Slack channels from configuration
if (typeof jobData.settings.slack === 'string' || Array.isArray(jobData.settings.slack)) {
jobData.settings.slack =
typeof jobData.settings.slack === 'string' ? [jobData.settings.slack] : jobData.settings.slack;
jobData.settings.slack = {
channels: jobData.settings.slack,
statuses: DEFAULT_STATUSES,
minimized: false
};
}

const isMinimized = jobData.settings.slack.minimized;
const message = isMinimized
? // eslint-disable-next-line max-len
`<${jobData.pipelineLink}|${jobData.pipeline.scmRepo.name}#${jobData.jobName}> *${jobData.status}*\n${jobData.message}`
: // eslint-disable-next-line max-len
`*${jobData.status}* ${STATUSES_MAP[jobData.status]} <${jobData.pipelineLink}|${
jobData.pipeline.scmRepo.name
} ${jobData.jobName}>\n${jobData.message}`;

const slackMessage = {
message
};

slacker(config.token, jobData.settings.slack.channels, slackMessage);
}

class SlackNotifier extends NotificationBase {
/**
* Constructs an SlackNotifier
Expand All @@ -78,139 +269,24 @@ class SlackNotifier extends NotificationBase {

/**
* Sets listener on server event of name 'eventName' in Screwdriver
* Currently, event is triggered with a build status is updated
* @method _notify
* @param {Object} buildData - Build data emitted with some event from Screwdriver
* @param {String} event - Event emitted from Screwdriver
* @param {Object} payload - Data emitted with some event from Screwdriver
*/
_notify(buildData) {
// Check buildData format against SCHEMA_BUILD_DATA
try {
Joi.attempt(buildData, SCHEMA_BUILD_DATA, 'Invalid build data format');
} catch (e) {
return;
}
if (Object.keys(buildData.settings).length === 0) {
return;
}

// Slack channel overwrite from meta data. Job specific only.
const metaReplaceVar = `build.meta.notification.slack.${buildData.jobName}.channels`;

const metaDataSlackChannel = hoek.reach(buildData, metaReplaceVar, { default: false });

let channelReplacement;

if (metaDataSlackChannel) {
channelReplacement = metaDataSlackChannel.split(',');
// Remove empty/blank items.
channelReplacement = channelReplacement.filter(x => x.trim() !== '');
}
// Slack channels from configuration
if (typeof buildData.settings.slack === 'string' || Array.isArray(buildData.settings.slack)) {
buildData.settings.slack =
typeof buildData.settings.slack === 'string' ? [buildData.settings.slack] : buildData.settings.slack;
buildData.settings.slack = {
channels: buildData.settings.slack,
statuses: DEFAULT_STATUSES,
minimized: false
};
}
if (channelReplacement) {
buildData.settings.slack.channels = channelReplacement;
}

if (buildData.settings.slack.statuses === undefined) {
buildData.settings.slack.statuses = DEFAULT_STATUSES;
}

// Add for fixed notification
if (buildData.isFixed) {
buildData.settings.slack.statuses.push('FIXED');
}

// Do not change the `buildData.status` directly
// It affects the behavior of other notification plugins
let notificationStatus = buildData.status;

if (buildData.settings.slack.statuses.includes('FAILURE')) {
if (notificationStatus === 'SUCCESS' && buildData.isFixed) {
notificationStatus = 'FIXED';
}
}

if (!buildData.settings.slack.statuses.includes(notificationStatus)) {
_notify(event, payload) {
if (!payload || !payload.settings || Object.keys(payload.settings).length === 0) {
return;
}
const pipelineLink = buildData.buildLink.split('/builds')[0];
const truncatedSha = buildData.event.sha.slice(0, 6);
const cutOff = 150;
const commitMessage =
buildData.event.commit.message.length > cutOff
? `${buildData.event.commit.message.substring(0, cutOff)}...`
: buildData.event.commit.message;

// Slack channel overwrite from meta data. Job specific only.
const metaMinimizedReplaceVar = `build.meta.notification.slack.${buildData.jobName}.minimized`;
const isMinimized = hoek.reach(buildData, metaMinimizedReplaceVar, {
default: buildData.settings.slack.minimized
});

let message = isMinimized
? // eslint-disable-next-line max-len
`<${pipelineLink}|${buildData.pipeline.scmRepo.name}#${buildData.jobName}> *${notificationStatus}*`
: // eslint-disable-next-line max-len
`*${notificationStatus}* ${STATUSES_MAP[notificationStatus]} <${pipelineLink}|${buildData.pipeline.scmRepo.name} ${buildData.jobName}>`;

const rootDir = hoek.reach(buildData, 'pipeline.scmRepo.rootDir', { default: false });

if (rootDir) {
message = `${message}\n*Source Directory:* ${rootDir}`;
}

const metaMessage = hoek.reach(buildData, 'build.meta.notification.slack.message', { default: false });

const metaVar = `build.meta.notification.slack.${buildData.jobName}.message`;

const buildMessage = hoek.reach(buildData, metaVar, { default: false });

// Use job specific message over generic message.
if (buildMessage) {
message = `${message}\n${buildMessage}`;
} else if (metaMessage) {
message = `${message}\n${metaMessage}`;
switch (event) {
case 'build_status':
buildStatus(payload, this.config);
break;
case 'job_status':
jobStatus(payload, this.config);
break;
default:
}
const attachments = isMinimized
? [
{
fallback: '',
color: COLOR_MAP[notificationStatus],
fields: [
{
title: 'Build',
value: `<${buildData.buildLink}|#${buildData.build.id}>`,
short: true
}
]
}
]
: [
{
fallback: '',
color: COLOR_MAP[notificationStatus],
title: `#${buildData.build.id}`,
title_link: `${buildData.buildLink}`,
// eslint-disable-next-line max-len
text:
`${commitMessage} (<${buildData.event.commit.url}|${truncatedSha}>)` +
`\n${buildData.event.causeMessage}`
}
];
const slackMessage = {
message,
attachments
};

slacker(this.config.token, buildData.settings.slack.channels, slackMessage);
}

static validateConfig(config) {
Expand Down
14 changes: 5 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
"main": "index.js",
"scripts": {
"pretest": "eslint .",
"test": "nyc --report-dir ./artifacts/coverage --reporter=lcov mocha --reporter mocha-multi-reporters --reporter-options configFile=./mocha.config.json --recursive --timeout 4000 --retries 1 --exit --allow-uncaught true --color true",
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
"test": "nyc --report-dir ./artifacts/coverage --reporter=lcov mocha --reporter mocha-multi-reporters --reporter-options configFile=./mocha.config.json --recursive --timeout 4000 --retries 1 --exit --allow-uncaught true --color true"
},
"repository": {
"type": "git",
"url": "git@github.com:screwdriver-cd/notifications-slack.git"
"url": "git+https://github.com/screwdriver-cd/notifications-slack.git"
},
"homepage": "https://github.com/screwdriver-cd/notifications-slack",
"bugs": "https://github.com/screwdriver-cd/screwdriver/issues",
Expand Down Expand Up @@ -47,14 +46,11 @@
"@slack/web-api": "^6.4.0",
"joi": "^17.4.0",
"samsam": "^1.3.0",
"screwdriver-data-schema": "^21.0.0",
"screwdriver-data-schema": "^21.28.1",
"screwdriver-logger": "^1.0.2",
"screwdriver-notifications-base": "^3.0.3"
"screwdriver-notifications-base": "^3.2.0"
},
"release": {
"debug": false,
"verifyConditions": {
"path": "./node_modules/semantic-release/src/lib/plugin-noop.js"
}
"debug": false
}
}
Loading

0 comments on commit fb8b84d

Please sign in to comment.