diff --git a/.azure-pipelines/azure-pipelines.yml b/.azure-pipelines/azure-pipelines.yml index 078109b..9aef200 100644 --- a/.azure-pipelines/azure-pipelines.yml +++ b/.azure-pipelines/azure-pipelines.yml @@ -36,7 +36,7 @@ variables: resources: containers: - container: default - image: quay.io/ansible/azure-pipelines-test-container:1.9.0 + image: quay.io/ansible/azure-pipelines-test-container:3.0.0 pool: Standard @@ -53,8 +53,24 @@ stages: targets: - name: CentOS 7 test: centos7 - - name: Fedora 35 - test: fedora35 + - name: Fedora 36 + test: fedora36 + - name: openSUSE 15 py3 + test: opensuse15 + - name: Ubuntu 20.04 + test: ubuntu2004 + - name: Ubuntu 22.04 + test: ubuntu2204 + - stage: Docker_2_14 + displayName: Docker 2.14 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: 2.14/linux/{0}/1 + targets: + - name: CentOS 7 + test: centos7 - name: Fedora 36 test: fedora36 - name: openSUSE 15 py3 @@ -197,8 +213,24 @@ stages: test: rhel/8.6 - name: RHEL 9.0 test: rhel/9.0 - - name: FreeBSD 12.3 - test: freebsd/12.3 + - name: FreeBSD 13.1 + test: freebsd/13.1 + - stage: Remote_2_14 + displayName: Remote 2.14 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: 2.14/{0}/1 + targets: + - name: MacOS 12.0 + test: macos/12.0 + - name: RHEL 7.9 + test: rhel/7.9 + - name: RHEL 8.6 + test: rhel/8.6 + - name: RHEL 9.0 + test: rhel/9.0 - name: FreeBSD 13.1 test: freebsd/13.1 - stage: Remote_2_13 @@ -297,6 +329,8 @@ stages: - Docker_2_12 - Remote_2_13 - Docker_2_13 + - Remote_2_14 + - Docker_2_14 - Remote_devel - Docker_devel jobs: diff --git a/changelogs/fragments/166_mount_absent_fstab.yml b/changelogs/fragments/166_mount_absent_fstab.yml new file mode 100644 index 0000000..be11324 --- /dev/null +++ b/changelogs/fragments/166_mount_absent_fstab.yml @@ -0,0 +1,2 @@ +minor_changes: + - mount - Add ``absent_from_fstab`` state (https://github.com/ansible-collections/ansible.posix/pull/166). diff --git a/changelogs/fragments/373_firewall_fix_missing_library_message.yml b/changelogs/fragments/373_firewall_fix_missing_library_message.yml new file mode 100644 index 0000000..a5faea8 --- /dev/null +++ b/changelogs/fragments/373_firewall_fix_missing_library_message.yml @@ -0,0 +1,3 @@ +--- +bugfixes: + - firewall - Fixed to output a more complete missing library message. diff --git a/changelogs/fragments/375_update_azp_container.yml b/changelogs/fragments/375_update_azp_container.yml new file mode 100644 index 0000000..6d02987 --- /dev/null +++ b/changelogs/fragments/375_update_azp_container.yml @@ -0,0 +1,3 @@ +--- +trivial: + - CI - AZP test container to 3.0.0 (https://github.com/ansible-collections/news-for-maintainers/issues/18). diff --git a/changelogs/fragments/380_update_usage_profile_tasks.yml b/changelogs/fragments/380_update_usage_profile_tasks.yml new file mode 100644 index 0000000..5b23d40 --- /dev/null +++ b/changelogs/fragments/380_update_usage_profile_tasks.yml @@ -0,0 +1,3 @@ +--- +bugfixes: + - Removed contentious terminology to match reference documentation in profile_tasks. diff --git a/changelogs/fragments/386_follow_ci_testing_rules.yml b/changelogs/fragments/386_follow_ci_testing_rules.yml new file mode 100644 index 0000000..f59e82a --- /dev/null +++ b/changelogs/fragments/386_follow_ci_testing_rules.yml @@ -0,0 +1,3 @@ +--- +trivial: + - CI - following the new CI testing rule ansible-test-sanity-docker-devel. diff --git a/changelogs/fragments/389_ci_add_stable_214.yml b/changelogs/fragments/389_ci_add_stable_214.yml new file mode 100644 index 0000000..6a174fd --- /dev/null +++ b/changelogs/fragments/389_ci_add_stable_214.yml @@ -0,0 +1,3 @@ +--- +trivial: +- CI - Add stable-2.14 to AZP (https://github.com/ansible-collections/ansible.posix/issues/388). diff --git a/changelogs/fragments/390_hosts_involved_same_password.yml b/changelogs/fragments/390_hosts_involved_same_password.yml new file mode 100644 index 0000000..1169a31 --- /dev/null +++ b/changelogs/fragments/390_hosts_involved_same_password.yml @@ -0,0 +1,3 @@ +--- +bugfixes: + - synchronize - Fixed hosts involved in rsync require the same password 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/changelogs/fragments/401_document_module_default_values.yml b/changelogs/fragments/401_document_module_default_values.yml new file mode 100644 index 0000000..8a631dc --- /dev/null +++ b/changelogs/fragments/401_document_module_default_values.yml @@ -0,0 +1,4 @@ +--- +trivial: +- acl - document default value for the ``entry`` parameter +- rhel_rpm_ostree - document default value for the ``name`` parameter diff --git a/plugins/action/synchronize.py b/plugins/action/synchronize.py index a5752b9..c70db5f 100644 --- a/plugins/action/synchronize.py +++ b/plugins/action/synchronize.py @@ -225,7 +225,6 @@ class ActionModule(ActionBase): # Parameter name needed by the ansible module _tmp_args['_local_rsync_path'] = task_vars.get('ansible_rsync_path') or 'rsync' - _tmp_args['_local_rsync_password'] = task_vars.get('ansible_ssh_pass') or task_vars.get('ansible_password') # rsync thinks that one end of the connection is localhost and the # other is the host we're running the task for (Note: We use @@ -333,8 +332,9 @@ class ActionModule(ActionBase): if src is None or dest is None: return dict(failed=True, msg="synchronize requires both src and dest parameters are set") - # Determine if we need a user@ + # Determine if we need a user@ and a password user = None + password = task_vars.get('ansible_ssh_pass', None) or task_vars.get('ansible_password', None) if not dest_is_local: # Src and dest rsync "path" handling if boolean(_tmp_args.get('set_remote_user', 'yes'), strict=False): @@ -344,10 +344,12 @@ class ActionModule(ActionBase): user = task_vars.get('ansible_user') or self._play_context.remote_user if not user: user = C.DEFAULT_REMOTE_USER - else: user = task_vars.get('ansible_user') or self._play_context.remote_user + if self._templar is not None: + user = self._templar.template(user) + # Private key handling # Use the private_key parameter if passed else use context private_key_file _tmp_args['private_key'] = _tmp_args.get('private_key', self._play_context.private_key_file) @@ -361,12 +363,17 @@ class ActionModule(ActionBase): # src is a local path, dest is a remote path: @ src = self._process_origin(src_host, src, user) dest = self._process_remote(_tmp_args, dest_host, dest, user, inv_port in localhost_ports) + + password = dest_host_inventory_vars.get('ansible_ssh_pass', None) or dest_host_inventory_vars.get('ansible_password', None) + if self._templar is not None: + password = self._templar.template(password) else: # Still need to munge paths (to account for roles) even if we aren't # copying files between hosts src = self._get_absolute_path(path=src) dest = self._get_absolute_path(path=dest) + _tmp_args['_local_rsync_password'] = password _tmp_args['src'] = src _tmp_args['dest'] = dest diff --git a/plugins/callback/profile_tasks.py b/plugins/callback/profile_tasks.py index c6118df..b7fc3ac 100644 --- a/plugins/callback/profile_tasks.py +++ b/plugins/callback/profile_tasks.py @@ -21,7 +21,7 @@ DOCUMENTATION = ''' - It also lists the top/bottom time consuming tasks in the summary (configurable) - Before 2.4 only the environment variables were available for configuration. requirements: - - whitelisting in configuration - see examples section below for details. + - enable in configuration - see examples section below for details. options: output_limit: description: Number of tasks to display in the summary @@ -46,7 +46,7 @@ EXAMPLES = ''' example: > To enable, add this to your ansible.cfg file in the defaults block [defaults] - callback_whitelist = ansible.posix.profile_tasks + callbacks_enabled=ansible.posix.profile_tasks sample output: > # # TASK: [ensure messaging security group exists] ******************************** diff --git a/plugins/module_utils/firewalld.py b/plugins/module_utils/firewalld.py index c79a126..6a76c32 100644 --- a/plugins/module_utils/firewalld.py +++ b/plugins/module_utils/firewalld.py @@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function from ansible_collections.ansible.posix.plugins.module_utils.version import LooseVersion +from ansible.module_utils.basic import missing_required_lib __metaclass__ = type @@ -314,6 +315,5 @@ class FirewallTransaction(object): if import_failure: module.fail_json( - msg='Python Module not found: firewalld and its python module are required for this module, \ - version 0.2.11 or newer required (0.3.9 or newer for offline operations)' + msg=missing_required_lib('firewall') + '. Version 0.2.11 or newer required (0.3.9 or newer for offline operations)' ) diff --git a/plugins/modules/acl.py b/plugins/modules/acl.py index a2e3d6d..119520e 100644 --- a/plugins/modules/acl.py +++ b/plugins/modules/acl.py @@ -44,6 +44,7 @@ options: description: - The actual user or group that the ACL applies to when matching entity types user or group are selected. type: str + default: "" etype: description: - The entity type of the ACL to apply, see C(setfacl) documentation for more info. diff --git a/plugins/modules/authorized_key.py b/plugins/modules/authorized_key.py index e11b416..5e37c28 100644 --- a/plugins/modules/authorized_key.py +++ b/plugins/modules/authorized_key.py @@ -347,6 +347,8 @@ def keyfile(module, user, write=False, path=None, manage_dir=True, follow=False) basedir = os.path.dirname(keysfile) if not os.path.exists(basedir): os.makedirs(basedir) + + f = None try: f = open(keysfile, "w") # touches file so we can set ownership and perms finally: diff --git a/plugins/modules/mount.py b/plugins/modules/mount.py index 36ceb98..61ce594 100644 --- a/plugins/modules/mount.py +++ b/plugins/modules/mount.py @@ -87,9 +87,12 @@ options: instead to work around this issue. C(remounted) expects the mount point to be present in the I(fstab). To remount a mount point not registered in I(fstab), use C(ephemeral) instead, especially with BSD nodes. + - C(absent_from_fstab) specifies that the device mount's entry will be + removed from I(fstab). This option does not unmount it or delete the + mountpoint. type: str required: true - choices: [ absent, mounted, present, unmounted, remounted, ephemeral ] + choices: [ absent, absent_from_fstab, mounted, present, unmounted, remounted, ephemeral ] fstab: description: - File to use instead of C(/etc/fstab). @@ -245,7 +248,7 @@ def _escape_fstab(v): if isinstance(v, int): return v else: - return( + return ( v. replace('\\', '\\134'). replace(' ', '\\040'). @@ -763,7 +766,7 @@ def main(): passno=dict(type='str', no_log=False, default='0'), src=dict(type='path'), backup=dict(type='bool', default=False), - state=dict(type='str', required=True, choices=['absent', 'mounted', 'present', 'unmounted', 'remounted', 'ephemeral']), + state=dict(type='str', required=True, choices=['absent', 'absent_from_fstab', 'mounted', 'present', 'unmounted', 'remounted', 'ephemeral']), ), supports_check_mode=True, required_if=( @@ -868,7 +871,9 @@ def main(): name = module.params['path'] changed = False - if state == 'absent': + if state == 'absent_from_fstab': + name, changed = unset_mount(module, args) + elif state == 'absent': name, changed = unset_mount(module, args) if changed and not module.check_mode: 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..0976e02 --- /dev/null +++ b/plugins/modules/rhel_rpm_ostree.py @@ -0,0 +1,124 @@ +#!/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 + default: [] + 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() diff --git a/tests/integration/targets/authorized_key/tasks/setup_steps.yml b/tests/integration/targets/authorized_key/tasks/setup_steps.yml index a3c21dc..2144b7a 100644 --- a/tests/integration/targets/authorized_key/tasks/setup_steps.yml +++ b/tests/integration/targets/authorized_key/tasks/setup_steps.yml @@ -1,5 +1,14 @@ # ------------------------------------------------------------- # Setup steps +- name: Clean up the working directory and files + file: + path: '{{ output_dir }}' + state: absent + +- name: Create the working directory + file: + path: '{{ output_dir }}' + state: directory - name: copy an existing file in place with comments copy: diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt new file mode 100644 index 0000000..0b6905e --- /dev/null +++ b/tests/sanity/ignore-2.15.txt @@ -0,0 +1,8 @@ +plugins/modules/synchronize.py pylint:disallowed-name +plugins/modules/synchronize.py use-argspec-type-path +plugins/modules/synchronize.py validate-modules:doc-default-does-not-match-spec +plugins/modules/synchronize.py validate-modules:nonexistent-parameter-documented +plugins/modules/synchronize.py validate-modules:parameter-type-not-in-doc +plugins/modules/synchronize.py validate-modules:undocumented-parameter +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang