337 строки
9.8 KiB
Python
337 строки
9.8 KiB
Python
import argparse
|
|
import ghlib
|
|
import jiralib
|
|
import os
|
|
import sys
|
|
import json
|
|
import util
|
|
from sync import Sync, DIRECTION_G2J, DIRECTION_J2G, DIRECTION_BOTH
|
|
import logging
|
|
import server
|
|
|
|
root = logging.getLogger()
|
|
root.setLevel(logging.DEBUG)
|
|
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
handler.setLevel(logging.DEBUG)
|
|
root.addHandler(handler)
|
|
|
|
|
|
def fail(msg):
|
|
print(msg)
|
|
sys.exit(1)
|
|
|
|
|
|
def direction_str_to_num(dstr):
|
|
if dstr == 'gh2jira':
|
|
return DIRECTION_G2J
|
|
elif dstr == 'jira2gh':
|
|
return DIRECTION_J2G
|
|
elif dstr == 'both':
|
|
return DIRECTION_BOTH
|
|
else:
|
|
fail('Unknown direction argument "{direction}"!'.format(direction=dstr))
|
|
|
|
|
|
def serve(args):
|
|
if not args.gh_url or not args.jira_url:
|
|
fail('Both GitHub and JIRA URL have to be specified!')
|
|
|
|
if not args.gh_token:
|
|
fail('No GitHub token specified!')
|
|
|
|
if not args.jira_user or not args.jira_token:
|
|
fail('No JIRA credentials specified!')
|
|
|
|
if not args.jira_project:
|
|
fail('No JIRA project specified!')
|
|
|
|
if not args.secret:
|
|
fail('No Webhook secret specified!')
|
|
|
|
github = ghlib.GitHub(args.gh_url, args.gh_token)
|
|
jira = jiralib.Jira(args.jira_url, args.jira_user, args.jira_token)
|
|
s = Sync(
|
|
github,
|
|
jira.getProject(args.jira_project),
|
|
direction=direction_str_to_num(args.direction)
|
|
)
|
|
server.run_server(s, args.secret, port=args.port)
|
|
|
|
|
|
def sync(args):
|
|
if not args.gh_url or not args.jira_url:
|
|
fail('Both GitHub and JIRA URL have to be specified!')
|
|
|
|
if not args.gh_token:
|
|
fail('No GitHub credentials specified!')
|
|
|
|
if not args.jira_user or not args.jira_token:
|
|
fail('No JIRA credentials specified!')
|
|
|
|
if not args.jira_project:
|
|
fail('No JIRA project specified!')
|
|
|
|
if not args.gh_org:
|
|
fail('No GitHub organization specified!')
|
|
|
|
if not args.gh_repo:
|
|
fail('No GitHub repository specified!')
|
|
|
|
github = ghlib.GitHub(args.gh_url, args.gh_token)
|
|
jira = jiralib.Jira(args.jira_url, args.jira_user, args.jira_token)
|
|
jira_project = jira.getProject(args.jira_project, args.issue_end_state, args.issue_reopen_state)
|
|
repo_id = args.gh_org + '/' + args.gh_repo
|
|
|
|
if args.state_file:
|
|
if args.state_issue:
|
|
fail('--state-file and --state-issue are mutually exclusive!')
|
|
|
|
state = util.state_from_file(args.state_file)
|
|
elif args.state_issue:
|
|
state = jira_project.fetch_repo_state(repo_id, args.state_issue)
|
|
else:
|
|
state = {}
|
|
|
|
s = Sync(
|
|
github,
|
|
jira_project,
|
|
direction=direction_str_to_num(args.direction)
|
|
)
|
|
s.sync_repo(repo_id, states=state)
|
|
|
|
if args.state_file:
|
|
util.state_to_file(args.state_file, state)
|
|
elif args.state_issue:
|
|
jira_project.save_repo_state(repo_id, state, args.state_issue)
|
|
|
|
|
|
def check_hooks(args):
|
|
pass
|
|
|
|
|
|
def install_hooks(args):
|
|
if not args.hook_url:
|
|
fail('No hook URL specified!')
|
|
|
|
if not args.secret:
|
|
fail('No hook secret specified!')
|
|
|
|
if not args.gh_url and not args.jira_url:
|
|
fail('Neither GitHub nor JIRA URL specified!')
|
|
|
|
# user wants to install a github hook
|
|
if args.gh_url:
|
|
if not args.gh_token:
|
|
fail('No GitHub token specified!')
|
|
|
|
if not args.gh_org:
|
|
fail('No GitHub organization specified!')
|
|
|
|
github = ghlib.GitHub(args.gh_url, args.gh_token)
|
|
|
|
if args.gh_repo:
|
|
ghrepo = github.getRepository(args.gh_org + '/' + args.gh_repo)
|
|
ghrepo.create_hook(url=args.hook_url, secret=args.secret)
|
|
else:
|
|
github.create_org_hook(url=args.hook_url, secret=args.secret)
|
|
|
|
# user wants to install a JIRA hook
|
|
if args.jira_url:
|
|
if not args.jira_user or not args.jira_token:
|
|
fail('No JIRA credentials specified!')
|
|
jira = jiralib.Jira(args.jira_url, args.jira_user, args.jira_token)
|
|
jira.create_hook('github_jira_synchronization_hook', args.hook_url, args.secret)
|
|
|
|
|
|
def list_hooks(args):
|
|
if not args.gh_url and not args.jira_url:
|
|
fail('Neither GitHub nor JIRA URL specified!')
|
|
|
|
# user wants to list github hooks
|
|
if args.gh_url:
|
|
if not args.gh_token:
|
|
fail('No GitHub token specified!')
|
|
|
|
if not args.gh_org:
|
|
fail('No GitHub organization specified!')
|
|
|
|
github = ghlib.GitHub(args.gh_url, args.gh_token)
|
|
|
|
if args.gh_repo:
|
|
for h in github.getRepository(args.gh_org + '/' + args.gh_repo).list_hooks():
|
|
print(json.dumps(h, indent=4))
|
|
else:
|
|
for h in github.list_org_hooks(args.gh_org):
|
|
print(json.dumps(h, indent=4))
|
|
|
|
# user wants to list JIRA hooks
|
|
if args.jira_url:
|
|
if not args.jira_user or not args.jira_token:
|
|
fail('No JIRA credentials specified!')
|
|
|
|
jira = jiralib.Jira(args.jira_url, args.jira_user, args.jira_token)
|
|
|
|
for h in jira.list_hooks():
|
|
print(json.dumps(h, indent=4))
|
|
|
|
|
|
def main():
|
|
credential_base = argparse.ArgumentParser(add_help=False)
|
|
credential_base.add_argument(
|
|
'--gh-org',
|
|
help='GitHub organization'
|
|
)
|
|
credential_base.add_argument(
|
|
'--gh-repo',
|
|
help='GitHub repository'
|
|
)
|
|
credential_base.add_argument(
|
|
'--gh-url',
|
|
help='API URL of GitHub instance',
|
|
)
|
|
credential_base.add_argument(
|
|
'--gh-token',
|
|
help='GitHub API token. Alternatively, the GH2JIRA_GH_TOKEN may be set.',
|
|
default=os.getenv('GH2JIRA_GH_TOKEN')
|
|
)
|
|
credential_base.add_argument(
|
|
'--jira-url',
|
|
help='URL of JIRA instance'
|
|
)
|
|
credential_base.add_argument(
|
|
'--jira-user',
|
|
help='JIRA user name'
|
|
)
|
|
credential_base.add_argument(
|
|
'--jira-token',
|
|
help='JIRA password. Alternatively, the GH2JIRA_JIRA_TOKEN may be set.',
|
|
default=os.getenv('GH2JIRA_JIRA_TOKEN')
|
|
)
|
|
credential_base.add_argument(
|
|
'--jira-project',
|
|
help='JIRA project key'
|
|
)
|
|
credential_base.add_argument(
|
|
'--secret',
|
|
help='Webhook secret. Alternatively, the GH2JIRA_SECRET may be set.',
|
|
default=os.getenv('GH2JIRA_SECRET')
|
|
)
|
|
|
|
direction_base = argparse.ArgumentParser(add_help=False)
|
|
direction_base.add_argument(
|
|
'--direction',
|
|
help='Sync direction. Possible values are "gh2jira" (alert states have higher priority than issue states),'
|
|
+ '"jira2gh" (issue states have higher priority than alert states) and "both" (adjust in both directions)',
|
|
default='both'
|
|
)
|
|
|
|
issue_state_base = argparse.ArgumentParser(add_help=False)
|
|
issue_state_base.add_argument(
|
|
'--issue-end-state',
|
|
help='Custom end state (e.g. Closed) Done by default',
|
|
default='Done'
|
|
)
|
|
issue_state_base.add_argument(
|
|
'--issue-reopen-state',
|
|
help='Custom reopen state (e.g. In Progress) To Do by default',
|
|
default='To Do'
|
|
)
|
|
|
|
parser = argparse.ArgumentParser(prog='gh2jira')
|
|
subparsers = parser.add_subparsers()
|
|
|
|
# serve
|
|
serve_parser = subparsers.add_parser(
|
|
'serve',
|
|
parents=[credential_base, direction_base, issue_state_base],
|
|
help='Spawn a webserver which keeps GitHub alerts and JIRA tickets in sync',
|
|
description='Spawn a webserver which keeps GitHub alerts and JIRA tickets in sync'
|
|
)
|
|
serve_parser.add_argument(
|
|
'--port',
|
|
help='The port the server will listen on',
|
|
default=5000
|
|
)
|
|
serve_parser.set_defaults(func=serve)
|
|
|
|
# sync
|
|
sync_parser = subparsers.add_parser(
|
|
'sync',
|
|
parents=[credential_base, direction_base, issue_state_base],
|
|
help='Synchronize GitHub alerts and JIRA tickets for a given repository',
|
|
description='Synchronize GitHub alerts and JIRA tickets for a given repository'
|
|
)
|
|
sync_parser.add_argument(
|
|
'--state-file',
|
|
help='File holding the current states of all alerts. The program will create the' +
|
|
' file if it doesn\'t exist and update it after each run.',
|
|
default=None
|
|
)
|
|
sync_parser.add_argument(
|
|
'--state-issue',
|
|
help='The key of the issue holding the current states of all alerts. The program ' +
|
|
'will create the issue if "-" is given as the argument. The issue will be ' +
|
|
'updated after each run.',
|
|
default=None
|
|
)
|
|
sync_parser.set_defaults(func=sync)
|
|
|
|
# hooks
|
|
hooks = subparsers.add_parser(
|
|
'hooks',
|
|
help='Manage JIRA and GitHub webhooks',
|
|
description='Manage JIRA and GitHub webhooks'
|
|
)
|
|
|
|
|
|
hooks_subparsers = hooks.add_subparsers()
|
|
|
|
# list hooks
|
|
hooks_list = hooks_subparsers.add_parser(
|
|
'list',
|
|
parents=[credential_base],
|
|
help='List existing GitHub or JIRA webhooks',
|
|
description='List existing GitHub or JIRA webhooks'
|
|
)
|
|
hooks_list.set_defaults(func=list_hooks)
|
|
|
|
# install hooks
|
|
hooks_install = hooks_subparsers.add_parser(
|
|
'install',
|
|
parents=[credential_base],
|
|
help='Install existing GitHub or JIRA webhooks',
|
|
description='Install GitHub or JIRA webhooks'
|
|
)
|
|
hooks_install.add_argument(
|
|
'--hook-url',
|
|
help='Webhook target url'
|
|
)
|
|
hooks_install.add_argument(
|
|
'--insecure-ssl',
|
|
action='store_true',
|
|
help='Install GitHub hook without SSL check'
|
|
)
|
|
hooks_install.set_defaults(func=install_hooks)
|
|
|
|
# check hooks
|
|
hooks_check = hooks_subparsers.add_parser(
|
|
'check',
|
|
parents=[credential_base],
|
|
help='Check that hooks are installed properly',
|
|
description='Check that hooks are installed properly'
|
|
)
|
|
hooks_check.set_defaults(func=check_hooks)
|
|
|
|
|
|
def print_usage(args):
|
|
print(parser.format_usage())
|
|
|
|
parser.set_defaults(func=print_usage)
|
|
args = parser.parse_args()
|
|
|
|
# run the given action
|
|
args.func(args)
|
|
|
|
main()
|