基于GitLab合并请求和提交备注自动进行Jira状态转换的Webhook脚本

内容目录

中文版本

具体功能如下:

  1. 自动处理GitLab推送和合并请求事件

    • 在推送事件中,根据提交备注中的Jira issue key和关键词进行状态转换。
    • 在合并请求创建(opened)时,根据配置自动转换Jira issue状态。
    • 在合并请求完成(merged)时,根据合并请求标题中的关键词进一步转换Jira issue状态。
  2. 支持多项目配置

    • 脚本根据项目的不同,应用各自的状态转换配置。
  3. 安全校验

    • 使用GitLab Webhook的Secret Token验证请求的合法性。

详细功能描述

  • 推送事件处理:当检测到推送事件时,脚本从提交信息中提取Jira issue key,并根据配置中的关键词匹配执行状态转换。
  • 合并请求事件处理:当合并请求创建或合并完成时,脚本根据合并请求的标题进行状态转换。
    • 在合并请求创建时,转换为初始配置的状态。
    • 在合并请求合并时,根据标题中的关键词匹配进行状态转换。
  • 多项目支持:脚本可以根据不同的项目应用不同的状态转换规则。
  • 安全性:通过校验GitLab Webhook的Secret Token确保请求的合法性。

示例配置和用法

  • config.json 中配置各个项目的状态转换规则。
  • 启动脚本并在GitLab项目中设置Webhook,确保推送事件和合并请求事件已启用。

通过这样的标题和详细描述,可以清楚地表达该脚本的功能和使用场景。

业务逻辑

提交合并请求 jira移动事务到指定状态1
合并完成 jira移动事务到指定状态2 如果有额外匹配的关键字jira移动到指定状态2

配置文件示例

config.json

{
    "port": 5000,
    "jira_base_url": "https://your-jira-instance.atlassian.net/rest/api/latest",
    "jira_username": "your_jira_username",
    "jira_api_key": "your_jira_api_key",
    "secret_token": "your_secret_token",
    "transition_mappings": {
        "JGT": {
            "create": "2",
            "complete": "3",
            "keywords": {
                "dev": "6",
                "jgt4": "4"
            }
        },
        "ABC": {
            "create": "1",
            "complete": "2",
            "keywords": {
                "test": "5",
                "release": "7"
            }
        }
    }
}

配置文件:在 config.json 中为每个项目定义创建和完成合并请求的状态ID,以及基于关键字的状态转换映射。

transitions id

transitions id获取接口地址,F12 response查看
https://your.atlassian.net/rest/api/latest/issue/JGT-806/transitions
create 创建merge请求流转状态
complete 合并完成刘庄状态
keywords title包含关键字合并之后流转状态

jira_api_key

https://id.atlassian.com/manage-profile/security/api-tokens
这个地址创建

jira_username

填写用户邮箱即可

secret_token

本webhook接口密钥 ,自己自定义设置

服务python脚本

vim jira_webhook.py

import json
import re
import requests
import hmac
import hashlib
from flask import Flask, request

# 加载配置文件
with open('config.json') as config_file:
    config = json.load(config_file)

# 从配置文件中读取配置信息
PORT = config['port']
JIRA_BASE_URL = config['jira_base_url']
JIRA_USERNAME = config['jira_username']
JIRA_API_KEY = config['jira_api_key']
SECRET_TOKEN = config['secret_token']
TRANSITION_MAPPINGS = config['transition_mappings']
ISSUE_KEY_PATTERN = re.compile(r'\b([A-Z]+-\d+)\b')

# 处理GitLab Webhook的函数
def handle_webhook(request):
    signature = request.headers.get("X-Gitlab-Token")
    if not signature or not hmac.compare_digest(signature, SECRET_TOKEN):
        print("Invalid signature.")
        return "Invalid signature", 403

    data = json.loads(request.data)
    print(data)
    # 检查事件类型
    event_type = request.headers.get("X-Gitlab-Event")
    if event_type == "Push Hook":
        handle_push_event(data)
    elif event_type == "Merge Request Hook":
        handle_merge_request_event(data)
    else:
        print("Unhandled event type: ", event_type)
        return "Unhandled event type", 400

