From 926a9e4e8cc1b5a9df2c93c7ddde3051634c90b2 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 1 Apr 2022 12:49:45 +0000 Subject: [PATCH] Convert process_func_test_pr.py to a module Convert process_func_test_pr.py to a module and rename it to process_commit_msg as it may end up doing more in the future. --- .../process_commit_msg.py} | 115 +++++++++++++++--- roles/handle-func-test-pr/tasks/main.yaml | 34 ++---- tox.ini | 1 + unit_tests/__init__.py | 2 +- ..._test_pr.py => test_process_commit_msg.py} | 22 ++-- 5 files changed, 113 insertions(+), 61 deletions(-) rename roles/handle-func-test-pr/{files/process_func_test_pr.py => library/process_commit_msg.py} (57%) rename unit_tests/{test_process_func_test_pr.py => test_process_commit_msg.py} (87%) diff --git a/roles/handle-func-test-pr/files/process_func_test_pr.py b/roles/handle-func-test-pr/library/process_commit_msg.py similarity index 57% rename from roles/handle-func-test-pr/files/process_func_test_pr.py rename to roles/handle-func-test-pr/library/process_commit_msg.py index dc6d442..131ce98 100755 --- a/roles/handle-func-test-pr/files/process_func_test_pr.py +++ b/roles/handle-func-test-pr/library/process_commit_msg.py @@ -1,29 +1,42 @@ #!/usr/bin/env python3 -import argparse import base64 import os +import hashlib +import pathlib import re import requests -import sys from urllib.parse import urlparse +from ansible.module_utils.basic import AnsibleModule -def parse_args(args): - """Parse command line arguments. +__metaclass__ = type - :param args: Command arguments - :type list: [str1, str2,...] List of command line arguments - :returns: Parsed arguments - :rtype: Namespace - """ - parser = argparse.ArgumentParser() - parser.add_argument('-f', '--file', action='append', - help='File to update', - required=True) - parser.add_argument('commit_message', type=str, - help='Commit message to process') - return parser.parse_args(args) +DOCUMENTATION = r''' +--- +module: process_commit_msg + +short_description: Read commit message and perform any actions +''' + +EXAMPLES = r''' +# Update requrements files based on commit message +- name: Process commit message + process_commit_msg: + commit_message: "{{ zm.content }}" + files: + - /home/ubuntu/charm-mycharm/test-requirements.txt + - /home/ubuntu/charm-mycharm/src/test-requirements.txt +''' + +RETURN = r''' +# Example of possible return value +message: + description: The output message that the test module generates. + type: str + returned: always + sample: 'Updated Files: /home/ubuntu/charm-mycharm/test-requirements.txt' +''' def extract_lines(commit_message, match_pattern): @@ -117,10 +130,74 @@ def process_commit(encoded_commit_message, files): func(commit_message, files) +def get_file_hash(file_loc): + """Calculate a hash for the file contents. + + :param files: File path + :type files: pathlib.Path + :returns: File hash + :rtype: str + """ + return hashlib.md5(file_loc.read_bytes()).hexdigest() + + +def get_files_hashes(files): + """Calculate a has for the contents of each file. + + :param files: List of file names. + :type files: List[str] + :returns: File hashes + :rtype: Dict[str] + """ + hashes = {} + for f in files: + file_loc = pathlib.Path(f) + if file_loc.exists(): + hashes[f] = get_file_hash(file_loc) + return hashes + + +def run_module(): + module_args = dict( + files=dict(type='list', required=True), + commit_message=dict(type='str', required=True) + ) + + result = dict( + changed=False, + message='' + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True + ) + + if module.check_mode: + module.exit_json(**result) + + old_hashes = get_files_hashes(module.params['files']) + + process_commit( + module.params['commit_message'], + module.params['files']) + + new_hashes = get_files_hashes(module.params['files']) + changed_files = [f for f, h in old_hashes.items() if h != new_hashes[f]] + + if changed_files: + result['changed'] = True + result['message'] = 'Updated Files: {}'.format(','.join(changed_files)) + else: + result['changed'] = False + result['message'] = 'No files updated' + + module.exit_json(**result) + + def main(): - args = parse_args(sys.argv[1:]) - process_commit(args.commit_message, args.file) + run_module() if __name__ == '__main__': - sys.exit(main()) + main() diff --git a/roles/handle-func-test-pr/tasks/main.yaml b/roles/handle-func-test-pr/tasks/main.yaml index 0e6a3bf..f4b5291 100644 --- a/roles/handle-func-test-pr/tasks/main.yaml +++ b/roles/handle-func-test-pr/tasks/main.yaml @@ -3,29 +3,11 @@ set_fact: charm_name: "{{ zuul.project.short_name | replace('charm-', '') }}" -- name: Create temporary directory - when: zuul.message is defined - tempfile: - state: directory - register: tmp_dir - -- name: Copy over process_func_test_pr.py - when: zuul.message is defined - copy: - src: "process_func_test_pr.py" - dest: "{{ tmp_dir.path }}/process_func_test_pr.py" - mode: '0500' - -- name: Run process_func_test_pr.py - when: zuul.message is defined - args: - executable: /bin/bash - shell: | - set -o pipefail - {{ tmp_dir.path }}/process_func_test_pr.py \ - -f "{{ zuul.project.src_dir }}/test-requirements.txt" \ - -f "{{ zuul.project.src_dir }}/src/test-requirements.txt" \ - -f "{{ zuul.project.src_dir }}/build/builds/{{ charm_name }}/test-requirements.txt" \ - {{ zuul.message }} - - +- name: Process commit message + when: zuul.message is defined + process_commit_msg: + commit_message: "{{ zuul.message }}" + files: + - "{{ zuul.project.src_dir }}/test-requirements.txt" + - "{{ zuul.project.src_dir }}/src/test-requirements.txt" + - "{{ zuul.project.src_dir }}/build/builds/{{ charm_name }}/test-requirements.txt" diff --git a/tox.ini b/tox.ini index f83579b..4d33838 100644 --- a/tox.ini +++ b/tox.ini @@ -71,5 +71,6 @@ deps = os-testr>0.4.1 mock requests + ansible basepython = python3 commands = ostestr {posargs} diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py index 74929e5..931f87f 100644 --- a/unit_tests/__init__.py +++ b/unit_tests/__init__.py @@ -14,4 +14,4 @@ import sys -sys.path.append('roles/handle-func-test-pr/files/') +sys.path.append('roles/handle-func-test-pr/library') diff --git a/unit_tests/test_process_func_test_pr.py b/unit_tests/test_process_commit_msg.py similarity index 87% rename from unit_tests/test_process_func_test_pr.py rename to unit_tests/test_process_commit_msg.py index 50b3451..0c1ff29 100644 --- a/unit_tests/test_process_func_test_pr.py +++ b/unit_tests/test_process_commit_msg.py @@ -16,7 +16,7 @@ import tempfile import unittest -import process_func_test_pr +import process_commit_msg TEST_MESSAGE = """ Update to build using charmcraft @@ -66,23 +66,15 @@ class TestProcessFuncTestPR(unittest.TestCase): - def test_parser(self): - args = process_func_test_pr.parse_args([ - '-f', 'file1', - '-f', 'file2', - 'My Message']) - self.assertEqual(args.file, ['file1', 'file2']) - self.assertEqual(args.commit_message, 'My Message') - def test_extract_lines(self): self.assertEqual( - process_func_test_pr.extract_lines(TEST_MESSAGE, 'Im not there'), + process_commit_msg.extract_lines(TEST_MESSAGE, 'Im not there'), []) self.assertEqual( - process_func_test_pr.extract_lines(TEST_MESSAGE, 'Change-Id'), + process_commit_msg.extract_lines(TEST_MESSAGE, 'Change-Id'), ["Change-Id: Iadd11634d1fe44731ecf0a6104561b4aeebff23f"]) self.assertEqual( - process_func_test_pr.extract_lines( + process_commit_msg.extract_lines( TEST_MESSAGE, r'.*(Unit test fi|add a build).*'), ['- add a build-requirements.txt', @@ -93,7 +85,7 @@ def test_apply_updates(self): file1 = '{}/file1.txt'.format(tmpdirname) with open(file1, 'w') as f: f.write("Yo, hit me with some biscuits and gravy") - process_func_test_pr.apply_updates( + process_commit_msg.apply_updates( [ ('Im not there', 'foo'), ('Yo,', 'Good morning,'), @@ -108,7 +100,7 @@ def test_apply_updates(self): ('Good morning, please may I have a savoury scone with ' 'Béchamel sauce')) - @mock.patch.object(process_func_test_pr.requests, 'get') + @mock.patch.object(process_commit_msg.requests, 'get') def _test_process_func_test_pr(self, locations, mock_get): with tempfile.TemporaryDirectory() as tmpdirname: def fake_get(url): @@ -120,7 +112,7 @@ def fake_get(url): file1 = '{}/file1.txt'.format(tmpdirname) with open(file1, 'w') as f: f.write(locations) - process_func_test_pr.process_func_test_pr( + process_commit_msg.process_func_test_pr( TEST_MESSAGE, [file1]) with open(file1, 'r') as f: