Add a validation that checks for available update
This patch adds a validation that checks for newer versions of python-tripleoclient. The check is done through the check_package_update module which uses yum list to find available versions of a package. Change-Id: I19660980618fae318555401f5a7be01cb4d0ef2b
This commit is contained in:
parent
2ff3c796e7
commit
4920dab28d
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
New validation to check for latest minor version of python-tripleoclient
|
||||
- |
|
||||
New module to check for new minor and major versions of a package
|
103
tripleo_validations/tests/library/test_check_package_update.py
Normal file
103
tripleo_validations/tests/library/test_check_package_update.py
Normal file
@ -0,0 +1,103 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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.
|
||||
|
||||
from mock import MagicMock
|
||||
from mock import patch
|
||||
|
||||
from tripleo_validations.tests import base
|
||||
from validations.library.check_package_update import check_update
|
||||
from validations.library.check_package_update import get_package_details
|
||||
|
||||
|
||||
PKG_INSTALLED = """\
|
||||
Last metadata expiration check: 1 day, 3:05:37 ago on Mon Jun 5 11:55:16 2017.
|
||||
Installed Packages
|
||||
foo-package.x86_64 2:6.1.5-1 @spideroak-one-stable
|
||||
"""
|
||||
|
||||
# This stretches the boundaries of a realistic yum list output a bit
|
||||
# but it's more explicit for testing.
|
||||
PKG_AVAILABLE = """\
|
||||
Last metadata expiration check: 1 day, 3:06:30 ago on Mon Jun 5 11:55:16 2017.
|
||||
Available Packages
|
||||
foo-package.i386 2:9.1.0-1 foo-stable
|
||||
foo-package.i386 2:6.2.3-1 foo-stable
|
||||
foo-package.x86_64 2:8.0.0-1 foo-stable
|
||||
foo-package.x86_64 2:7.0.0-1 foo-stable
|
||||
foo-package.x86_64 2:6.2.0-1 foo-stable
|
||||
foo-package.x86_64 2:6.1.6-1 foo-stable
|
||||
"""
|
||||
|
||||
|
||||
class TestGetPackageDetails(base.TestCase):
|
||||
def setUp(self):
|
||||
super(TestGetPackageDetails, self).setUp()
|
||||
self.entry = get_package_details("""\
|
||||
foo-package.x86_64 2:6.2.0-1 spideroak-one-stable
|
||||
""")
|
||||
|
||||
def test_name(self):
|
||||
self.assertEqual(self.entry.name, 'foo-package')
|
||||
|
||||
def test_arch(self):
|
||||
self.assertEqual(self.entry.arch, 'x86_64')
|
||||
|
||||
def test_version(self):
|
||||
self.assertEqual(self.entry.version, '6.2.0')
|
||||
|
||||
|
||||
class TestCheckUpdate(base.TestCase):
|
||||
def setUp(self):
|
||||
super(TestCheckUpdate, self).setUp()
|
||||
self.module = MagicMock()
|
||||
|
||||
def test_unsupported_pkg_mgr_fails(self):
|
||||
check_update(self.module, 'foo-package', 'apt')
|
||||
self.module.fail_json.assert_called_with(
|
||||
msg='Package manager "apt" is not supported.')
|
||||
|
||||
@patch('validations.library.check_package_update._command')
|
||||
def test_fails_if_installed_package_not_found(self, mock_command):
|
||||
mock_command.side_effect = [
|
||||
['', 'No package found.'],
|
||||
]
|
||||
check_update(self.module, 'foo-package', 'yum')
|
||||
self.module.fail_json.assert_called_with(
|
||||
msg='No package found.')
|
||||
|
||||
@patch('validations.library.check_package_update._command')
|
||||
def test_returns_current_and_available_versions(self, mock_command):
|
||||
mock_command.side_effect = [
|
||||
[PKG_INSTALLED, ''],
|
||||
[PKG_AVAILABLE, ''],
|
||||
]
|
||||
check_update(self.module, 'foo-package', 'yum')
|
||||
self.module.exit_json.assert_called_with(changed=False,
|
||||
name='foo-package',
|
||||
current_version='6.1.5',
|
||||
latest_minor_version='6.2.0',
|
||||
latest_major_version='8.0.0')
|
||||
|
||||
@patch('validations.library.check_package_update._command')
|
||||
def test_returns_current_version_if_no_updates(self, mock_command):
|
||||
mock_command.side_effect = [
|
||||
[PKG_INSTALLED, ''],
|
||||
['', 'No packages found'],
|
||||
]
|
||||
check_update(self.module, 'foo-package', 'yum')
|
||||
self.module.exit_json.assert_called_with(changed=False,
|
||||
name='foo-package',
|
||||
current_version='6.1.5',
|
||||
latest_minor_version='6.1.5',
|
||||
latest_major_version=None)
|
23
validations/check-latest-minor-version.yaml
Normal file
23
validations/check-latest-minor-version.yaml
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
- hosts: undercloud
|
||||
vars:
|
||||
metadata:
|
||||
name: Check if latest minor version is installed
|
||||
description: >
|
||||
Makes sure python-tripleoclient is at its latest minor version
|
||||
before starting an upgrade.
|
||||
groups:
|
||||
- pre-upgrade
|
||||
packages:
|
||||
- python-tripleoclient
|
||||
tasks:
|
||||
- name: Get available updates for packages
|
||||
check_package_update: package={{ item }} pkg_mgr={{ ansible_pkg_mgr }}
|
||||
with_items: "{{ packages }}"
|
||||
register: updates
|
||||
|
||||
- name: Check if current version is latest minor
|
||||
with_items: "{{ updates.results }}"
|
||||
assert:
|
||||
that: "item.latest_minor_version == item.current_version"
|
||||
msg: "A newer version of the {{ item.name }} package is available: {{ item.latest_minor_version }} (currently {{ item.current_version }})."
|
150
validations/library/check_package_update.py
Executable file
150
validations/library/check_package_update.py
Executable file
@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2017 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
""" Check for available updates for a given package."""
|
||||
|
||||
import collections
|
||||
import subprocess
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: check_package_update
|
||||
short_description: Check for available updates for a given package
|
||||
options:
|
||||
package:
|
||||
required: true
|
||||
description:
|
||||
- The name of the package you want to check
|
||||
type: str
|
||||
pkg_mgr:
|
||||
required: true
|
||||
description:
|
||||
- Supported Package Manager, DNF or YUM
|
||||
type: str
|
||||
author: "Florian Fuchs"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- hosts: webservers
|
||||
tasks:
|
||||
- name: Get available updates for packages
|
||||
check_package_update:
|
||||
package: python-tripleoclient
|
||||
pkg_mgr: {{ ansible_pkg_mgr}}
|
||||
'''
|
||||
|
||||
SUPPORTED_PKG_MGRS = (
|
||||
'yum',
|
||||
'dnf',
|
||||
)
|
||||
|
||||
|
||||
PackageDetails = collections.namedtuple('PackageDetails',
|
||||
['name', 'arch', 'version'])
|
||||
|
||||
|
||||
def get_package_details(line):
|
||||
# Parses an output line from a package manager's
|
||||
# `list (available|installed)` command and returns
|
||||
# a named tuple
|
||||
parts = line.rstrip().split()
|
||||
name, arch = parts[0].split('.')
|
||||
# Version string, excluding release string and epoch
|
||||
version = parts[1].split('-')[0].split(':')[-1]
|
||||
return PackageDetails(name, arch, version)
|
||||
|
||||
|
||||
def _command(command):
|
||||
# Return the result of a subprocess call
|
||||
# as [stdout, stderr]
|
||||
process = subprocess.Popen(command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,)
|
||||
return process.communicate()
|
||||
|
||||
|
||||
def _get_installed_version_from_output(output, package):
|
||||
for line in output.split('\n'):
|
||||
if package in line:
|
||||
return get_package_details(line)
|
||||
|
||||
|
||||
def _get_latest_available_versions(output, installed):
|
||||
# Returns the latest available minor and major versions,
|
||||
# one for each.
|
||||
latest_minor = None
|
||||
latest_major = None
|
||||
# Get all packages with the same architecture
|
||||
packages = list([get_package_details(line) for line in output.split('\n')
|
||||
if '{i.name}.{i.arch}'.format(i=installed) in line])
|
||||
# Get all packages with the *same* major version
|
||||
minor = sorted((p for p in packages
|
||||
if p.version[0] == installed.version[0]))
|
||||
if len(minor) > 0:
|
||||
latest_minor = minor[-1].version
|
||||
# Get all packages with a *higher* available major version
|
||||
major = sorted((p for p in packages
|
||||
if p.version[0] > installed.version[0]))
|
||||
if len(major) > 0:
|
||||
latest_major = major[-1].version
|
||||
# If the output doesn't contain packages with the same major version
|
||||
# let's assume the currently installed version as latest minor one.
|
||||
if latest_minor is None:
|
||||
latest_minor = installed.version
|
||||
return latest_minor, latest_major
|
||||
|
||||
|
||||
def check_update(module, package, pkg_mgr):
|
||||
if pkg_mgr not in SUPPORTED_PKG_MGRS:
|
||||
module.fail_json(
|
||||
msg='Package manager "{}" is not supported.'.format(pkg_mgr))
|
||||
return
|
||||
|
||||
installed_stdout, installed_stderr = _command(
|
||||
[pkg_mgr, 'list', 'installed', package])
|
||||
# Fail the module if for some reason we can't lookup the current package.
|
||||
if installed_stderr != '':
|
||||
module.fail_json(msg=installed_stderr)
|
||||
return
|
||||
installed = _get_installed_version_from_output(installed_stdout, package)
|
||||
|
||||
available_stdout, available_stderr = _command(
|
||||
[pkg_mgr, 'list', 'available', installed.name])
|
||||
latest_minor_version, latest_major_version = \
|
||||
_get_latest_available_versions(available_stdout, installed)
|
||||
|
||||
module.exit_json(changed=False,
|
||||
name=installed.name,
|
||||
current_version=installed.version,
|
||||
latest_minor_version=latest_minor_version,
|
||||
latest_major_version=latest_major_version)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(argument_spec=dict(
|
||||
package=dict(required=True, type='str'),
|
||||
pkg_mgr=dict(required=True, type='str')
|
||||
))
|
||||
|
||||
check_update(module,
|
||||
module.params.get('package'),
|
||||
module.params.get('pkg_mgr'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
x
Reference in New Issue
Block a user