def handle_push_event(data):
    commit_message = data["commits"][0]["message"]
    issue_key_match = ISSUE_KEY_PATTERN.search(commit_message)
    if not issue_key_match:
        print("No issue key found in commit message.")
        return "No issue key found", 400

    issue_key = issue_key_match.group(1)
    project_key = issue_key.split('-')[0]

    if project_key in TRANSITION_MAPPINGS:
        transition_mapping = TRANSITION_MAPPINGS[project_key]
        process_transitions(issue_key, commit_message, transition_mapping)

def handle_merge_request_event(data):
    title = data["object_attributes"]["title"]
    state = data["object_attributes"]["state"]
    print(f'title is {title}')
    print(f'state is {state}')
    issue_key_match = ISSUE_KEY_PATTERN.search(title)
    if not issue_key_match:
        print("No issue key found in merge request title.")
        return "No issue key found", 400

    issue_key = issue_key_match.group(1)
    project_key = issue_key.split('-')[0]

    if project_key in TRANSITION_MAPPINGS:
        transition_mapping = TRANSITION_MAPPINGS[project_key]

        if state == "opened":
            transition_id = transition_mapping.get("create")
            if transition_id:
                transition_issue(issue_key, transition_id)

        elif state == "merged":
            transition_id = transition_mapping.get("complete")
            if transition_id:
                transition_issue(issue_key, transition_id)

            # 根据标题中的关键字进行进一步的状态转换
            keyword_transition_mapping = transition_mapping.get("keywords", {})
            for keyword, trans_id in keyword_transition_mapping.items():
                if keyword.lower() in title.lower():
                    transition_issue(issue_key, trans_id)
                    break
    else:
        print(f"No configuration found for project: {project_key}. Skipping processing.")
def process_transitions(issue_key, message, transition_mapping):
    for keyword in transition_mapping:
        if keyword.lower() in message.lower():
            transition_id = transition_mapping[keyword]
            transition_issue(issue_key, transition_id)
            break

def transition_issue(issue_key, transition_id):
    url = f"{JIRA_BASE_URL}/issue/{issue_key}/transitions"
    headers = {
        "Content-Type": "application/json",
    }
    auth = (JIRA_USERNAME, JIRA_API_KEY)
    data = {
        "transition": {"id": transition_id}
    }
    response = requests.post(url, headers=headers, auth=auth, json=data)

    if response.status_code == 204:
        print(f"Issue {issue_key} transitioned {transition_id}  successfully.")
    else:
        print(f"Failed to transition issue {issue_key}. Status code: {response.status_code}")

if __name__ == "__main__":
    app = Flask(__name__)

    @app.route("/", methods=["POST"])
    def webhook():
        handle_webhook(request)
        return "Webhook received", 200

    app.run(host='127.0.0.1', port=PORT)

运行

python3 jira_webhook.py

后台运行

nohup python3 jira_webhook.py >/dev/null  2>&1 &

gitlab 添加webhook

设置>>webhooks
根据需求看Trigger是否勾选Push events,我这边业务环境进需要对merge进行jira状态变更Trigger仅勾选Merge request events

1.git根据merge 请求title变更jira状态

webhook Trigger需勾选Merge request events
不区分大小写
merge title 为JGT-4 DEV 提交合并请求时transition_id更改为2 完成合并时因为有关键字DEV transition_id最终更改为6
如果 title 为 JGT-4 JUST DO IT,没有任何匹配关键字,提交合并请求时transition_id更改为2, 完成合并时transition_id最终更改为3

2. push 提交更改状态

webhook Trigger需勾选Push events
commit_message里面提取isuse_id和关键字
push没有处理流程 将会根据关键字直接触发transition_id变更

