diff --git a/README.md b/README.md
index c8d2e29..f86a4c4 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@ todo:
- [X] 使用mustache模板
- [X] 增加note通知
+- [X] 增加system hook通知,参考 [gitlab system hook](https://docs.gitlab.com/ee/administration/system_hooks.html)
- [ ] 增加消息模板配置文件
- [ ] 支持飞书机器人
- [ ] 支持按天统计数据
diff --git a/app/controller/home.js b/app/controller/home.js
index bcf559c..d042269 100644
--- a/app/controller/home.js
+++ b/app/controller/home.js
@@ -1,31 +1,71 @@
'use strict';
const Controller = require('egg').Controller;
+const { X_GITLAB_EVENT } = require('../imports/const');
class HomeController extends Controller {
async index() {
- const { ctx } = this;
+ const { ctx, config } = this;
const { path = '' } = ctx.params;
- const webhookUrl =
- process.env['WEBHOOK_URL' + (path ? '_' + path.toUpperCase() : '')];
-
- this.logger.debug('webhookUrl', webhookUrl);
- this.logger.info('request body: ', ctx.request.body);
- const message = await ctx.service.webhook.translateMsg(ctx.request.body);
+ this.logger.info('====> request headers: ', ctx.request.headers);
+ this.logger.info('====> request body: ', ctx.request.body);
- if (!message) {
- this.logger.info('====> message is empty, suppressed.');
- ctx.body = { msg: 'message is empty or not supported, suppressed.' };
+ // platform check
+ const platform = process.env['PLATFORM'] || config.platform;
+ this.logger.debug('platform: ', platform);
+ if (config.supportPlatforms.indexOf(platform) === -1) {
+ const errMsg = `====> platform "${platform}" is not supported, only support: ${config.supportPlatforms.join(
+ ', '
+ )}`;
+ this.logger.error(errMsg);
+ ctx.body = {
+ error: errMsg,
+ };
return;
}
+ // webhookUrl check
+ const webhookUrl =
+ process.env['WEBHOOK_URL' + (path ? '_' + path.toUpperCase() : '')];
+
+ this.logger.debug('webhookUrl: ', webhookUrl);
+
+ // if webhookUrl not match, exit
if (!webhookUrl) {
- this.logger.error('webhook url error, webhookUrl: ' + webhookUrl);
+ this.logger.error('====> webhook url error, webhookUrl: ' + webhookUrl);
ctx.body = {
error: 'webhook url error, webhookUrl: ' + webhookUrl,
};
return;
}
+ let gitlabEvent = X_GITLAB_EVENT.push;
+
+ // check x-gitlab-event
+ if (ctx.request.headers['x-gitlab-event']) {
+ gitlabEvent = ctx.request.headers['x-gitlab-event'];
+ if (Object.values(X_GITLAB_EVENT).indexOf(gitlabEvent) === -1) {
+ const errMsg = `====> x-gitlab-event "${gitlabEvent}" is not supported}`;
+ this.logger.error(errMsg);
+ ctx.body = {
+ error: errMsg,
+ };
+ return;
+ }
+ }
+
+ const message = await ctx.service.webhook.translateMsg(
+ ctx.request.body,
+ platform,
+ gitlabEvent
+ );
+
+ if (!message) {
+ this.logger.info('====> message is empty, suppressed.');
+ ctx.body = {
+ msg: '====> message is empty or not supported, suppressed.',
+ };
+ return;
+ }
const result = await ctx.curl(webhookUrl, {
method: 'POST',
diff --git a/app/imports/const.js b/app/imports/const.js
new file mode 100644
index 0000000..758bd3a
--- /dev/null
+++ b/app/imports/const.js
@@ -0,0 +1,41 @@
+const OBJECT_KIND = {
+ push: 'push',
+ tag_push: 'tag_push',
+ issue: 'issue',
+ note: 'note',
+ merge_request: 'merge_request',
+ wiki_page: 'wiki_page',
+ pipeline: 'pipeline',
+ build: 'build', // todo
+};
+
+const EVENT_TYPE = {
+ group_create: 'group_create',
+ group_destroy: 'group_destroy',
+ group_rename: 'group_rename',
+ key_create: 'key_create',
+ key_destroy: 'key_destroy',
+ project_create: 'project_create',
+ project_destroy: 'project_destroy',
+ project_rename: 'project_rename',
+ project_transfer: 'project_transfer',
+ project_update: 'project_update',
+ repository_update: 'repository_update',
+ user_add_to_group: 'user_add_to_group',
+ user_add_to_team: 'user_add_to_team',
+ user_create: 'user_create',
+ user_destroy: 'user_destroy',
+ user_failed_login: 'user_failed_login',
+ user_remove_from_group: 'user_remove_from_group',
+ user_remove_from_team: 'user_remove_from_team',
+ user_rename: 'user_rename',
+ user_update_for_group: 'user_update_for_group',
+ user_update_for_team: 'user_update_for_team',
+};
+
+const X_GITLAB_EVENT = {
+ push: 'Push Hook',
+ system: 'System Hook',
+};
+
+module.exports = { OBJECT_KIND, EVENT_TYPE, X_GITLAB_EVENT };
diff --git a/app/service/webhook.js b/app/service/webhook.js
index d11b9f8..abd5226 100644
--- a/app/service/webhook.js
+++ b/app/service/webhook.js
@@ -4,6 +4,8 @@ const Service = require('egg').Service;
const moment = require('moment');
const Mustache = require('mustache');
+const { OBJECT_KIND, X_GITLAB_EVENT, EVENT_TYPE } = require('../imports/const');
+
// set default lang
moment.locale('zh-cn');
@@ -11,59 +13,53 @@ moment.locale('zh-cn');
Mustache.escape = text =>
text.toString().replace('\n', ' ').replace(/\s+/g, ' ');
-const OBJECT_KIND = {
- push: 'push',
- tag_push: 'tag_push',
- issue: 'issue',
- note: 'note',
- merge_request: 'merge_request',
- wiki_page: 'wiki_page',
- pipeline: 'pipeline',
- build: 'build', // todo
-};
-
-const REDIS_KEY = {
- pipeline: id => `gitlab.pipeline.${id}`,
-};
-
-const REDIS_VAL = {
- pipeline: ({ pipelineId, stages, status, duration, builds }) => {
+// all customized variables start with GB_
+class WebhookService extends Service {
+ async translateMsg(data = {}, platform, gitlabEvent) {
+ this.platform = platform;
+
+ const { object_kind } = data;
+
+ const content = [];
+ switch (gitlabEvent) {
+ case X_GITLAB_EVENT.push:
+ this.pushHookHandler(content, data);
+ break;
+ case X_GITLAB_EVENT.system:
+ // system hook to push hook if object_kind exists
+ OBJECT_KIND[object_kind]
+ ? this.pushHookHandler(content, data)
+ : this.systemHookHandler(content, data);
+ break;
+ default:
+ // controller make sure not to here
+ break;
+ }
+
return {
- type: 'pipeline',
- id: pipelineId,
- duration: duration,
- durationMin: Math.round(duration / 60 - 0.5),
- durationSec: duration % 60,
- status: status,
- stages: stages,
- builds: builds,
+ msgtype: 'markdown',
+ markdown: { content: content.join(' \n ') },
};
- },
-};
+ }
+
+ async pushHookHandler(content, data) {
+ const { object_kind } = data;
-// all customized variables start with GB_
-class WebhookService extends Service {
- async translateMsg(data) {
- const { object_kind } = data || {};
if (!OBJECT_KIND[object_kind]) {
return {};
}
let res = true;
- const content = [];
switch (object_kind) {
case OBJECT_KIND.push:
res = await this.assemblePushMsg(content, data);
break;
-
case OBJECT_KIND.pipeline:
res = await this.assemblePipelineMsg(content, data);
break;
-
case OBJECT_KIND.merge_request:
res = await this.assembleMergeMsg(content, data);
break;
-
case OBJECT_KIND.tag_push:
res = await this.assembleTagPushMsq(content, data);
break;
@@ -80,12 +76,36 @@ class WebhookService extends Service {
res = false;
break;
}
- if (!res) return false;
+ return res;
+ }
- return {
- msgtype: 'markdown',
- markdown: { content: content.join(' \n ') },
- };
+ async systemHookHandler(content, data) {
+ const template = this.getTemplateByPlatform(this.platform);
+
+ const { event_name } = data;
+ if (!EVENT_TYPE[event_name]) {
+ const errMsg = `====> event_name "${event_name}" is not supported, suppressed}`;
+ this.logger.error(errMsg);
+ return false;
+ }
+
+ this.logger.debug('template: ', template.push);
+ this.logger.debug('content: ', content);
+ if (template[event_name]) {
+ // match template by event_name first
+ content.push(Mustache.render(template[event_name], data));
+ } else {
+ const event_arr = event_name.split('_');
+ const event_action = event_arr[0] + '_action';
+ if (!template[event_action]) {
+ const errMsg = `====> event_action "${event_action}" is not supported, suppressed}`;
+ this.logger.error(errMsg);
+ return false;
+ }
+ content.push(Mustache.render(template[event_action], data));
+ }
+
+ return content;
}
async assemblePushMsg(content, data) {
@@ -103,7 +123,7 @@ class WebhookService extends Service {
GB_op = '将代码推至';
}
- const template = this.getTemplateByPlatform('qywx');
+ const template = this.getTemplateByPlatform(this.platform);
this.logger.debug('template: ', template.push);
this.logger.debug('content: ', content);
@@ -180,7 +200,7 @@ class WebhookService extends Service {
// gitlab 11.3 未支持source参数
GB_sourceString = `${name}`;
}
- const template = this.getTemplateByPlatform('qywx');
+ const template = this.getTemplateByPlatform(this.platform);
const pipeline = Mustache.render(template.pipeline, {
...data,
GB_pipelineId,
@@ -222,7 +242,7 @@ class WebhookService extends Service {
default:
}
- const template = this.getTemplateByPlatform('qywx');
+ const template = this.getTemplateByPlatform(this.platform);
const merge_request = Mustache.render(template.merge_request, {
...data,
GB_stateAction,
@@ -247,7 +267,7 @@ class WebhookService extends Service {
GB_op = '删除';
}
- const template = this.getTemplateByPlatform('qywx');
+ const template = this.getTemplateByPlatform(this.platform);
const tag_push = Mustache.render(template.tag_push, {
...data,
GB_tag,
@@ -262,7 +282,7 @@ class WebhookService extends Service {
const { object_attributes = {} } = data;
const { state } = object_attributes;
- const template = this.getTemplateByPlatform('qywx');
+ const template = this.getTemplateByPlatform(this.platform);
const issue = Mustache.render(template.issue, {
...data,
GB_state: this.formatStatus(state),
@@ -274,7 +294,7 @@ class WebhookService extends Service {
const { object_attributes = {} } = data;
const { action } = object_attributes;
- const template = this.getTemplateByPlatform('qywx');
+ const template = this.getTemplateByPlatform(this.platform);
const issue = Mustache.render(template.wiki, {
...data,
GB_action: this.formatAction(action),
@@ -286,7 +306,7 @@ class WebhookService extends Service {
const { object_attributes = {} } = data;
const { action } = object_attributes;
- const template = this.getTemplateByPlatform('qywx');
+ const template = this.getTemplateByPlatform(this.platform);
const note = Mustache.render(template.note, {
...data,
GB_action: this.formatAction(action),
diff --git a/config/config.default.js b/config/config.default.js
index 60f2e3c..e4d6997 100644
--- a/config/config.default.js
+++ b/config/config.default.js
@@ -26,7 +26,8 @@ module.exports = appInfo => {
// add your user config here
const userConfig = {
- platform: ['qywx'],
+ supportPlatforms: ['qywx'],
+ platform: 'qywx',
response: {
qywx: {
content: 'markdown.content',
@@ -47,8 +48,7 @@ module.exports = appInfo => {
},
template: {
qywx: {
- push:
-`\`{{user_name}}\` {{GB_op}} [[{{project.name}} | {{GB_branch}}分支]({{project.web_url}}/tree/{{GB_branch}})]
+ push: `\`{{user_name}}\` {{GB_op}} [[{{project.name}} | {{GB_branch}}分支]({{project.web_url}}/tree/{{GB_branch}})]
> 包含\`{{total_commits_count}}\`个提交, \`{{GB_changes.added}}\`新增 | \`{{GB_changes.modified}}\`修改 | \`{{GB_changes.removed}}\`删除
{{#commits}}
> 》 \`{{author.name}}\`: [{{title}}]({{url}})
@@ -57,22 +57,20 @@ module.exports = appInfo => {
{{#project}}项目信息: [[{{name}} / {{namespace}}]({{web_url}})]{{/project}}
`,
- pipeline:
-`[[#{{GB_pipelineId}}流水线 | {{object_attributes.ref}}分支]({{GB_pipelineUrl}})] {{GB_status.str}},由\`{{user.name}}\`通过\`{{GB_sourceString}}\`触发。
+ pipeline: `[[#{{GB_pipelineId}}流水线 | {{object_attributes.ref}}分支]({{GB_pipelineUrl}})] {{GB_status.str}},由\`{{user.name}}\`通过\`{{GB_sourceString}}\`触发。
> **流水线详情:** 耗时\`{{GB_duration}}\`, {{object_attributes.stages.length}}个阶段 {{#object_attributes.stages}}{{.}} | {{/object_attributes.stages}}
> {{#merge_request}}**合并详情:** [{{title}}]({{url}}), \`{{source_branch}}\`合并至\`{{target_branch}}\`{{/merge_request}}
> {{#commit}}**提交详情:** \`{{author.name}}\`: [{{message}}]({{url}}){{/commit}}
> **编译详情**:
{{#GB_builds}}> 》 \`{{stage}}\`:
-{{#builds}}> - [\`{{name}}\`{{#GB_duration}} ({{GB_duration}}){{/GB_duration}} -> {{GB_status.str}}{{#failure_reason}}, {{failure_reason}}{{/failure_reason}} {{#GB_user}}({{GB_user}}){{/GB_user}}]({{GB_url}})
+{{#builds}}> - [\`{{name}}\`{{#GB_duration}}({{GB_duration}}){{/GB_duration}} -> {{GB_status.str}}{{#failure_reason}}, {{failure_reason}}{{/failure_reason}} {{#GB_user}}({{GB_user}}){{/GB_user}}]({{GB_url}})
{{/builds}}
{{/GB_builds}}
{{#project}}项目信息: [[{{name}} / {{namespace}}]({{web_url}})]{{/project}}
`,
- merge_request:
-`{{#GB_stateAction}}{{GB_stateAction}} :{{/GB_stateAction}}\`{{user.name}}\`**{{GB_stateString}}{{#object_attributes}}**[[#{{iid}}合并请求 {{title}}]({{iid}})],从\`{{source_branch}}\`合并至\`{{target_branch}}\`{{/object_attributes}}
+ merge_request: `{{#GB_stateAction}}{{GB_stateAction}} :{{/GB_stateAction}}\`{{user.name}}\`**{{GB_stateString}}{{#object_attributes}}**[[#{{iid}}合并请求 {{title}}]({{iid}})],从\`{{source_branch}}\`合并至\`{{target_branch}}\`{{/object_attributes}}
> **MR详情:**
> 提交时间: {{GB_updated_at}}
> 提交详情:
@@ -80,15 +78,13 @@ module.exports = appInfo => {
{{#project}}项目信息: [[{{name}} / {{namespace}}]({{web_url}})]{{/project}}
`,
- tag_push:
-`\`{{user_name}}\`{{GB_op}}标签 [[{{project.name}} | {{GB_tag}}]({{web_url}}/-/tags/{{GB_tag}})]。
+ tag_push: `\`{{user_name}}\`{{GB_op}}标签 [[{{project.name}} | {{GB_tag}}]({{web_url}}/-/tags/{{GB_tag}})]。
> 包含\`{{total_commits_count}}\`个提交, \`{{GB_changes.added}}\`新增 | \`{{GB_changes.modified}}\`修改 | \`{{GB_changes.removed}}\`删除
{{#commits}}
> 》 \`{{author.name}}\`: [{{title}}]({{url}})
{{/commits}}
`,
- issue:
-`\`{{user.name}}\`{{GB_state.str}} {{#object_attributes}}[[#{{id}}议题]({{url}})]{{/object_attributes}}
+ issue: `\`{{user.name}}\`{{GB_state.str}} {{#object_attributes}}[[#{{id}}议题]({{url}})]{{/object_attributes}}
> **议题详情:**
{{#object_attributes}}> 标题: [{{title}}]({{url}})
> 描述: {{description}}
@@ -98,14 +94,12 @@ module.exports = appInfo => {
{{#project}}项目信息: [[{{name}} / {{namespace}}]({{web_url}})]{{/project}}
`,
- wiki:
-`\`{{user.name}}\` {{#object_attributes}}{{GB_action.actionString}} WIKI页 [{{title}}](url)
+ wiki: `\`{{user.name}}\` {{#object_attributes}}{{GB_action.actionString}} WIKI页 [{{title}}](url)
> 内容: {{content}}{{/object_attributes}}
{{#project}}项目信息: [[{{name}} / {{namespace}}]({{web_url}})]{{/project}}
`,
- note:
-`\`{{user.name}}\` 评论了 {{#object_attributes}} [{{note}}](url)
+ note: `\`{{user.name}}\` 评论了 {{#object_attributes}} [{{note}}](url)
> **评论详情:**
> 类型: \`{{noteable_type}}\`{{/object_attributes}}
{{#merge_request}}> 标题: [{{title}}]({{url}})
@@ -118,7 +112,12 @@ module.exports = appInfo => {
文件: {{file_name}}{{/snippet}}
{{#project}}项目信息: [[{{name}} / {{namespace}}]({{web_url}})]{{/project}}
-`
+`,
+ project_action: `{{owner_name}} 做了 \`{{event_name}}\` 操作
+> 项目路径:{{path_with_namespace}}`,
+ repository_action: `{{user_name}} 做了 \`{{event_name}}\` 操作
+{{#project}}> 项目信息: [[{{name}} / {{namespace}}]({{web_url}})]{{/project}}`,
+ user_action: '\`{{event_name}}\`: {{name}}({{username}} {{email}})',
},
},
};