diff --git a/.gitignore b/.gitignore index 35eb97f..79fdf95 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ inventories/* !inventories/.gitkeep site.yml vapid-private-key.pem + +library/__pycache__/ +library/*.pyc diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..86c1774 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,13 @@ +--- +image: python:3.7.6-slim-buster + +stages: +- test + +test: + stage: test + before_script: + - apt-get update && apt-get install make + - pip install ansible==2.9.6 + script: + - make test_modules_unit diff --git a/Makefile b/Makefile index 73a27b1..b0abff3 100644 --- a/Makefile +++ b/Makefile @@ -24,3 +24,6 @@ vapid_private_key: $(VAPID_PRIVATE_KEY_FILE) vapid_public_key: $(VAPID_PRIVATE_KEY_FILE) @echo -e "\n\nVapid public key:" @openssl ec -in $(VAPID_PRIVATE_KEY_FILE) -pubout -outform DER 2> /dev/null | tail -c 65 | base64 | tr '/+' '_-' | tr -d '=' | tr -d '\n' + +test_modules_unit: + @find ./library -mindepth 1 -maxdepth 1 -type f -name test_*.py | xargs python3 diff --git a/library/compare_semantic_versions.py b/library/compare_semantic_versions.py new file mode 100644 index 0000000..39a4413 --- /dev/null +++ b/library/compare_semantic_versions.py @@ -0,0 +1,162 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Daniel Anglin +# The MIT License (see LICENSE or https://mit-license.org) + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['preview'] +} + +DOCUMENTATION = ''' +--- +module: compare_semantic_versions + +short_description: Compares two semantic version values. + +description: +- This module compares two semantic versions and outputs whether the change is an upgrade, a downgrade or no change. +- The values must use valid semantic versioning in order for a successful comparison. + +version_added: "2.9.5" + +author: Daniel Anglin (@dananglin) + +options: + new_version: + description: + - The semantic version of the new software. + required: true + type: string + old_version: + description: + - The semantic version of the old software. + required: true + type: string +''' + +EXAMPLES = ''' +- name: Simple comparison between two semantic versions. + compare_semantic_versions: + old_version: "1.7.10" + new_version: "1.8.0" + register: output1 + +- debug: + msg: "{{ output1.result }}" + +- name: Semantic versions with the 'v' prefix are supported. + compare_semantic_versions: + old_version: "1.6.7" + new_version: "v3.5.9" + register: output2 + +- debug: + msg: "{{ output2.result }}" + +- name: Semantic versions without minor and/or patch versions are supported. + compare_semantic_versions: + old_version: "v1.100.3" + new_version: "v2" + register: output3 + +- debug: + msg: "{{ output3.result }}" +''' + +RETURN = ''' +result: + description: + - The result of the comparison between the two semantic versions. + - The value "upgrade" is returned if the new version is higher than the old version. + - The value "downgrade" is returned if the new version is lower than the old version. + - The value "noVersionChange" is returned if the new version equals the old version. + returned: On success + type: string + sample: upgrade +''' + +from ansible.module_utils.basic import AnsibleModule + +UPGRADE = "upgrade" +DOWNGRADE = "downgrade" +NO_VERSION_CHANGE = "noVersionChange" +MAJOR = "major" +MINOR = "minor" +PATCH = "patch" +MAX_VERSION_COMPONENTS = 3 + +def process(semVer): + if semVer.startswith('v'): + semVer = semVer[1:] + + semVerArr = semVer.split(".") + + if len(semVerArr) > MAX_VERSION_COMPONENTS: + raise ValueError("invalid number of version components in semantic version '{}'.".format(semVer)) + + if len(semVerArr) < MAX_VERSION_COMPONENTS: + diff = MAX_VERSION_COMPONENTS - len(semVerArr) + for i in range(diff): + semVerArr.append('0') + + try: + semVerMap = { + MAJOR: int(semVerArr[0]), + MINOR: int(semVerArr[1]), + PATCH: int(semVerArr[2]) + } + except ValueError as e: + raise ValueError("unable to parse an int for semantic version '{}' due to invalid format. ({})".format(semVer, str(e))) + + return semVerMap + +def compare_versions(old, new): + try: + oldSemVerMap = process(old) + newSemVerMap = process(new) + except: + raise + + versions = [MAJOR, MINOR, PATCH] + + for v in range(len(versions)): + if newSemVerMap[versions[v]] > oldSemVerMap[versions[v]]: + return UPGRADE + elif newSemVerMap[versions[v]] < oldSemVerMap[versions[v]]: + return DOWNGRADE + + return NO_VERSION_CHANGE + +def run_module(): + module_args = dict( + new_version = dict(type='str', required=True), + old_version = dict(type='str', required=True) + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True + ) + + output = dict( + changed=False, + result='' + ) + + try: + result = compare_versions(module.params['old_version'], module.params['new_version']) + except ValueError as e: + module.fail_json(msg="Unable to compare semantic versions: {}".format(str(e)), **output) + + output['result'] = result + + module.exit_json(**output) + +def main(): + run_module() + +if __name__ == '__main__': + main() diff --git a/library/test_compare_semantic_versions.py b/library/test_compare_semantic_versions.py new file mode 100644 index 0000000..8fda93a --- /dev/null +++ b/library/test_compare_semantic_versions.py @@ -0,0 +1,33 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Daniel Anglin +# The MIT License (see LICENSE or https://mit-license.org) + +import unittest as u +import compare_semantic_versions as c + +class TestCompareSemanticVersions(u.TestCase): + def test_compare_versions_results(self): + self.assertEqual(c.compare_versions("2.3.0", "3.0.0"), c.UPGRADE) + self.assertEqual(c.compare_versions("2.4.0", "2.3.0"), c.DOWNGRADE) + self.assertEqual(c.compare_versions("1.2.2", "1.2.3"), c.UPGRADE) + self.assertEqual(c.compare_versions("v2", "2.0.0"), c.NO_VERSION_CHANGE) + self.assertEqual(c.compare_versions("1.4.0", "v1.4"), c.NO_VERSION_CHANGE) + self.assertEqual(c.compare_versions("12.24.1", "v12.25.6"), c.UPGRADE) + + def test_compare_versions_exceptions(self): + with self.assertRaises(ValueError) as ctx: + c.compare_versions("1.2.3.4", "1.2.3") + self.assertEqual("invalid number of version components in semantic version '1.2.3.4'.", str(ctx.exception)) + + with self.assertRaises(ValueError) as ctx: + c.compare_versions("10.3.1", "11..3") + self.assertRegex(str(ctx.exception), r'^unable to parse an int for semantic version \'11..3\' due to invalid format\..*$') + + with self.assertRaises(ValueError) as ctx: + c.compare_versions("10.FOUR.8", "10.4.8") + self.assertRegex(str(ctx.exception), r'^unable to parse an int for semantic version \'10.FOUR.8\' due to invalid format\..*$') + +if __name__ == '__main__': + u.main() diff --git a/roles/pleroma-main/tasks/main.yml b/roles/pleroma-main/tasks/main.yml index ac13200..ce27373 100644 --- a/roles/pleroma-main/tasks/main.yml +++ b/roles/pleroma-main/tasks/main.yml @@ -37,10 +37,37 @@ - "{{ pleroma_static_dir }}/static" - "{{ pleroma_static_dir }}/static/themes" -- name: Ensuring that the release build of pleroma is downloaded. +- name: Checking if Pleroma is already installed. + stat: + path: "{{ pleroma_user.home }}/bin/pleroma" + register: pleroma_bin + +- debug: + msg: "Pleroma is currently installed." + verbosity: 0 + when: pleroma_bin.stat.isreg is defined + +- debug: + msg: "Pleroma does not appear to be installed. It will be downloaded and installed." + verbosity: 0 + when: pleroma_bin.stat.isreg is not defined + +- name: Registering the 'enable_pleroma_download' flag. + set_fact: + enable_pleroma_download: true + when: (pleroma_bin.stat.isreg is not defined) or + (enable_pleroma_upgrade | default(False)) + +- name: Registering the 'enable_pleroma_installation' flag. + set_fact: + enable_pleroma_installation: true + when: pleroma_bin.stat.isreg is not defined + +- name: Ensuring that the stable release build of pleroma is downloaded. get_url: url: "{{ pleroma_download_url }}" dest: "{{ pleroma_download_dest }}" + when: enable_pleroma_download | default(False) - name: Unzipping the release build of pleroma. unarchive: @@ -49,12 +76,71 @@ dest: /tmp owner: "{{ pleroma_user.name }}" group: "{{ pleroma_user.group }}" + when: enable_pleroma_download | default(False) + +- name: Registering the installed version of Pleroma. + shell: "{{ pleroma_user.home }}/bin/pleroma version | awk '{print $2}'" + register: pleroma_installed_version + when: enable_pleroma_upgrade | default(False) + +- debug: + msg: "Version {{ pleroma_installed_version.stdout }} is installed." + verbosity: 0 + when: enable_pleroma_upgrade | default(False) + +- name: Registering the downloaded version of Pleroma. + shell: /tmp/release/bin/pleroma version | awk '{print $2}' + register: pleroma_downloaded_version + when: enable_pleroma_upgrade | default(False) + +- debug: + msg: "Version {{ pleroma_downloaded_version.stdout }} is downloaded." + verbosity: 0 + when: enable_pleroma_upgrade | default(False) + +- name: Comparing the installed and downloaded versions of Pleroma. + compare_semantic_versions: + old_version: "{{ pleroma_installed_version.stdout }}" + new_version: "{{ pleroma_downloaded_version.stdout }}" + register: comparison + when: enable_pleroma_upgrade | default(False) + +- fail: + msg: "This playbook does not currently support downgrading Pleroma." + when: comparison.result is defined and comparison.result == "downgrade" + +- debug: + msg: "Pleroma is already installed at the target version {{ pleroma_downloaded_version.stdout }}." + verbosity: 0 + when: comparison.result is defined and comparison.result == "noVersionChange" + +- debug: + msg: "Pleroma will be upgraded to version {{ pleroma_downloaded_version.stdout }}." + verbosity: 0 + when: comparison.result is defined and comparison.result == "upgrade" + +- name: Registering the 'enable_pleroma_installation' flag for the upgrade. + set_fact: + enable_pleroma_installation: true + when: comparison.result is defined and comparison.result == "upgrade" + +- name: Ensuring that the Pleroma service is stopped. + service: + name: pleroma + state: stopped + when: comparison.result is defined and comparison.result == "upgrade" + +- name: Ensuring that the previous version of Pleroma is uninstalled. + shell: | + find {{ pleroma_user.home }} -mindepth 1 -maxdepth 1 | xargs -I dir rm -rf dir + when: comparison.result is defined and comparison.result == "upgrade" - name: Ensuring that Pleroma is installed. shell: | find /tmp/release/ -mindepth 1 -maxdepth 1 | xargs -I dir mv dir {{ pleroma_user.home }} args: creates: "{{ pleroma_user.home }}/bin/pleroma" + when: enable_pleroma_installation is defined - name: Ensuring the configuration file is set. template: @@ -71,6 +157,7 @@ - migrate environment: PATH: "{{ ansible_env.PATH }}:/opt/pleroma/bin" + when: enable_pleroma_installation is defined - name: Ensuring that folder permissions are set properly in /opt/pleroma. shell: | @@ -103,7 +190,7 @@ group: "{{ pleroma_user.group }}" mode: '0400' -- name: Setting up the Pleroma service. +- name: Ensuring that the pleroma init file is installed. copy: src: "{{ pleroma_user.home }}/installation/init.d/pleroma" dest: /etc/init.d/pleroma @@ -116,7 +203,7 @@ service: name: pleroma enabled: yes - state: restarted + state: started - name: Cleaning up file: