fix: implement an upgrade procedure for Pleroma.

This commit implements different installation paths when
running the pleroma-main role, depending on whether Pleroma
needs to be installed for the first time or upgraded.

For first time installations the playbook will run through
the normal download and installation process and also executes
the database migration. If Pleroma is already installed then,
by default, the playbook will not re-install Pleroma or re-run
the database migration. If the user wants to update Pleroma to
a newer version then they can re-run the playbook with the
command-line argument '--extra-vars enable_pleroma_upgrade=True'.

This commit also introduces a custom module used to compare the
installed and downloaded semantic versions of Pleroma.
The playbook uses this to see whether the version change is an upgrade,
a downgrade or no version change. If it's an upgrade the playbook
will proceed with the re-installation of Pleroma. If there is no
change then the playbook will skip installation. Finally if
it detects that the user is trying to downgrade Pleroma then it will
fail.

This commit resolves dananglin/pleroma-ansible-playbook#9 and also
resolves dananglin/pleroma-ansible-playbook#5
This commit is contained in:
Dan Anglin 2020-03-06 12:04:48 +00:00
parent c28f0a199b
commit c9aad82027
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
6 changed files with 304 additions and 3 deletions

3
.gitignore vendored
View file

@ -2,3 +2,6 @@ inventories/*
!inventories/.gitkeep
site.yml
vapid-private-key.pem
library/__pycache__/
library/*.pyc

13
.gitlab-ci.yml Normal file
View file

@ -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

View file

@ -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

View file

@ -0,0 +1,162 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Daniel Anglin <d.n.i.anglin@gmail.com>
# 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()

View file

@ -0,0 +1,33 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Daniel Anglin <d.n.i.anglin@gmail.com>
# 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()

View file

@ -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: