diff --git a/changelogs/fragments/393-rpm-ostree.yml b/changelogs/fragments/393-rpm-ostree.yml new file mode 100644 index 0000000..e473b39 --- /dev/null +++ b/changelogs/fragments/393-rpm-ostree.yml @@ -0,0 +1,5 @@ +--- +minor_changes: + - rhel_facts - new facts module to handle RHEL specific facts + - rhel_rpm_ostree - new module to handle RHEL rpm-ostree specific package management functionality + - rpm_ostree_upgrade - new module to automate rpm-ostree upgrades diff --git a/changelogs/fragments/393_rhel_for_edge.yml b/changelogs/fragments/393_rhel_for_edge.yml new file mode 100644 index 0000000..118d377 --- /dev/null +++ b/changelogs/fragments/393_rhel_for_edge.yml @@ -0,0 +1,4 @@ +--- +minor_changes: +- r4e_rpm_ostree - new module for validating package state on RHEL for Edge +- rpm_ostree_upgrade - new module to manage upgrades for rpm-ostree based systems diff --git a/plugins/modules/rhel_facts.py b/plugins/modules/rhel_facts.py new file mode 100644 index 0000000..57c15f7 --- /dev/null +++ b/plugins/modules/rhel_facts.py @@ -0,0 +1,76 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: rhel_facts +version_added: 1.5.0 +short_description: Facts module to set or override RHEL specific facts. +description: + - Compatibility layer for using the "package" module for rpm-ostree based systems via setting the "pkg_mgr" fact correctly. +author: + - Adam Miller (@maxamillion) +requirements: + - rpm-ostree +seealso: + - module: ansible.builtin.package +options: {} +''' + +EXAMPLES = ''' +- name: Playbook to use the package module on all RHEL footprints + vars: + ansible_facts_modules: + - setup # REQUIRED to be run before all custom fact modules + - ansible.posix.rhel_facts + tasks: + - name: Ensure packages are installed + ansible.builtin.package: + name: + - htop + - ansible + state: present +''' + +RETURN = """ +ansible_facts: + description: Relevant Ansible Facts + returned: when needed + type: complex + contains: + pkg_mgr: + description: System-level package manager override + returned: when needed + type: str + sample: {'pkg_mgr': 'ansible.posix.rhel_facts'} +""" + +import os + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + + module = AnsibleModule( + argument_spec=dict(), + supports_check_mode=True, + ) + + ansible_facts = {} + + # Verify that the platform is an rpm-ostree based system + if os.path.exists("/run/ostree-booted"): + ansible_facts['pkg_mgr'] = 'ansible.posix.rhel_rpm_ostree' + + module.exit_json(ansible_facts, changed=False) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/rhel_rpm_ostree.py b/plugins/modules/rhel_rpm_ostree.py new file mode 100644 index 0000000..e5e8f2b --- /dev/null +++ b/plugins/modules/rhel_rpm_ostree.py @@ -0,0 +1,123 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: rhel_rpm_ostree +version_added: 1.5.0 +short_description: Ensure packages exist in a RHEL for Edge rpm-ostree based system +description: + - Compatibility layer for using the "package" module for RHEL for Edge systems utilizing the RHEL System Roles. +author: + - Adam Miller (@maxamillion) +requirements: + - rpm-ostree +options: + name: + description: + - A package name or package specifier with version, like C(name-1.0). + - Comparison operators for package version are valid here C(>), C(<), C(>=), C(<=). Example - C(name>=1.0) + - If a previous version is specified, the task also needs to turn C(allow_downgrade) on. + See the C(allow_downgrade) documentation for caveats with downgrading packages. + - When using state=latest, this can be C('*') which means run C(yum -y update). + - You can also pass a url or a local path to a rpm file (using state=present). + To operate on several packages this can accept a comma separated string of packages or (as of 2.0) a list of packages. + aliases: [ pkg ] + type: list + elements: str + state: + description: + - Whether to install (C(present) or C(installed), C(latest)), or remove (C(absent) or C(removed)) a package. + - C(present) and C(installed) will simply ensure that a desired package is installed. + - C(latest) will update the specified package if it's not of the latest available version. + - C(absent) and C(removed) will remove the specified package. + - Default is C(None), however in effect the default action is C(present) unless the C(autoremove) option is + enabled for this module, then C(absent) is inferred. + type: str + choices: [ absent, installed, latest, present, removed ] +notes: + - This module does not support installing or removing packages to/from an overlay as this is not supported + by RHEL for Edge, packages needed should be defined in the osbuild Blueprint and provided to Image Builder + at build time. This module exists only for C(package) module compatibility. +''' + +EXAMPLES = ''' +- name: Ensure htop and ansible are installed on rpm-ostree based RHEL + ansible.posix.rhel_rpm_ostree: + name: + - htop + - ansible + state: present +''' + +RETURN = """ +msg: + description: status of rpm transaction + returned: always + type: str + sample: "No changes made." +""" + +import os +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text + + +def locally_installed(module, pkgname): + (rc, out, err) = module.run_command('{0} -q {1}'.format(module.get_bin_path("rpm"), pkgname).split()) + return (rc == 0) + + +def rpm_ostree_transaction(module): + pkgs = [] + + if module.params['state'] in ['present', 'installed', 'latest']: + for pkg in module.params['name']: + if not locally_installed(module, pkg): + pkgs.append(pkg) + elif module.params['state'] in ['absent', 'removed']: + for pkg in module.params['name']: + if locally_installed(module, pkg): + pkgs.append(pkg) + + if not pkgs: + module.exit_json(msg="No changes made.") + else: + if module.params['state'] in ['present', 'installed', 'latest']: + module.fail_json(msg="The following packages are absent in the currently booted rpm-ostree commit: %s" ' '.join(pkgs)) + else: + module.fail_json(msg="The following packages are present in the currently booted rpm-ostree commit: %s" ' '.join(pkgs)) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(type='list', elements='str', aliases=['pkg'], default=[]), + state=dict(type='str', default=None, choices=['absent', 'installed', 'latest', 'present', 'removed']), + ), + ) + + # Verify that the platform is an rpm-ostree based system + if not os.path.exists("/run/ostree-booted"): + module.fail_json(msg="Module rpm_ostree is only applicable for rpm-ostree based systems.") + + try: + rpm_ostree_transaction(module) + except Exception as e: + module.fail_json(msg=to_text(e), exception=traceback.format_exc()) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/rpm_ostree_upgrade.py b/plugins/modules/rpm_ostree_upgrade.py new file mode 100644 index 0000000..16689ca --- /dev/null +++ b/plugins/modules/rpm_ostree_upgrade.py @@ -0,0 +1,125 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: rpm_ostree_upgrade +short_description: Manage rpm-ostree upgrade transactions +description: + - Manage an rpm-ostree upgrade transactions. +version_added: 1.5.0 +author: +- Adam Miller (@maxamillion) +requirements: + - rpm-ostree +options: + os: + description: + - The OSNAME upon which to operate. + type: str + default: "" + required: false + cache_only: + description: + - Perform the transaction using only pre-cached data, do not download. + type: bool + default: false + required: false + allow_downgrade: + description: + - Allow for the upgrade to be a chronologically older tree. + type: bool + default: false + required: false + peer: + description: + - Force peer-to-peer connection instead of using a system message bus. + type: bool + default: false + required: false + +''' + +EXAMPLES = ''' +- name: Upgrade the rpm-ostree image without options, accept all defaults + ansible.posix.rpm_ostree_upgrade: + +- name: Upgrade the rpm-ostree image allowing downgrades + ansible.posix.rpm_ostree_upgrade: + allow_downgrade: true +''' + +RETURN = ''' +msg: + description: The command standard output + returned: always + type: str + sample: 'No upgrade available.' +''' + +import os +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native, to_text + + +def rpm_ostree_transaction(module): + cmd = [] + cmd.append(module.get_bin_path("rpm-ostree")) + cmd.append('upgrade') + + if module.params['os']: + cmd += ['--os', module.params['os']] + if module.params['cache_only']: + cmd += ['--cache-only'] + if module.params['allow_downgrade']: + cmd += ['--allow-downgrade'] + if module.params['peer']: + cmd += ['--peer'] + + module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C') + + rc, out, err = module.run_command(cmd) + + if rc != 0: + module.fail_json(rc=rc, msg=err) + else: + if to_text("No upgrade available.") in to_text(out): + module.exit_json(msg=out, changed=False) + else: + module.exit_json(msg=out, changed=True) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + os=dict(type='str', default=''), + cache_only=dict(type='bool', default=False), + allow_downgrade=dict(type='bool', default=False), + peer=dict(type='bool', default=False), + ), + ) + + # Verify that the platform is an rpm-ostree based system + if not os.path.exists("/run/ostree-booted"): + module.fail_json(msg="Module rpm_ostree_upgrade is only applicable for rpm-ostree based systems.") + + try: + rpm_ostree_transaction(module) + except Exception as e: + module.fail_json(msg=to_native(e), exception=traceback.format_exc()) + + +if __name__ == '__main__': + main()