163 lines
4 KiB
Python
163 lines
4 KiB
Python
|
#!/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()
|