Skip to content

Commit

Permalink
sysupgrade: new module (#341)
Browse files Browse the repository at this point in the history
* Adding types to sysupgrade documentation
* Apply suggestions from code review
* Adding installurl flag. Changing wording in example.
* Use None for installurl by default
* Changing word case in description
* sysupgrade: use module structure recommended by Ansible unit test docs
* Adding unit test for sysupgrade

Signed-off-by:  Andrew Klaus <andrew@aklaus.ca>
Co-authored-by: Abhijeet Kasurde <akasurde@redhat.com>
Co-authored-by: Andrew Klychkov <aaklychkov@mail.ru>
Co-authored-by: Felix Fontein <felix@fontein.de>

Co-authored-by: Abhijeet Kasurde <akasurde@redhat.com>
Co-authored-by: Andrew Klychkov <aaklychkov@mail.ru>
Co-authored-by: Felix Fontein <felix@fontein.de>
  • Loading branch information
4 people authored Aug 18, 2020
1 parent 0ae3d0a commit 2aabf5e
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 0 deletions.
152 changes: 152 additions & 0 deletions plugins/modules/system/sysupgrade.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/python

# Copyright: (c) 2020, Andrew Klaus <andrewklaus@gmail.com>
# 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 = r'''
---
module: sysupgrade
short_description: Manage OpenBSD system upgrades
version_added: 1.1.0
description:
- Manage OpenBSD system upgrades using sysupgrade.
options:
snapshot:
description:
- Apply the latest snapshot.
- Otherwise release will be applied.
default: no
type: bool
force:
description:
- Force upgrade (for snapshots only).
default: no
type: bool
keep_files:
description:
- Keep the files under /home/_sysupgrade.
- By default, the files will be deleted after the upgrade.
default: no
type: bool
fetch_only:
description:
- Fetch and verify files and create /bsd.upgrade but do not reboot.
- Set to C(false) if you want sysupgrade to reboot. This will cause Ansible to error, as it expects the module to exit gracefully. See the examples.
default: yes
type: bool
installurl:
description:
- OpenBSD mirror top-level URL for fetching an upgrade.
- By default, the mirror URL is pulled from /etc/installurl.
type: str
author:
- Andrew Klaus (@precurse)
'''

EXAMPLES = r'''
- name: Upgrade to latest release
community.general.sysupgrade:
register: sysupgrade
- name: Upgrade to latest snapshot
community.general.sysupgrade:
snapshot: yes
installurl: https://cloudflare.cdn.openbsd.org/pub/OpenBSD
register: sysupgrade
- name: Reboot to apply upgrade if needed
ansible.builtin.reboot:
when: sysupgrade.changed
# Note: Ansible will error when running this way due to how
# the reboot is forcefully handled by sysupgrade:
- name: Have sysupgrade automatically reboot
community.general.sysupgrade:
fetch_only: no
ignore_errors: yes
'''

RETURN = r'''
rc:
description: The command return code (0 means success).
returned: always
type: int
stdout:
description: Sysupgrade standard output.
returned: always
type: str
stderr:
description: Sysupgrade standard error.
returned: always
type: str
sample: "sysupgrade: need root privileges"
'''

from ansible.module_utils.basic import AnsibleModule


def sysupgrade_run(module):
sysupgrade_bin = module.get_bin_path('/usr/sbin/sysupgrade', required=True)
cmd = [sysupgrade_bin]
changed = False
warnings = []

# Setup command flags
if module.params['snapshot']:
run_flag = ['-s']
if module.params['force']:
# Force only applies to snapshots
run_flag.append('-f')
else:
# release flag
run_flag = ['-r']

if module.params['keep_files']:
run_flag.append('-k')

if module.params['fetch_only']:
run_flag.append('-n')

# installurl must be the last argument
if module.params['installurl']:
run_flag.append(module.params['installurl'])

rc, out, err = module.run_command(cmd + run_flag)

if rc != 0:
module.fail_json(msg="Command %s failed rc=%d, out=%s, err=%s" % (cmd, rc, out, err))
elif out.lower().find('already on latest snapshot') >= 0:
changed = False
elif out.lower().find('upgrade on next reboot') >= 0:
changed = True

return dict(
changed=changed,
rc=rc,
stderr=err,
stdout=out,
warnings=warnings
)


def main():
module = AnsibleModule(
argument_spec=dict(
snapshot=dict(type='bool', default=False),
fetch_only=dict(type='bool', default=True),
force=dict(type='bool', default=False),
keep_files=dict(type='bool', default=False),
installurl=dict(type='str'),
),
supports_check_mode=False,
)
return_dict = sysupgrade_run(module)
module.exit_json(**return_dict)


if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions plugins/modules/sysupgrade.py
67 changes: 67 additions & 0 deletions tests/unit/plugins/modules/system/test_sysupgrade.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# 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

from ansible.module_utils import basic
from ansible_collections.community.general.tests.unit.compat.mock import patch
from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase
from ansible_collections.community.general.plugins.modules.system import sysupgrade


class TestSysupgradeModule(ModuleTestCase):

def setUp(self):
super(TestSysupgradeModule, self).setUp()
self.module = sysupgrade
self.mock_get_bin_path = (patch('ansible.module_utils.basic.AnsibleModule.get_bin_path'))
self.get_bin_path = self.mock_get_bin_path.start()

def tearDown(self):
super(TestSysupgradeModule, self).tearDown()
self.mock_get_bin_path.stop()

def test_upgrade_success(self):
""" Upgrade was successful """

rc = 0
stdout = """
SHA256.sig 100% |*************************************| 2141 00:00
Signature Verified
INSTALL.amd64 100% |************************************| 43512 00:00
base67.tgz 100% |*************************************| 238 MB 02:16
bsd 100% |*************************************| 18117 KB 00:24
bsd.mp 100% |*************************************| 18195 KB 00:17
bsd.rd 100% |*************************************| 10109 KB 00:14
comp67.tgz 100% |*************************************| 74451 KB 00:53
game67.tgz 100% |*************************************| 2745 KB 00:03
man67.tgz 100% |*************************************| 7464 KB 00:04
xbase67.tgz 100% |*************************************| 22912 KB 00:30
xfont67.tgz 100% |*************************************| 39342 KB 00:28
xserv67.tgz 100% |*************************************| 16767 KB 00:24
xshare67.tgz 100% |*************************************| 4499 KB 00:06
Verifying sets.
Fetching updated firmware.
Will upgrade on next reboot
"""
stderr = ""

with patch.object(basic.AnsibleModule, "run_command") as run_command:
run_command.return_value = (rc, stdout, stderr)
with self.assertRaises(AnsibleExitJson) as result:
self.module.main()
self.assertTrue(result.exception.args[0]['changed'])

def test_upgrade_failed(self):
""" Upgrade failed """

rc = 1
stdout = ""
stderr = "sysupgrade: need root privileges"

with patch.object(basic.AnsibleModule, "run_command") as run_command_mock:
run_command_mock.return_value = (rc, stdout, stderr)
with self.assertRaises(AnsibleFailJson) as result:
self.module.main()
self.assertTrue(result.exception.args[0]['failed'])
self.assertIn('need root', result.exception.args[0]['msg'])

0 comments on commit 2aabf5e

Please sign in to comment.