Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Module: Adding OpenBSD sysupgrade support #341

Merged
merged 1 commit into from
Aug 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
precurse marked this conversation as resolved.
Show resolved Hide resolved
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.
precurse marked this conversation as resolved.
Show resolved Hide resolved
- 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
precurse marked this conversation as resolved.
Show resolved Hide resolved
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'])