# HG changeset patch # User Peter Sanchez # Date 1635295643 25200 # Tue Oct 26 17:47:23 2021 -0700 # Branch redmine # Node ID 00e23ef5166bed1fb0515c143a158469f8327ce8 # Parent 23ffcf1308bd56c9db903f3f6213c73bea4d2693 Adding working Redmine integration diff --git a/README.rst b/README.rst --- a/README.rst +++ b/README.rst @@ -5,6 +5,13 @@ :Version: 0.3 :Author: Peter Sanchez - Netlandish Inc. (http://www.netlandish.com) +Attention +========= + +DO NOT USE THIS BRANCH! This project is essentially abandoned as we no longer use Trello. + +This branch (redmine) in particular is just a band-aid until we create a more universal solution that will plan to share in the future. This branch is literally just a hack to get our current setup working and integrated with our Redmine install. + Dependencies ============ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,29 @@ +[tool.black] +line-length = 79 +target-version = ['py37', 'py38'] +include = '\.pyi?$' +exclude = ''' + +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | env + | _build + | buck-out + | build + | dist + )/ + | foo.py # also separately exclude a file named foo.py in + # the root of the project +) +''' + +[tool.isort] +line_length=79 +multi_line_output=5 +include_trailing_comma=true diff --git a/trello_broker/settings.py b/trello_broker/settings.py --- a/trello_broker/settings.py +++ b/trello_broker/settings.py @@ -1,60 +1,61 @@ from django.conf import settings - # Hard coded for now to avoid external requirement. Will change # if the list becomes unmanageable or once we're all moved over to Py3 :) # https://confluence.atlassian.com/bitbucket/what-are-the-bitbucket-cloud-ip-addresses-i-should-use-to-configure-my-corporate-firewall-343343385.html BITBUCKET_IP_ADDRESSES = [ - '131.103.20.165', # Remove after 1/10/2016 - '131.103.20.166', # Remove after 1/10/2016 - - '104.192.143.193', # 104.192.143.192/28 - '104.192.143.194', - '104.192.143.195', - '104.192.143.196', - '104.192.143.197', - '104.192.143.198', - '104.192.143.199', - '104.192.143.200', - '104.192.143.201', - '104.192.143.202', - '104.192.143.203', - '104.192.143.204', - '104.192.143.205', - '104.192.143.206', - - '104.192.143.209', # 104.192.143.208/28 - '104.192.143.210', - '104.192.143.211', - '104.192.143.212', - '104.192.143.213', - '104.192.143.214', - '104.192.143.215', - '104.192.143.216', - '104.192.143.217', - '104.192.143.218', - '104.192.143.219', - '104.192.143.220', - '104.192.143.221', - '104.192.143.222', + "131.103.20.165", # Remove after 1/10/2016 + "131.103.20.166", # Remove after 1/10/2016 + "104.192.143.193", # 104.192.143.192/28 + "104.192.143.194", + "104.192.143.195", + "104.192.143.196", + "104.192.143.197", + "104.192.143.198", + "104.192.143.199", + "104.192.143.200", + "104.192.143.201", + "104.192.143.202", + "104.192.143.203", + "104.192.143.204", + "104.192.143.205", + "104.192.143.206", + "104.192.143.209", # 104.192.143.208/28 + "104.192.143.210", + "104.192.143.211", + "104.192.143.212", + "104.192.143.213", + "104.192.143.214", + "104.192.143.215", + "104.192.143.216", + "104.192.143.217", + "104.192.143.218", + "104.192.143.219", + "104.192.143.220", + "104.192.143.221", + "104.192.143.222", ] -USE_CELERY = getattr(settings, 'TRELLO_BROKER_USE_CELERY', False) +USE_CELERY = getattr(settings, "TRELLO_BROKER_USE_CELERY", False) -RESTRICT_IPS = getattr(settings, 'TRELLO_BROKER_RESTRICT_IPS', False) +RESTRICT_IPS = getattr(settings, "TRELLO_BROKER_RESTRICT_IPS", False) # https://confluence.atlassian.com/display/BITBUCKET/What+are+the+Bitbucket+IP+addresses+I+should+use+to+configure+my+corporate+firewall # Only needed if TRELLO_BROKER_RESTRICT_IPS is True BITBUCKET_IPS = getattr( - settings, - 'TRELLO_BROKER_BITBUCKET_IPS', - BITBUCKET_IP_ADDRESSES, + settings, "TRELLO_BROKER_BITBUCKET_IPS", BITBUCKET_IP_ADDRESSES, ) -RULE_CLASSES = getattr(settings, - 'TRELLO_BROKER_RULE_CLASSES', - ['trello_broker.rules.DefaultRuleProc'], +RULE_CLASSES = getattr( + settings, + "TRELLO_BROKER_RULE_CLASSES", + ["trello_broker.rules.DefaultRuleProc"], ) + +REDMINE_API_KEY = getattr(settings, "TRELLO_BROKER_REDMINE_API_KEY", None) + +# No trailing slash! +REDMINE_ROOT_URL = getattr(settings, "TRELLO_BROKER_REDMINE_ROOT_URL", None) diff --git a/trello_broker/utils.py b/trello_broker/utils.py --- a/trello_broker/utils.py +++ b/trello_broker/utils.py @@ -1,14 +1,20 @@ +import json import re + +import requests +from django.template.loader import render_to_string + +from . import settings + try: from urlparse import urljoin except ImportError: from urllib.parse import urljoin -from django.template.loader import render_to_string -from . import api # Based on pattern from https://bitbucket.org/sntran/trello-broker -re_pattern = re.compile(r''' +re_pattern = re.compile( + r""" ( # start capturing the verb fix # contains 'fix' | close # or 'close' @@ -19,79 +25,65 @@ \s # then a white space [#] # and '#' to indicate the card ([0-9]+) # with the card's short id. - ''', + """, re.VERBOSE | re.IGNORECASE, ) def process_commits(repo, json_data): - ''' Function to parse commit messages and act on + """ Function to parse commit messages and act on BitBucketRule sets. - ''' - base_repo_url = u'{0}{1}'.format( - json_data['canon_url'], - json_data['repository']['absolute_url'], + """ + base_repo_url = "{0}{1}".format( + json_data["canon_url"], json_data["repository"]["absolute_url"], ) - base_repo_url += '/' if base_repo_url[-1] != '/' else '' - base_commit_url = urljoin(base_repo_url, 'commits/') - trello_token = repo.trello_board.trello_token + base_repo_url += "/" if base_repo_url[-1] != "/" else "" + base_commit_url = urljoin(base_repo_url, "commits/") - for commit in json_data['commits']: + for commit in json_data["commits"]: + print("in commit") proc_cards = [] - msg = commit['message'] + msg = commit["message"] context = { - 'author': commit['author'], - 'author_full': commit['raw_author'], - 'changeset': commit['node'], - 'changeset_full': commit['raw_node'], - 'commit_url': commit.get( - 'commit_url', - urljoin(base_commit_url, commit['raw_node']), + "author": commit["author"], + "author_full": commit["raw_author"], + "changeset": commit["node"], + "changeset_full": commit["raw_node"], + "commit_url": commit.get( + "commit_url", urljoin(base_commit_url, commit["raw_node"]), ), - 'commit_message': msg, + "commit_message": msg, } for action, card_id in re_pattern.findall(msg): + print(f"in {action} -> {card_id}") if card_id in proc_cards: # Referenced more than once in the commit msg continue proc_cards.append(card_id) - use_rule = 'fix_rule' \ - if action.lower() in ['fix', 'close'] else 'ref_rule' - rule = getattr(repo, use_rule) - if not rule: - continue - # Get Card information - card = api.get_card_from_board( - trello_token, - repo.trello_board.trello_id, - card_id, + # Update the card. + card_msg = render_to_string( + "trello_broker/update_message.txt", context ) - full_card_id = card['id'] + update = {"issue": {"notes": card_msg,}} - # Let's process the rule - if rule.update: - # Update the card. - card_msg = render_to_string( - 'trello_broker/update_message.txt', - context, - ) - api.add_comment_to_card( - trello_token, - full_card_id, - card_msg, - ) + if action.lower() == "close": + # FYI, this is hard coded for our own use case. + # DO NOT USE! + print("updating status") + update["issue"]["status_id"] = 15 + print(f"Final result: {update}") - if rule.move: - # Move the card - api.move_card( - trello_token, - full_card_id, - rule.trello_list.trello_id, - ) - - if rule.archive: - # Archive the card - api.archive_card(trello_token, full_card_id) + result = requests.put( + f"{settings.REDMINE_ROOT_URL}/issues/{card_id}.json", + data=json.dumps(update), + headers={ + "X-Redmine-API-Key": settings.REDMINE_API_KEY, + "Content-Type": "application/json", + }, + ) + if result.status_code >= 400: + # XXX Log something + pass diff --git a/trello_broker/views.py b/trello_broker/views.py --- a/trello_broker/views.py +++ b/trello_broker/views.py @@ -1,12 +1,14 @@ import json -from django.views.generic import View + +from django.http import HttpResponse, HttpResponseBadRequest from django.shortcuts import get_object_or_404 -from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator -from django.http import HttpResponse, HttpResponseBadRequest, Http404 -from .models import Q, BitBucketRepo +from django.views.decorators.csrf import csrf_exempt +from django.views.generic import View + +from . import settings +from .models import BitBucketRepo, Q from .utils import process_commits -from . import settings if settings.USE_CELERY: from .tasks import celery_process_commits @@ -15,9 +17,9 @@ def trello_distribute(repo, json_data): - ''' Helper function that decides how to process + """ Helper function that decides how to process Trello interaction (celery or not) - ''' + """ if celery_process_commits is not None: celery_process_commits.delay(repo.pk, json_data) else: @@ -29,22 +31,29 @@ return HttpResponseBadRequest() def post(self, request, *args, **kwargs): + if settings.REDMINE_API_KEY is None: + return HttpResponseBadRequest("Redmine API key required.") + + if settings.REDMINE_ROOT_URL is None: + return HttpResponseBadRequest("Redmine API key required.") + if settings.RESTRICT_IPS: - if request.META['REMOTE_ADDR'] not in settings.BITBUCKET_IPS: + if request.META["REMOTE_ADDR"] not in settings.BITBUCKET_IPS: return HttpResponseBadRequest() try: - json_data = json.loads(request.POST['payload']) - repo_slug = json_data['repository']['slug'] - except (ValueError, KeyError) as e: + json_data = json.loads(request.POST["payload"]) + repo_slug = json_data["repository"]["slug"] + except (ValueError, KeyError): return HttpResponseBadRequest() - access_key = \ - request.POST.get('access_key', request.GET.get('access_key', '')) + access_key = request.POST.get( + "access_key", request.GET.get("access_key", "") + ) query = Q(slug=repo_slug) & Q(access_key=access_key) repo = get_object_or_404(BitBucketRepo, query) trello_distribute(repo, json_data) - return HttpResponse('OK') + return HttpResponse("OK") @method_decorator(csrf_exempt) def dispatch(self, *args, **kwargs):