From 4faf07f89746853d9f8b625de0d1a9cb97c10e5d Mon Sep 17 00:00:00 2001 From: Gael Chamoulaud Date: Thu, 25 Jul 2019 16:44:58 +0200 Subject: [PATCH] Simplifying the check of the latest package version validation This patch makes this validation simpler by checking if a package update is available. This patch also renames the role's name: - Old: check-latest-minor-version - New: check-latest-packages-version The molecule tests are also included in this commit. Change-Id: Ia4976b1cb79911d2fdb07eb6536c9a898c35be23 Signed-off-by: Gael Chamoulaud --- library/check_package_update.py | 94 +++++++++---------- ...aml => check-latest-packages-version.yaml} | 6 +- .../check-latest-minor-version/tasks/main.yml | 16 ---- .../check-latest-minor-version/vars/main.yml | 8 -- .../defaults/main.yml | 0 .../molecule/default/Dockerfile.j2 | 37 ++++++++ .../molecule/default/molecule.yml | 47 ++++++++++ .../molecule/default/playbook.yml | 51 ++++++++++ .../molecule/default/prepare.yml | 25 +++++ .../tasks/main.yml | 16 ++++ .../vars/main.yml | 8 ++ .../library/test_check_package_update.py | 34 +++---- 12 files changed, 243 insertions(+), 99 deletions(-) rename playbooks/{check-latest-minor-version.yaml => check-latest-packages-version.yaml} (50%) delete mode 100644 roles/check-latest-minor-version/tasks/main.yml delete mode 100644 roles/check-latest-minor-version/vars/main.yml rename roles/{check-latest-minor-version => check-latest-packages-version}/defaults/main.yml (100%) create mode 100644 roles/check-latest-packages-version/molecule/default/Dockerfile.j2 create mode 100644 roles/check-latest-packages-version/molecule/default/molecule.yml create mode 100644 roles/check-latest-packages-version/molecule/default/playbook.yml create mode 100644 roles/check-latest-packages-version/molecule/default/prepare.yml create mode 100644 roles/check-latest-packages-version/tasks/main.yml create mode 100644 roles/check-latest-packages-version/vars/main.yml diff --git a/library/check_package_update.py b/library/check_package_update.py index b016c1dee..0590ed6f8 100755 --- a/library/check_package_update.py +++ b/library/check_package_update.py @@ -55,18 +55,17 @@ SUPPORTED_PKG_MGRS = ( PackageDetails = collections.namedtuple('PackageDetails', - ['name', 'arch', 'version']) + ['name', 'version', 'release', 'arch']) -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 get_package_details(output): + if output: + return PackageDetails( + output.split('|')[0], + output.split('|')[1], + output.split('|')[2], + output.split('|')[3], + ) def _command(command): @@ -79,37 +78,6 @@ def _command(command): 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( @@ -117,23 +85,47 @@ def check_update(module, package, pkg_mgr): return installed_stdout, installed_stderr = _command( - [pkg_mgr, 'list', 'installed', package]) + ['rpm', '-qa', '--qf', + '%{NAME}|%{VERSION}|%{RELEASE}|%{ARCH}', + 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) + elif not installed_stdout: + module.fail_json( + msg='"{}" is not an installed package.'.format(package)) + return + + installed = get_package_details(installed_stdout) + + pkg_mgr_option = 'available' + if pkg_mgr == 'dnf': + pkg_mgr_option = '--available' available_stdout, available_stderr = _command( - [pkg_mgr, 'list', 'available', installed.name]) - latest_minor_version, latest_major_version = \ - _get_latest_available_versions(available_stdout, installed) + [pkg_mgr, '-q', 'list', pkg_mgr_option, installed.name]) - module.exit_json(changed=False, - name=installed.name, - current_version=installed.version, - latest_minor_version=latest_minor_version, - latest_major_version=latest_major_version) + if available_stdout: + new_pkg_info = available_stdout.split('\n')[1].rstrip().split()[:2] + new_ver, new_rel = new_pkg_info[1].split('-') + + module.exit_json( + changed=False, + name=installed.name, + current_version=installed.version, + current_release=installed.release, + new_version=new_ver, + new_release=new_rel) + else: + module.exit_json( + changed=False, + name=installed.name, + current_version=installed.version, + current_release=installed.release, + new_version=None, + new_release=None) def main(): diff --git a/playbooks/check-latest-minor-version.yaml b/playbooks/check-latest-packages-version.yaml similarity index 50% rename from playbooks/check-latest-minor-version.yaml rename to playbooks/check-latest-packages-version.yaml index b9f695a3b..e850171d1 100644 --- a/playbooks/check-latest-minor-version.yaml +++ b/playbooks/check-latest-packages-version.yaml @@ -3,11 +3,11 @@ gather_facts: yes vars: metadata: - name: Check if latest minor version is installed + name: Check if latest version of packages is installed description: > - Makes sure python-tripleoclient is at its latest minor version + Makes sure python-tripleoclient is at its latest version before starting an upgrade. groups: - pre-upgrade roles: - - check-latest-minor-version + - check-latest-packages-version diff --git a/roles/check-latest-minor-version/tasks/main.yml b/roles/check-latest-minor-version/tasks/main.yml deleted file mode 100644 index f97d0f9cc..000000000 --- a/roles/check-latest-minor-version/tasks/main.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- 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 }})." diff --git a/roles/check-latest-minor-version/vars/main.yml b/roles/check-latest-minor-version/vars/main.yml deleted file mode 100644 index 593c45b26..000000000 --- a/roles/check-latest-minor-version/vars/main.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -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 diff --git a/roles/check-latest-minor-version/defaults/main.yml b/roles/check-latest-packages-version/defaults/main.yml similarity index 100% rename from roles/check-latest-minor-version/defaults/main.yml rename to roles/check-latest-packages-version/defaults/main.yml diff --git a/roles/check-latest-packages-version/molecule/default/Dockerfile.j2 b/roles/check-latest-packages-version/molecule/default/Dockerfile.j2 new file mode 100644 index 000000000..1b91a0e0b --- /dev/null +++ b/roles/check-latest-packages-version/molecule/default/Dockerfile.j2 @@ -0,0 +1,37 @@ +# Molecule managed +# Copyright 2019 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. + + +{% if item.registry is defined %} +FROM {{ item.registry.url }}/{{ item.image }} +{% else %} +FROM {{ item.image }} +{% endif %} + +RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates && apt-get clean; \ + elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python*-dnf bash {{ item.pkg_extras | default('') }} && dnf clean all; \ + elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl python-setuptools bash {{ item.pkg_extras | default('') }} && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \ + elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml {{ item.pkg_extras | default('') }} && zypper clean -a; \ + elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates {{ item.pkg_extras | default('') }}; \ + elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates {{ item.pkg_extras | default('') }} && xbps-remove -O; fi + +{% for pkg in item.easy_install | default([]) %} +# install pip for centos where there is no python-pip rpm in default repos +RUN easy_install {{ pkg }} +{% endfor %} + + +CMD ["sh", "-c", "while true; do sleep 10000; done"] diff --git a/roles/check-latest-packages-version/molecule/default/molecule.yml b/roles/check-latest-packages-version/molecule/default/molecule.yml new file mode 100644 index 000000000..7067900c6 --- /dev/null +++ b/roles/check-latest-packages-version/molecule/default/molecule.yml @@ -0,0 +1,47 @@ +--- +driver: + name: docker + +log: true + +platforms: + - name: centos7 + hostname: centos7 + image: centos:7 + pkg_extras: python-setuptools + easy_install: + - pip + environment: &env + http_proxy: "{{ lookup('env', 'http_proxy') }}" + https_proxy: "{{ lookup('env', 'https_proxy') }}" + + - name: fedora28 + hostname: fedora28 + image: fedora:28 + pkg_extras: python*-setuptools + environment: + <<: *env + +provisioner: + name: ansible + log: true + env: + ANSIBLE_STDOUT_CALLBACK: yaml + ANSIBLE_LIBRARY: "../../../../library" + +scenario: + test_sequence: + - destroy + - create + - prepare + - converge + - verify + - destroy + +lint: + enabled: false + +verifier: + name: testinfra + lint: + name: flake8 diff --git a/roles/check-latest-packages-version/molecule/default/playbook.yml b/roles/check-latest-packages-version/molecule/default/playbook.yml new file mode 100644 index 000000000..953905777 --- /dev/null +++ b/roles/check-latest-packages-version/molecule/default/playbook.yml @@ -0,0 +1,51 @@ +--- +# Copyright 2019 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. + + +- name: Converge + hosts: all + + tasks: + - name: Validate No Available Update for patch rpm + include_role: + name: check-latest-packages-version + vars: + packages: + - patch + + - name: Working Detection of Update for Pam package + block: + - include_role: + name: check-latest-packages-version + vars: + packages: + - pam + + rescue: + - name: Clear host errors + meta: clear_host_errors + + - debug: + msg: The validation works! End the playbook run + + - name: End play + meta: end_play + + - name: Fail the test + fail: + msg: | + The check-latest-packages-version role should have detected + that packages have available updates. diff --git a/roles/check-latest-packages-version/molecule/default/prepare.yml b/roles/check-latest-packages-version/molecule/default/prepare.yml new file mode 100644 index 000000000..4d1b75233 --- /dev/null +++ b/roles/check-latest-packages-version/molecule/default/prepare.yml @@ -0,0 +1,25 @@ +--- +# Copyright 2019 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. + + +- name: Prepare + hosts: all + gather_facts: no + + tasks: + - name: install patch rpm + package: + name: patch diff --git a/roles/check-latest-packages-version/tasks/main.yml b/roles/check-latest-packages-version/tasks/main.yml new file mode 100644 index 000000000..dd782ea6b --- /dev/null +++ b/roles/check-latest-packages-version/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- 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 the latest one + fail: + msg: >- + A newer version of the {{ item.name }} package is + available: {{ item.new_version }}-{{ item.new_release }} + (currently {{ item.current_version }}-{{ item.current_release }}) + with_items: "{{ updates.results }}" + when: item.new_version diff --git a/roles/check-latest-packages-version/vars/main.yml b/roles/check-latest-packages-version/vars/main.yml new file mode 100644 index 000000000..fa8403955 --- /dev/null +++ b/roles/check-latest-packages-version/vars/main.yml @@ -0,0 +1,8 @@ +--- +metadata: + name: Check if latest version of packages is installed + description: > + Makes sure python-tripleoclient is at its latest version + before starting an upgrade. + groups: + - pre-upgrade diff --git a/tripleo_validations/tests/library/test_check_package_update.py b/tripleo_validations/tests/library/test_check_package_update.py index 4af807875..b12c1dd70 100644 --- a/tripleo_validations/tests/library/test_check_package_update.py +++ b/tripleo_validations/tests/library/test_check_package_update.py @@ -20,32 +20,18 @@ from library.check_package_update import get_package_details from tripleo_validations.tests import base -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 -""" +PKG_INSTALLED = "foo-package|6.1.5|1|x86_64" -# 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 +foo-package.x86_64 8.0.0-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 -""") + self.entry = get_package_details("foo-package|6.2.0|1|x86_64") def test_name(self): self.assertEqual(self.entry.name, 'foo-package') @@ -56,6 +42,9 @@ foo-package.x86_64 2:6.2.0-1 spideroak-one-stable def test_version(self): self.assertEqual(self.entry.version, '6.2.0') + def test_release(self): + self.assertEqual(self.entry.release, '1') + class TestCheckUpdate(base.TestCase): def setUp(self): @@ -82,12 +71,14 @@ class TestCheckUpdate(base.TestCase): [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') + current_release='1', + new_version='8.0.0', + new_release='1') @patch('library.check_package_update._command') def test_returns_current_version_if_no_updates(self, mock_command): @@ -99,5 +90,6 @@ class TestCheckUpdate(base.TestCase): 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) + current_release='1', + new_version=None, + new_release=None)