English Version

Title:

“Webhook Script for Automating Jira Status Transitions Based on GitLab Merge Requests and Commit Messages”

Detailed Function Description:

  1. Automatic Handling of GitLab Push and Merge Request Events:

    • For push events, the script transitions Jira issue statuses based on the Jira issue key and keywords found in commit messages.
    • For merge request events:
      • When a merge request is created (opened), the script automatically transitions the Jira issue to a configured status.
      • When a merge request is merged, the script transitions the Jira issue to a new status based on keywords found in the merge request title.
  2. Multi-Project Configuration Support:

    • The script applies different status transition configurations for different projects based on the project key.
  3. Security Validation:

    • The script validates the legitimacy of requests using the Secret Token from GitLab Webhook settings.

Detailed Features:

  • Push Event Handling: Upon detecting a push event, the script extracts the Jira issue key from commit messages and performs status transitions based on configured keywords.
  • Merge Request Event Handling:
    • When a merge request is opened, the script transitions the Jira issue to an initial configured status.
    • When a merge request is merged, the script transitions the Jira issue to a new status based on keywords found in the merge request title.
  • Multi-Project Support: The script can apply different transition rules for different projects based on their configurations.
  • Security: The script ensures the validity of requests by verifying the Secret Token from the GitLab Webhook.

Example Configuration and Usage:

  • Configure the status transition rules for each project in the config.json file.
  • Start the script and set up a Webhook in your GitLab project, ensuring that push events and merge request events are enabled.

Business Logic

Commit merge request jira move transaction to specified state 1
merge complete jira move transaction to specified state 2 if there are additional matching keywords jira move to specified state 2

Configuration file example

config.json

{
    "port": 5000
    "jira_base_url": "https://your-jira-instance.atlassian.net/rest/api/latest",
    "jira_username": "your_jira_username",
    "jira_api_key": "your_jira_api_key",
    "secret_token": "your_secret_token",
    "transition_mappings": {
        "JGT ": {
            "create": "2 ",
            "complete": "3 ",
            "keywords ": {
                "dev": "6 ",
                "jgt4": "4"
            }
        },
        "ABC ": {
            "create": "1 ",
            "complete": "2 ",
            "keywords ": {
                "test": "5 ",
                "release": "7"
            }
        }
    }
}

Configuration file: The state ID that creates and completes the merge request is defined in config.json for each project, as well as the key-based state transition mapping.

transitions id

transitions the id to obtain the interface address, F12 response view
https://your.atlassian.net/rest/api/latest/issue/JGT-806/transitions
create create merge request flow status
complete Merger Completed Liu Zhuang Status
keywords title contains the circulation status after keyword merging

jira_api_key

https://id.atlassian.com/manage-profile/security/api-tokens
This address is created.

jira_username

Fill in the user mailbox

secret_token

This webhook interface key, own custom settings

Service python script

vim jira_webhook.py

import json
import re
import requests
import hmac
import hashlib
from flask import Flask, request

# Load the configuration file
with open('config.json') as config_file:
    config = json.load(config_file)

# Read configuration information from the configuration file
PORT = config['port']
JIRA_BASE_URL = config['jira_base_url']
JIRA_USERNAME = config['jira_username']
JIRA_API_KEY = config['jira_api_key']
SECRET_TOKEN = config['secret_token']
TRANSITION_MAPPINGS = config['transition_mappings']
ISSUE_KEY_PATTERN = re.compile(r'\ B ([A-Z]+-\d+)\ B ')

# Functions that handle GitLab Webhook
def handle_webhook(request):
    signature = request.headers.get("X-Gitlab-Token")
    if not signature or not hmac.compare_digest(signature, SECRET_TOKEN):
        print("Invalid signature.")
        return "Invalid signature", 403

    data = json.loads(request.data)
    print(data)
    # Check the event type
    event_type = request.headers.get("X-Gitlab-Event")
    if event_type == "Push Hook ":
        handle_push_event(data)
    elif event_type == "Merge Request Hook ":
        handle_merge_request_event(data)
    else:
        print("Unhandled event type: ", event_type)
        return "Unhandled event type", 400

