diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d8a9b7337..bda4e0625 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,9 +60,19 @@ to the issue with a link to your change on Gerrit and change the "wip" label to a "ready for review" label to indicate to the community that you are seeking reviews. -In your commit message, be sure to include a bracketed tag for the issue you -are addressing from GitHub Issues. For example, if you are addressing the issue -\#17, you would start your commit message with `[#17] `. +In your commit message, be sure to include a reference for the issue you +are addressing from GitHub Issues. There are three ways of doing this: + +1. Add a statement in your commit message in the format of `Relates-To: #X`. +This will add a link on issue "#X" to your change. +2. Add a statement in your commit message in the format of `Closes: #X`. +This will add a link on issue "#X" to your change and will close the issue when +your change merges. +3. Add a bracketed tag at the beginning of your commit message in the format of +`[#X] `. This will add a link on issue "#X" to your +change. This method is considered a fallback in lieu of the other two methods. + +Any issue references should be evaluated within 15 minutes of being uploaded. ## Reviewing Changes diff --git a/playbooks/airship-airshipctl-update-github-issues.yaml b/playbooks/airship-airshipctl-update-github-issues.yaml new file mode 100644 index 000000000..6fe7bdcc8 --- /dev/null +++ b/playbooks/airship-airshipctl-update-github-issues.yaml @@ -0,0 +1,27 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- hosts: primary + tasks: + - name: Install script dependencies + pip: + name: + - PyGithub==1.46 + executable: pip3 + - name: Run python script + script: > + zuul.d/scripts/update_github_issues.py "{{ github_credentials.username }}" \ + "{{ github_credentials.password }}" \ + "{{ zuul.message }}" \ + "{{ zuul.items[0].change_url }}" + args: + executable: python3 diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml index a0a5f2249..299f7a46c 100644 --- a/zuul.d/jobs.yaml +++ b/zuul.d/jobs.yaml @@ -24,6 +24,15 @@ run: playbooks/airship-airshipctl-lint-unit.yaml nodeset: airship-airshipctl-single-node +- job: + name: airship-airshipctl-update-github-issues + description: Updates and/or closes related issues on Github on merge + run: playbooks/airship-airshipctl-update-github-issues.yaml + nodeset: airship-airshipctl-single-node + secrets: + - name: github_credentials + secret: airship-airshipctl-airshipit-github-username-password + - job: name: airship-airshipctl-build-image nodeset: airship-airshipctl-single-node diff --git a/zuul.d/projects.yaml b/zuul.d/projects.yaml index 07e79d125..e3a14c814 100644 --- a/zuul.d/projects.yaml +++ b/zuul.d/projects.yaml @@ -30,3 +30,4 @@ - airship-airshipctl-publish-docs - airship-airshipctl-publish-image - airship-airshipctl-upload-git-mirror + - airship-airshipctl-update-github-issues diff --git a/zuul.d/scripts/update_github_issues.py b/zuul.d/scripts/update_github_issues.py new file mode 100755 index 000000000..1d2c36c80 --- /dev/null +++ b/zuul.d/scripts/update_github_issues.py @@ -0,0 +1,83 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +import re +import sys + +import github + +GH_USER = sys.argv[1] +GH_PW = sys.argv[2] +ZUUL_MESSAGE = sys.argv[3] +GERRIT_URL = sys.argv[4] +REPO_NAME = 'airshipit/airshipctl' +PROCESS_LABELS = ['wip', 'ready for review', 'triage', 'blocked'] + + +def construct_issue_list(match_list: list) -> set: + new_list = [] + for _issue in match_list: + try: + new_list.append(int(_issue)) + except ValueError: + logging.warning(f'Value {_issue} could not be converted to `int` type') + return set(new_list) + + +def parse_issue_number(commit_msg: str) -> dict: + # Searches for Relates-To or Closes tags first to match and return + logging.debug(f'Parsing commit message: {commit_msg}') + related = re.findall(r'(?<=Relates-To: #)([0-9]+?)(?=\n)', commit_msg) + logging.debug(f'Captured related issues: {related}') + closes = re.findall(r'(?<=Closes: #)([0-9]+?)(?=\n)', commit_msg) + logging.debug(f'Captured closes issues: {closes}') + if related or closes: + return { + 'related': construct_issue_list(related), + 'closes': construct_issue_list(closes) + } + # If no Relates-To or Closes tags are defined, find legacy [#X] style tags + logging.debug('Falling back to legacy tags') + legacy_matches = re.findall(r'(?<=\[#)([0-9]+?)(?=\])', commit_msg) + logging.debug(f'Captured legacy issues: {legacy_matches}') + if not legacy_matches: + return {} + return { + 'related': construct_issue_list(legacy_matches) + } + + +def remove_duplicated_issue_numbers(issue_dict: dict) -> dict: + if 'closes' in issue_dict: + issue_dict['related'] = [x for x in issue_dict.get('related', []) if x not in issue_dict['closes']] + return issue_dict + + +if __name__ == '__main__': + issue_number_dict = parse_issue_number(ZUUL_MESSAGE) + issue_number_dict = remove_duplicated_issue_numbers(issue_number_dict) + gh = github.Github(GH_USER, GH_PW) + repo = gh.get_repo(REPO_NAME) + for key, issue_list in issue_number_dict.items(): + for issue_number in issue_list: + issue = repo.get_issue(number=issue_number) + comment_msg = '' + link_exists = False + if key == 'closes': + issue.create_comment(f'The [Change]({GERRIT_URL}) that closes this issue was merged.') + for label in PROCESS_LABELS: + try: + issue.remove_from_labels(label) + except github.GithubException: + pass + else: + issue.create_comment(f'A [Related Change]({GERRIT_URL} was merged. This issue may be ready to close.') diff --git a/zuul.d/secrets.yaml b/zuul.d/secrets.yaml index a86666db8..6e6ac5754 100644 --- a/zuul.d/secrets.yaml +++ b/zuul.d/secrets.yaml @@ -58,6 +58,22 @@ uPzQ1Ero9vGyHmFuvFczZK+TnJBxyKiyWr4kR9GbI3WVXMSa78M+kRqQmcnqdwS9AAFC2 595F3m2UtgR6QKN5xbJJmWp7ipihdRvQ1eVl5GCjG7MBi27YvILp4cIWg+MO1I= +- secret: + name: airship-airshipctl-airshipit-github-username-password + data: + username: airshipbot + password: !encrypted/pkcs1-oaep + - AbUjw8rBEhFCVxOE/9dpdR4g+viQfC2/W0uewRWOU4duEZL5LXzCSZBLKaMFE1Oi38lut + PFyfPS2WqABWDgdYAHmYdgnxDqF/TXHPObQxqGwIJ9wxG3siplMXv6T6I0iz6/U7MfX7q + 5D/jy9RWd+px7fvohbjG5rwL3wfZZsmn6KMrEzYbScHH7w8qYL6vnQr5Z0YRXs9h79VJ7 + 01XrtdipuFWYWILmwJzV25YZ2AavNFnk0p2a9zfDxYty+bFpkqKWrd92mgpsGIbckFS24 + Wdhn/2gIfPv9/clBzUgTeK6v7TR1MpQDIQ6NWeF8YpB9Rm7fdmDbTQKXOwX0ung2L0+rk + GO8j9xzVNV+CsGgIKyOPEeOiloxEmDD7ie2CSp8pWnlb6nP6g+R2koOz0Xs/uvPO4ACS/ + vARWor3q6zOKj7maW0Z2jZR+i/yGu2Pf+sJZt2HfvLMvYX2avP+9mtoyZY30cHBMSahZ+ + pqpk7Oq6RVA63PPFHGAEhyD6c6tas0bC2/KHjjgLvcgxuzRVHcKJUTDW8Usmm2QTBijHn + mTMWfj2ffriZ4+ZtPmGDzGoAhoUe6oU8DI86opjGmDFcNk+2T8WEgJ7guvG57GzKXFnTd + zY8AzTMSMOKEzSaiiT2lvzBMFhSNRIBTM+m8I+DDBooy0nM4ijcHSP7MdOhGbs= + - secret: name: airshipctl-image-repo-credentials data: