Skip to content

Commit

Permalink
Add reset_info module (#148)
Browse files Browse the repository at this point in the history
* Add reset_info module.

* Increase coverage.
  • Loading branch information
felixfontein authored Mar 1, 2025
1 parent e974ca8 commit fd2cbbc
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 2 deletions.
1 change: 1 addition & 0 deletions meta/runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ action_groups:
- firewall
- firewall_info
- reset
- reset_info
- reverse_dns
- server
- server_info
Expand Down
8 changes: 6 additions & 2 deletions plugins/modules/reset.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
- Felix Fontein (@felixfontein)
description:
- Reset a dedicated server with a software or hardware reset, or by requesting a manual reset.
seealso:
- module: community.hrobot.reset_info
description: Retrieve information on resetter.
extends_documentation_fragment:
- community.hrobot.robot
- community.hrobot.attributes
Expand Down Expand Up @@ -49,7 +52,8 @@
- V(hardware) is a hardware reset similar to pressing the Restart button. The power is cycled for the server.
- V(manual) is a manual reset. This requests a technician to manually do the shutdown while looking at the screen output.
B(Be careful) and only use this when really necessary!
- Note that not every server supports every reset method!
- "Note that not every server supports every reset method! You can query the supported reset methods by using the
RV(community.hrobot.reset_info#module:reset.type) return value of the M(community.hrobot.reset_info) module."
type: str
required: true
choices:
Expand All @@ -64,7 +68,7 @@
community.hrobot.reset:
hetzner_user: foo
hetzner_password: bar
failover_ip: 1.2.3.4
server_number: 1234
state: power
- name: Make sure that the server supports manual reset
Expand Down
141 changes: 141 additions & 0 deletions plugins/modules/reset_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) 2019 Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import absolute_import, division, print_function
__metaclass__ = type


DOCUMENTATION = r"""
module: reset_info
short_description: Query information on the resetter of a dedicated server
version_added: 2.2.0
author:
- Felix Fontein (@felixfontein)
description:
- Query information on the resetter of a dedicated server.
seealso:
- module: community.hrobot.reset
description: Reset dedicated server.
extends_documentation_fragment:
- community.hrobot.robot
- community.hrobot.attributes
- community.hrobot.attributes.actiongroup_robot
- community.hrobot.attributes.idempotent_not_modify_state
- community.hrobot.attributes.info_module
options:
server_number:
description:
- The server number of the server to query its resetter.
type: int
required: true
"""

EXAMPLES = r"""
- name: Query resetter information for server 1234
community.hrobot.reset_info:
hetzner_user: foo
hetzner_password: bar
server_number: 1234
register: result
- name: Show reset methods
ansible.builtin.debug:
msg: "{{ result.reset.type }}"
"""

RETURN = r"""
reset:
description:
- Information on the server's resetter.
type: dict
returned: success
contains:
server_ip:
description:
- The primary IPv4 address of the server.
type: str
returned: success
sample: 123.123.123.123
server_ipv6_net:
description:
- The primary IPv6 network of the server.
type: str
returned: success
sample: "2a01:4f8:111:4221::"
server_number:
description:
- The server's ID.
type: int
returned: success
sample: 321
type:
description:
- The reset types supported by the resetter.
- "Can be used for the O(community.hrobot.reset#module:reset_type) option of the M(community.hrobot.reset) module."
type: list
elements: str
returned: success
sample: [software, hardware, manual]
choices:
- software
- hardware
- power
- manual
operating_status:
description:
- The server's operating status.
type: str
returned: success
sample: not supported
"""

from ansible.module_utils.basic import AnsibleModule

from ansible_collections.community.hrobot.plugins.module_utils.robot import (
BASE_URL,
ROBOT_DEFAULT_ARGUMENT_SPEC,
fetch_url_json,
)


def main():
argument_spec = dict(
server_number=dict(type='int', required=True),
)
argument_spec.update(ROBOT_DEFAULT_ARGUMENT_SPEC)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)

server_number = module.params['server_number']

url = "{0}/reset/{1}".format(BASE_URL, server_number)
result, error = fetch_url_json(module, url, accept_errors=['SERVER_NOT_FOUND', 'RESET_NOT_AVAILABLE'])
if error == 'SERVER_NOT_FOUND':
module.fail_json(msg='This server does not exist, or you do not have access rights for it')
if error == 'RESET_NOT_AVAILABLE':
module.fail_json(msg='The server has no reset option available')

reset = dict(result['reset'])

reset_types = reset.get('type')
if isinstance(reset_types, list):
translation = {
'sw': 'software',
'hw': 'hardware',
'power': 'power',
'man': 'manual',
}
reset['type'] = [translation.get(elt, elt) for elt in reset_types]

module.exit_json(reset=reset, changed=True)


if __name__ == '__main__': # pragma: no cover
main() # pragma: no cover
122 changes: 122 additions & 0 deletions tests/unit/plugins/modules/test_reset_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Copyright (c) 2025 Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type


from ansible_collections.community.internal_test_tools.tests.unit.utils.fetch_url_module_framework import (
FetchUrlCall,
BaseTestModule,
)

from ansible_collections.community.hrobot.plugins.module_utils.robot import BASE_URL
from ansible_collections.community.hrobot.plugins.modules import reset_info


class TestHetznerResetInfo(BaseTestModule):
MOCK_ANSIBLE_MODULEUTILS_BASIC_ANSIBLEMODULE = 'ansible_collections.community.hrobot.plugins.modules.reset_info.AnsibleModule'
MOCK_ANSIBLE_MODULEUTILS_URLS_FETCH_URL = 'ansible_collections.community.hrobot.plugins.module_utils.robot.fetch_url'

def test_valid(self, mocker):
result = self.run_module_success(mocker, reset_info, {
'hetzner_user': 'test',
'hetzner_password': 'hunter2',
'server_number': 23,
}, [
FetchUrlCall('GET', 200)
.expect_basic_auth('test', 'hunter2')
.expect_force_basic_auth(True)
.result_json({
'reset': {
'server_ip': '123.123.123.123',
'server_ipv6_net': '2a01:4f8:111:4221::',
'server_number': 23,
'type': [
'sw',
'hw',
'man',
],
'operating_status': 'not supported',
},
})
.expect_url('{0}/reset/23'.format(BASE_URL)),
])
assert result['changed'] is True
assert result['reset'] == {
'server_ip': '123.123.123.123',
'server_ipv6_net': '2a01:4f8:111:4221::',
'server_number': 23,
'type': [
'software',
'hardware',
'manual',
],
'operating_status': 'not supported',
}

def test_valid_no_type(self, mocker):
result = self.run_module_success(mocker, reset_info, {
'hetzner_user': 'test',
'hetzner_password': 'hunter2',
'server_number': 23,
}, [
FetchUrlCall('GET', 200)
.expect_basic_auth('test', 'hunter2')
.expect_force_basic_auth(True)
.result_json({
'reset': {
'server_ip': '123.123.123.123',
'server_ipv6_net': '2a01:4f8:111:4221::',
'server_number': 23,
'operating_status': 'not supported',
},
})
.expect_url('{0}/reset/23'.format(BASE_URL)),
])
assert result['changed'] is True
assert result['reset'] == {
'server_ip': '123.123.123.123',
'server_ipv6_net': '2a01:4f8:111:4221::',
'server_number': 23,
'operating_status': 'not supported',
}

# Errors

def test_server_not_found(self, mocker):
result = self.run_module_failed(mocker, reset_info, {
'hetzner_user': '',
'hetzner_password': '',
'server_number': 23,
}, [
FetchUrlCall('GET', 404)
.result_json({
'error': {
'status': 404,
'code': 'SERVER_NOT_FOUND',
'message': 'Server not found',
},
})
.expect_url('{0}/reset/23'.format(BASE_URL)),
])
assert result['msg'] == 'This server does not exist, or you do not have access rights for it'

def test_reset_not_available(self, mocker):
result = self.run_module_failed(mocker, reset_info, {
'hetzner_user': '',
'hetzner_password': '',
'server_number': 23,
}, [
FetchUrlCall('GET', 404)
.result_json({
'error': {
'status': 404,
'code': 'RESET_NOT_AVAILABLE',
'message': 'The server has no reset option',
},
})
.expect_url('{0}/reset/23'.format(BASE_URL)),
])
assert result['msg'] == 'The server has no reset option available'

0 comments on commit fd2cbbc

Please sign in to comment.