Skip to content

Instantly share code, notes, and snippets.

@dustymabe
Created November 28, 2018 19:27
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dustymabe/926c0d9bb998b1404b9bc55021977fb7 to your computer and use it in GitHub Desktop.
Save dustymabe/926c0d9bb998b1404b9bc55021977fb7 to your computer and use it in GitHub Desktop.
ansible module for rpm-ostree package layering
#!/usr/bin/python
# https://github.com/ansible/ansible/issues/21185
# Copyright: (c) 2018, Dusty Mabe <dusty@dustymabe.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
# Most of this module was inspired by the command module
# ./lib/ansible/modules/commands/command.py
import shlex
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import b
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: rpm_ostree
short_description: A module for a few rpm-ostree operations
version_added: "2.X"
description:
- "A module for a few rpm-ostree operations"
options:
name:
description:
- This is the message to send to the sample module
required: true
state:
install:
description:
- Packages to install on the system
new:
description:
- Control to demo if the result of this module is changed or not
required: false
extends_documentation_fragment:
- azure
author:
- Your Name (@yourhandle)
'''
EXAMPLES = '''
# Pass in a message
- name: Test with a message
my_new_test_module:
name: hello world
# pass in a message and have changed true
- name: Test with a message and changed output
rpm-ostree:
name: hello world
new: true
# fail the module
- name: Test failure of the module
my_new_test_module:
name: fail me
'''
RETURN = '''
original_message:
description: The original name param that was passed in
type: str
message:
description: The output message that the sample module generates
'''
from ansible.module_utils.basic import AnsibleModule
def run_module():
# define available arguments/parameters a user can pass to the module
module_args = dict(
reboot=dict(type='bool', required=False, default=False),
name=dict(aliases=['pkg'], type='list', required=True),
state=dict(default='present',
choices=['absent', 'present',
'installed', 'removed', 'latest']),
)
# seed the result dict in the object
# we primarily care about changed and state
# change is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
original_message='',
message=''
)
# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
# if the user is working with this module in only check mode we do not
# want to make any changes to the environment, just return the current
# state with no modifications
if module.check_mode:
return result
# manipulate or modify the state as needed (this is going to be the
# part where your module will do what it needs to do)
# result['original_message'] = module.params['name']
# result['message'] = 'goodbye'
if module.params['state'] in ['installed', 'present']:
action = 'install'
elif module.params['state'] in ['absent', 'removed']:
action = 'uninstall'
cmd = "rpm-ostree {} --allow-inactive --idempotent --unchanged-exit-77 {}"
cmd = cmd.format(action, ' '.join(module.params['name']))
cmd = shlex.split(cmd)
rc, out, err = module.run_command(cmd, encoding=None)
if out is None:
out = b('')
if err is None:
err = b('')
result.update(dict(
rc = rc,
cmd = cmd,
stdout = out.rstrip(b("\r\n")),
stderr = err.rstrip(b("\r\n")),
))
# A few possible options:
# - rc=0 - succeeded in making a change
# - rc=77 - no change was needed
# - rc=? - error
if rc == 0:
result['changed'] = True
elif rc == 77:
result['changed'] = False
result['rc'] = 0
else:
module.fail_json(msg='non-zero return code', **result)
# in the event of a successful module execution, you will want to
# simple AnsibleModule.exit_json(), passing the key/value results
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()
@dustymabe
Copy link
Author

dustymabe commented Dec 19, 2018

an example of using this:

- block:
    - name: Install NTP                                              
      rpm_ostree_pkg: name=ntp state=present      
      notify: reboot_system_if_ostree      
    - meta: flush_handlers

with a reboot handler that looks like:

- name: reboot_system_if_ostree
  reboot:
  when: ansible_cmdline.ostree is defined

@fabianvf
Copy link

@dustymabe are you planning on submitting this to Ansible?

@fabianvf
Copy link

Would also be great to integrate it with the generic package module

@dustymabe
Copy link
Author

@dustymabe are you planning on submitting this to Ansible?

@fabianvf - yeah, at some point

Would also be great to integrate it with the generic package module

yeah the code works like that today if you insert it into the list of modules to "switch" on.

@fbruetting
Copy link

fbruetting commented May 25, 2019

Thank you for this module! However, I tried this with Ansible v2.8 and get the following error. Would you like to fix this, please?

FAILED! => {
"changed": false,
"module_stderr": "Shared connection to <HOST-FQDN> closed.\r\n",
"module_stdout": "
  /home/frank/.ansible/tmp/ansible-tmp-1558824059.0516987-80466753735172/AnsiballZ_rpm_ostree_pkg.py:18:
    DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
    import imp
    Traceback (most recent call last):
      File \"/home/frank/.ansible/tmp/ansible-tmp-1558824059.0516987-80466753735172/AnsiballZ_rpm_ostree_pkg.py\", line 114, in <module>
        _ansiballz_main()
      File \"/home/frank/.ansible/tmp/ansible-tmp-1558824059.0516987-80466753735172/AnsiballZ_rpm_ostree_pkg.py\", line 106, in _ansiballz_main
        invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
      File \"/home/frank/.ansible/tmp/ansible-tmp-1558824059.0516987-80466753735172/AnsiballZ_rpm_ostree_pkg.py\", line 49, in invoke_module
        imp.load_module('__main__', mod, module, MOD_DESC)
      File \"/usr/lib64/python3.7/imp.py\", line 234, in load_module
        return load_source(name, filename, file)
      File \"/usr/lib64/python3.7/imp.py\", line 169, in load_source
        module = _exec(spec, sys.modules[name])
      File \"<frozen importlib._bootstrap>\", line 630, in _exec
      File \"<frozen importlib._bootstrap_external>\", line 728, in exec_module
      File \"<frozen importlib._bootstrap>\", line 219, in _call_with_frames_removed
      File \"/tmp/ansible_rpm_ostree_pkg_payload_8pmby7pg/__main__.py\", line 169, in <module>
      File \"/tmp/ansible_rpm_ostree_pkg_payload_8pmby7pg/__main__.py\", line 166, in main
      File \"/tmp/ansible_rpm_ostree_pkg_payload_8pmby7pg/__main__.py\", line 135, in run_module
    UnboundLocalError: local variable 'action' referenced before assignment\r\n",
"msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}

And in check mode, Ansible displayed this error:

fatal: [<HOST>]: FAILED! => {"changed": false, "msg": "New-style module did not handle its own exit"}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment