├── src └── hooks │ ├── hooks.log │ ├── change-merged │ ├── patchset-created │ ├── change-abandoned │ ├── run_test │ ├── jira-hook.config │ ├── jira-hook.config.example │ └── jira-hook ├── requirements.txt ├── .gitignore ├── install.sh └── README.md /src/hooks/hooks.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | jira==1.0.3 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | jirett.ini 3 | .venv 4 | wheelhouse 5 | dist 6 | -------------------------------------------------------------------------------- /src/hooks/change-merged: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python `dirname $0`/jira-hook --action merged "$@" 3 | -------------------------------------------------------------------------------- /src/hooks/patchset-created: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python `dirname $0`/jira-hook --action new "$@" 3 | -------------------------------------------------------------------------------- /src/hooks/change-abandoned: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python `dirname $0`/jira-hook --action abandoned "$@" 3 | -------------------------------------------------------------------------------- /src/hooks/run_test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python `dirname $0`/jira-hook --action new --change Iad6c380722a2d982c8b5ad96c04a16a4dc0f60c6 --commit c3c567c7adfb165f2501734927e905b60169107e --change-url "http://localhost:8088/30" --branch "refs/for/master" -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ "x$GERRIT_PATH" == "x" ]; 3 | then echo "Usage: GERRIT_PATH= ./install.sh" 4 | exit 1; 5 | fi 6 | # install virtualenv 7 | pip install --use-wheel --no-index --find-links=wheelhouse virtualenv 8 | pip install --use-wheel --no-index --find-links=wheelhouse -r requirements.txt 9 | # copy hooks to gerrit install path 10 | cp -r src/hooks $GERRIT_PATH/ 11 | -------------------------------------------------------------------------------- /src/hooks/jira-hook.config: -------------------------------------------------------------------------------- 1 | [jira] 2 | 3 | #username for jira user 4 | user=admin 5 | 6 | #passowrd for jira user 7 | pass=admin 8 | 9 | #URL to jira instance api calls (http://) 10 | url=http://localhost:8080 11 | 12 | # the command that is used to ssh into the gerrit sshd 13 | gerritcmd=ssh -p 29418 sintonwong@localhost gerrit 14 | 15 | # If you create a custom field for "Gerrit status", put the numeric ID 16 | # here. The ID is found from the URL when editing that custom field. 17 | # if set, it will set it to "pending" when a patch is uploaded, and "merged" 18 | # when it is merged 19 | custom_field= 20 | 21 | # optional comma separated list of jira projects 22 | projects=HELLOWORLD,NEW 23 | 24 | # Set to 'true' if you'd like Jirret to search for Gerrit tracking IDs rather 25 | # than in the subject line for your issue IDs 26 | use_trackingid=false 27 | 28 | # Set to false to disable {quote} markers surrounding comments 29 | enable_quotes=true 30 | -------------------------------------------------------------------------------- /src/hooks/jira-hook.config.example: -------------------------------------------------------------------------------- 1 | [jira] 2 | 3 | #username for jira user 4 | user=admin 5 | 6 | #passowrd for jira user 7 | pass=sphere 8 | 9 | #URL to jira instance rpc calls (http:///rpc/xmlrpc) 10 | url=http://10.13.0.134:8082 11 | 12 | # the command that is used to ssh into the gerrit sshd 13 | gerritcmd=ssh -p 29418 admin@localhost gerrit 14 | 15 | # If you create a custom field for "Gerrit status", put the numeric ID 16 | # here. The ID is found from the URL when editing that custom field. 17 | # if set, it will set it to "pending" when a patch is uploaded, and "merged" 18 | # when it is merged 19 | custom_field= 20 | 21 | # optional comma separated list of jira projects 22 | projects=TUT 23 | 24 | # Set to 'true' if you'd like Jirret to search for Gerrit tracking IDs rather 25 | # than in the subject line for your issue IDs 26 | use_trackingid=false 27 | 28 | # Set to false to disable {quote} markers surrounding comments 29 | enable_quotes=true 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Overview 3 | Gerrit hooks. 4 | * update JIRA issue 5 | * Require JIRA Version >= 7.0.0 6 | 7 | ## Installation Steps 8 | * copy dist/grrit-jira-hook.tar to Gerrit instance server 9 | ``` 10 | # wget https://github.com/sintonwong/jirret/releases/download/v0.2/gerrit-jira-hook.tar.gz 11 | ``` 12 | * install dependencies and copy hooks to gerrit install path 13 | ``` 14 | # mkdir install 15 | # tar -xzvf gerrit-jira-hook.tar.gz -C install 16 | # cd install 17 | # chmod +x install.sh 18 | # GERRIT_PATH= ./install.sh 19 | ``` 20 | * config file. Edit config file at hooks/jira-hook.config. 21 | * check gerrit permission. 22 | ``` 23 | # ssh -p 29418 admin@localhost gerrit 24 | ``` 25 | ## How it works: 26 | ### 1. Modify the “jira-hook.config” file. 27 | ``` 28 | [jira] 29 | 30 | #username for jira user 31 | user=admin 32 | 33 | #passowrd for jira user 34 | pass=admin 35 | 36 | #URL to jira instance api calls (http://) 37 | url=http://localhost:8080 38 | 39 | # the command that is used to ssh into the gerrit sshd 40 | gerritcmd=ssh -p 29418 admin@localhost gerrit 41 | 42 | # If you create a custom field for "Gerrit status", put the numeric ID 43 | # here. The ID is found from the URL when editing that custom field. 44 | # if set, it will set it to "pending" when a patch is uploaded, and "merged" 45 | # when it is merged 46 | custom_field= 47 | 48 | # optional comma separated list of jira projects 49 | # two or more projects seperate with "," 50 | projects=HELLOWORLD 51 | 52 | # Set to 'true' if you'd like Jirret to search for Gerrit tracking IDs rather 53 | # than in the subject line for your issue IDs 54 | use_trackingid=false 55 | 56 | # Set to false to disable {quote} markers surrounding comments 57 | enable_quotes=true 58 | ``` 59 | 60 | ### 2. Create a single user in JIRA and permit it to create comment on JIRA Issues. 61 | ### 3. Check gerrit permission.(add user’s pub key to Gerrit) 62 | ``` 63 | # ssh -p 29418 admin@localhost gerrit 64 | ``` 65 | ### 4. In the user-end, the operation should like this: 66 | ``` 67 | # git add . 68 | # git commit -s 69 | ## remember change prjkey-id to real issue's id. 70 | add new feature. 71 | JIRA:prjkey-id 72 | # git push origin HEAD:refs/for/master 73 | ``` 74 | ### 5. All log saves in “hooks.log”, make it writable! 75 | ``` 76 | # chmod 755 hooks.log 77 | ``` 78 | 79 | ## More Links Related: 80 | * [Gerrit Code Review - Searching Changes](https://review.openstack.org/Documentation/user-search.html) 81 | -------------------------------------------------------------------------------- /src/hooks/jira-hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | # -*- coding:utf-8 -*- 3 | import sys 4 | import ConfigParser 5 | import commands 6 | import re 7 | import getopt 8 | import os.path 9 | import logging 10 | from jira import JIRA 11 | 12 | # Config file path 13 | config_path = os.path.dirname(__file__) + '/jira-hook.config' 14 | log_path = os.path.dirname(__file__) + '/hooks.log' 15 | config = ConfigParser.RawConfigParser() 16 | config.read(config_path) 17 | 18 | FORMAT = '%(asctime)s %(name)-12s %(levelname)-8s %(message)s' 19 | logging.basicConfig(format=FORMAT, filename=log_path, level=logging.DEBUG) 20 | 21 | # Get config info from ./jira-hook.config 22 | user = config.get('jira', 'user') 23 | password = config.get('jira', 'pass') 24 | url = config.get('jira', 'url') 25 | gerrit_cmd = config.get('jira', 'gerritcmd') 26 | custom_field = config.get('jira', 'custom_field') 27 | 28 | use_trackingid_option = ('jira', 'use_trackingid') 29 | use_trackingid = config.get(*use_trackingid_option).lower() == 'true' \ 30 | if config.has_option(*use_trackingid_option) else False 31 | 32 | enable_quotes_option = ('jira', 'enable_quotes') 33 | enable_quotes = config.get(*enable_quotes_option).lower() == 'true' \ 34 | if config.has_option(*enable_quotes_option) else True 35 | 36 | jira = JIRA(url, basic_auth=(user, password), logging=True, ) 37 | 38 | # If no args, show usage info. 39 | def show_usage(): 40 | print '\nNormal hook usage: ' + sys.argv[0] + ' --action [new|merged|abandoned] --change --commit --change-url --branch ' 41 | print '\nTo automatically update projects list in config: ' + sys.argv[0] + ' update-projects' 42 | 43 | 44 | """ 45 | # Old Code, waiting to be fixed. 46 | def updateProjects(): 47 | projects = rpc.jira1.getProjectsNoSchemes(auth) 48 | s = '' 49 | for p in projects: 50 | s += p['key'] + ',' 51 | 52 | s = s.rstrip(',') 53 | config.set('jira', 'projects', s) 54 | 55 | with open(config_path, 'wb') as configfile: 56 | config.write(configfile) 57 | 58 | print '\nAdded "' + s + '" to ' + config_path + '\n' 59 | """ 60 | 61 | def make_comment_message(what, id, hash, url, who, branch, subject, gerrit_prj, ): 62 | """Make commment message. Not send http request yet.""" 63 | template = """ 64 | {flag} {who} {summary}: {url} 65 | {{quote}} 66 | Subject: *{subject}* 67 | Project: {project} 68 | Branch: {branch} 69 | ChangeId: {change_id} 70 | Commit: {commit_id} 71 | {{quote}} 72 | 73 | {{color:#cccccc}}Generated by Gerrit hooks{{color}} 74 | """ 75 | flag = '(i)' 76 | if who.find('(') != -1: 77 | who = re.search('(.+)\s+\(.+\)', who).group(1) 78 | who = '[~' + who + ']' 79 | if what == 'new': 80 | summary = ' uploaded a new patchset: ' 81 | status = 'pending' 82 | elif what == 'merged': 83 | flag = '(/)' 84 | summary = ' merged' 85 | status = 'merged' 86 | elif what == 'abandoned': 87 | summary = ' abandoned' 88 | status = 'abandoned' 89 | else: 90 | print 'Illegal argument, stopping: ' + what 91 | exit() 92 | return template.format(flag=flag, who=who, summary=summary, url=url, subject=subject, project=gerrit_prj, branch=branch, change_id=id, commit_id=hash, topic=branch) 93 | 94 | 95 | def update_ticket(what, id, hash, url, who, branch,topic): 96 | status, out = commands.getstatusoutput(gerrit_cmd + ' query --format TEXT change:' + id + ' limit:1') 97 | logging.debug("[update_ticket] Running Gerrit Query Result:\n" + out) 98 | if (status != 0): 99 | print 'Could not run gerrit query command.\n' + out 100 | logging.error("Error, Could not run gerrit query command.\n" + out) 101 | exit() 102 | subject = re.search('subject: (.*)\n', out).group(1); 103 | # group(1)匹配最后一个,通过运行gerrit query返回的结果中,获取主题、gerrit项目名等信息 104 | logging.debug("[update_ticket] RE Searched Subject Result :" + subject) 105 | 106 | gerrit_prj = re.search('project: (.*)\n', out).group(1); 107 | logging.debug("[update_ticket] RE Searched Subject gerrit_prj :" + gerrit_prj) 108 | projects = config.get('jira', 'projects').split(',') 109 | 110 | issues = [] 111 | # account issue. 112 | issue_needle = '(%s-[0-9]+)' 113 | issue_haystack = subject 114 | if use_trackingid: 115 | issue_needle = ' +id: +' + issue_needle 116 | issue_haystack = out 117 | 118 | for p in projects: 119 | # match from git commit msg. like . 120 | matches = re.findall(issue_needle % p, issue_haystack) 121 | #logging.debug("[update_ticket] RE Matched JIRA Issue Key :" + matches) 122 | for m in matches: 123 | issues.append(m) 124 | 125 | if (len(issues) > 0): 126 | message = make_comment_message(what, id, hash, url, who, branch, subject, gerrit_prj) 127 | logging.debug("[update_ticket] Comment MSG Preview:\n" + message) 128 | for i in issues: 129 | logging.info('[update_ticket] Updating issue: ' + i) 130 | jira.add_comment(i, message) 131 | if len(custom_field) > 0: 132 | issue = jira.issue(i) 133 | issue.update(fields={'customfield_' + custom_field: [status]}) 134 | updateStatus = commands.getstatusoutput(issue.update(fields={'customfield_' + custom_field: [status]})) 135 | logging.debug('[update_ticket] [Custom_field] Updating status : ' + updateStatus) 136 | 137 | def run_test(): 138 | """unit test, for message generate""" 139 | print 'running test' 140 | what = 'new' 141 | commit_id = 'Ieddc9185f6ec5ec2f454a65f1cc4620659e8a5f7' 142 | hash_id = 'Ieddc9185f6ec5ec2f454a65f1cc4620659e8a5f7' 143 | subject = 'refs HELLO-1' 144 | gerrit_prj = 'hello-world' 145 | message = make_comment_message(what, commit_id, hash_id, 'http://10.13.0.134:8083/81', 'san.zhang (san.zhang@qq.com)', 'master', subject, gerrit_prj) 146 | print message 147 | 148 | def main(): 149 | if (len(sys.argv) < 2): 150 | show_usage() 151 | exit() 152 | 153 | """ 154 | if (sys.argv[1] == 'update-projects'): 155 | #updateProjects() 156 | exit() 157 | """ 158 | if (sys.argv[1] == 'test'): 159 | run_test() 160 | exit() 161 | 162 | 163 | need = ['action=', 'change=', 'change-url=', 'commit=', 'project=', 'branch=', 'uploader=', 164 | 'patchset=', 'abandoner=', 'reason=', 'submitter=', 'kind=', 'is-draft=', 'change-owner=','newrev=','topic='] 165 | optlist, args = getopt.getopt(sys.argv[1:], '', need) 166 | id = url = hash = who = topic ='' 167 | 168 | logging.info("------------------------------Starting New Comment Operation------------------------------") 169 | for o, a in optlist: 170 | if o == '--change': 171 | id = a 172 | logging.debug("[DEBUG] Optlist Change-ID :" + id) 173 | elif o == '--change-url': 174 | url = a 175 | logging.debug("[DEBUG] Optlist Change-URL :" + url) 176 | elif o == '--commit': 177 | hash = a 178 | logging.debug("[DEBUG] Optlist Commit-ID(Hash) :" + hash) 179 | elif o == '--action': 180 | what = a 181 | logging.debug("[DEBUG] Optlist action :" + what) 182 | elif o == '--uploader': 183 | who = a 184 | logging.debug("[DEBUG] Optlist uploader(who) :" + who) 185 | elif o == '--submitter': 186 | who = a 187 | logging.debug("[DEBUG] Optlist submitter(who) :" + who) 188 | elif o == '--abandoner': 189 | who = a 190 | logging.debug("[DEBUG] Optlist abandoner(who) :" + who) 191 | elif o == '--branch': 192 | branch = a 193 | logging.debug("[DEBUG] Optlist branch :" + branch) 194 | elif o == '--topic': 195 | topic = a 196 | logging.debug("[DEBUG] Optlist topic :" + topic) 197 | 198 | if (len(what) > 0 and len(id) > 0): 199 | update_ticket(what, id, hash, url, who, branch, topic) 200 | logging.info("------------------------------Ending New Comment Operation------------------------------") 201 | else: 202 | show_usage() 203 | 204 | if __name__ == '__main__': 205 | try: 206 | main() 207 | except Exception, e: 208 | logging.error(e) 209 | --------------------------------------------------------------------------------