def handle_push_event(data):
    commit_message = data["commits"][0]["message"]
    issue_key_match = ISSUE_KEY_PATTERN.search(commit_message)
    if not issue_key_match:
        print("No issue key found in commit message.")
        return "No issue key found", 400

    issue_key = issue_key_match.group(1)
    project_key = issue_key.split('-')[0]

    if project_key in TRANSITION_MAPPINGS:
        transition_mapping = TRANSITION_MAPPINGS[project_key]
        process_transitions(issue_key, commit_message, transition_mapping)

def handle_merge_request_event(data):
    title = data["object_attributes"]["title"]
    state = data["object_attributes"]["state"]
    print(f'title is {title}')
    print(f'state is {state}')
    issue_key_match = ISSUE_KEY_PATTERN.search(title)
    if not issue_key_match:
        print("No issue key found in merge request title.")
        return "No issue key found", 400

    issue_key = issue_key_match.group(1)
    project_key = issue_key.split('-')[0]

    if project_key in TRANSITION_MAPPINGS:
        transition_mapping = TRANSITION_MAPPINGS[project_key]

        if state == "opened ":
            transition_id = transition_mapping.get("create")
            if transition_id:
                transition_issue(issue_key, transition_id)

        elif state == "merged ":
            transition_id = transition_mapping.get("complete")
            if transition_id:
                transition_issue(issue_key, transition_id)

            # Make further state transitions based on keywords in the title
            keyword_transition_mapping = transition_mapping.get("keywords", {})
            for keyword, trans_id in keyword_transition_mapping.items():
                if keyword.lower() in title.lower():
                    transition_issue(issue_key, trans_id)
                    break
    else:
        print(f"No configuration found for project: {project_key}. Skipping processing.")
def process_transitions(issue_key, message, transition_mapping):
    for keyword in transition_mapping:
        if keyword.lower() in message.lower():
            transition_id = transition_mapping[keyword]
            transition_issue(issue_key, transition_id)
            break

def transition_issue(issue_key, transition_id):
    url = f"{JIRA_BASE_URL}/issue/{issue_key}/transitions"
    headers = {
        "Content-Type": "application/json ",
    }
    auth = (JIRA_USERNAME, JIRA_API_KEY)
    data = {
        "transition": {"id": transition_id}
    }
    response = requests.post(url, headers=headers, auth=auth, json=data)

    if response.status_code == 204:
        print(f"Issue {issue_key} transitioned {transition_id} successfully.")
    else:
        print(f"Failed to transition issue {issue_key}. Status code: {response.status_code}")

if __name__ == "__main __":
    app = Flask(__name__)

    @app.route("/", methods=["POST"])
    def webhook():
        handle_webhook(request)
        return "Webhook received", 200

    app.run(host='127.0.0.1', port=PORT)

Run

python3 jira_webhook.py

Background Run

nohup python3 jira_webhook.py >/dev/null 2>&1 &

gitlab Add webhook

Settings>>webhooks
According to the requirements, see whether Push events is checked in the Trigger. Trigger the business environment on my side needs to change the jira status of merge, only events request Merge is checked.

1.git changes jira status according to merge request title

webhook Trigger need to check Merge request events
Case insensitive
The merge title is changed to 2 when the merge request is submitted by the JGT-4 DEV, and the transition_id is changed to 6 when the merge is completed due to the keyword DEV transition_id.
If title is JGT-4 JUST DO IT without any matching keywords, the transition_id changes to 2 when the merge request is submitted, and the transition_id changes to 3 when the merge is completed

2. push commit change status

webhook Trigger need to check Push events
Extract isuse_id and keywords inside the commit_message
No push process will directly trigger transition_id changes based on keywords.

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注