Skip to content

Commit eb8292f

Browse files
caladdbsanders
authored andcommitted
FEATURE: Ansible module for host info
An Ansible module for returning host info. The module takes a single optional parameter: `name` for requesting the data for a specific host. If name is not provided, then the data for all hosts is returned. The data returned is a list of `hosts`. Example playbook: ``` --- - hosts: localhost tasks: - name: Get all host info stacki_host_info: register: result - name: All host output debug: var: result - name: Get host info for frontend-0-0 stacki_host_info: name: frontend-0-0 register: result - name: Host frontend-0-0 output debug: var: result ``` Output of the debug commands, showing the structure of the data returned: ``` TASK [All host output] ************************************************************************************************************************** ok: [localhost] => { "result": { "changed": false, "failed": false, "hosts": [ { "appliance": "frontend", "boot": { "action": "os", "nukecontroller": false, "nukedisks": false }, "box": "frontend", "comment": null, "environment": null, "groups": [], "host": "frontend-0-0", "installaction": "default", "interfaces": [ { "channel": null, "default": true, "interface": "eth1", "ip": "192.168.0.2", "mac": "08:00:27:24:c3:45", "module": null, "name": "frontend-0-0", "network": "private", "options": null, "vlan": null } ], "os": "sles", "osaction": "default", "rack": "0", "rank": "0", "status": { "ssh": "up", "state": "online" } } ] } } TASK [Host frontend-0-0 output] ***************************************************************************************************************** ok: [localhost] => { "result": { "changed": false, "failed": false, "hosts": [ { "appliance": "frontend", "boot": { "action": "os", "nukecontroller": false, "nukedisks": false }, "box": "frontend", "comment": null, "environment": null, "groups": [], "host": "frontend-0-0", "installaction": "default", "interfaces": [ { "channel": null, "default": true, "interface": "eth1", "ip": "192.168.0.2", "mac": "08:00:27:24:c3:45", "module": null, "name": "frontend-0-0", "network": "private", "options": null, "vlan": null } ], "os": "sles", "osaction": "default", "rack": "0", "rank": "0", "status": { "ssh": "up", "state": "online" } } ] } } ```
1 parent 49e304c commit eb8292f

File tree

2 files changed

+391
-0
lines changed

2 files changed

+391
-0
lines changed
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
# @copyright@
2+
# Copyright (c) 2006 - 2020 Teradata
3+
# All rights reserved. Stacki(r) v5.x stacki.com
4+
# https://github.com/Teradata/stacki/blob/master/LICENSE.txt
5+
# @copyright@
6+
7+
DOCUMENTATION = """
8+
module: stacki_host_info
9+
short_description: Return data about Stacki hosts
10+
description:
11+
- If name is supplied, returns data about a single host
12+
- If name is not supplied, returns data about all hosts in the system
13+
14+
options:
15+
name:
16+
description:
17+
- The name of the host to return data about
18+
required: false
19+
"""
20+
21+
EXAMPLES = """
22+
- name: Get info for a single host
23+
stacki_host_info:
24+
name: backend-0-0
25+
register: output
26+
27+
- name: Get info for all the hosts
28+
stacki_host_info:
29+
register: output
30+
"""
31+
32+
RETURN = """
33+
hosts:
34+
description:
35+
- List of hosts
36+
returned: on success
37+
type: list
38+
elements: dict
39+
contains:
40+
name:
41+
description:
42+
- Name of the host
43+
type: str
44+
45+
rack:
46+
description:
47+
- Rack number of the host
48+
type: str
49+
50+
rank:
51+
description:
52+
- Location of the host in the rack
53+
type: str
54+
55+
appliance:
56+
description:
57+
- The appliance of the host
58+
type: str
59+
60+
os:
61+
description:
62+
- OS of the host
63+
type: str
64+
65+
box:
66+
description:
67+
- The box of the host
68+
type: str
69+
70+
environment:
71+
description:
72+
- Environment of the host
73+
type: str
74+
75+
osaction:
76+
description:
77+
- Action used during boot of the host
78+
type: str
79+
80+
installaction:
81+
description:
82+
- Action used during installation of the host
83+
type: str
84+
85+
comment:
86+
description:
87+
- Freeform string about the host
88+
type: str
89+
90+
interfaces:
91+
description:
92+
- List of network interfaces for the host
93+
type: list
94+
elements: dict
95+
contains:
96+
interface:
97+
description:
98+
- Device for this interface
99+
type: str
100+
101+
default:
102+
description:
103+
- True if the interface is the default for the host
104+
type: bool
105+
106+
network:
107+
description:
108+
- Network attached to this interface
109+
type: str
110+
111+
mac:
112+
description:
113+
- Hardware MAC address for this interface
114+
type: str
115+
116+
ip:
117+
description:
118+
- IP address for this interface
119+
type: str
120+
121+
name:
122+
description:
123+
- Logical name for this interface
124+
type: str
125+
126+
module:
127+
description:
128+
- Device module for this interface
129+
type: str
130+
131+
vlan:
132+
description:
133+
- The VLAN ID for this interface
134+
type: str
135+
136+
options:
137+
description:
138+
- Module options for this interface
139+
type: str
140+
141+
channel:
142+
description:
143+
- Channel for this interface
144+
type: str
145+
146+
groups:
147+
description:
148+
- Groups this host is a member of
149+
type: list
150+
elements: str
151+
152+
boot:
153+
description:
154+
- Boot info for this host
155+
type: dict
156+
contains:
157+
action:
158+
description:
159+
- The action taken during next host boot
160+
type: str
161+
162+
nukedisks:
163+
description:
164+
- True if host storage partitions should be nuked next install
165+
type: bool
166+
167+
nukecontroller:
168+
description:
169+
- True if host storage controller should be nuked next install
170+
type: bool
171+
172+
status:
173+
description:
174+
- Various status fields for the host
175+
type: dict
176+
contains:
177+
state:
178+
description:
179+
- Latest state of the host
180+
type: str
181+
182+
ssh:
183+
description:
184+
- State of the SSH daemon
185+
type: str
186+
"""
187+
188+
from ansible.module_utils.basic import AnsibleModule
189+
from ansible.module_utils.stacki import run_stack_command, StackCommandError
190+
191+
192+
def main():
193+
# Define the arguments for this module
194+
argument_spec = dict(
195+
name=dict(type="str", required=False, default=None)
196+
)
197+
198+
# Create our module object
199+
module = AnsibleModule(
200+
argument_spec=argument_spec,
201+
supports_check_mode=True
202+
)
203+
204+
# Initialize a blank result
205+
result = {
206+
"changed": False,
207+
"hosts": []
208+
}
209+
210+
# Bail if the user is just checking syntax of their playbook
211+
if module.check_mode:
212+
module.exit_json(**result)
213+
214+
# Fetch our host info from Stacki
215+
args = []
216+
if module.params["name"]:
217+
args.append(module.params["name"])
218+
219+
try:
220+
for host in run_stack_command("list.host", args):
221+
# Fetch this host's interfaces
222+
host["interfaces"] = []
223+
for interface in run_stack_command("list.host.interface", [host["host"]]):
224+
del interface["host"]
225+
host["interfaces"].append(interface)
226+
227+
# Fetch the groups
228+
host["groups"] = run_stack_command(
229+
"list.host.group", [host["host"]]
230+
)[0]["groups"].split()
231+
232+
# Now boot info
233+
host["boot"] = run_stack_command("list.host.boot", [host["host"]])[0]
234+
del host["boot"]["host"]
235+
236+
# And finally the host status info
237+
host["status"] = run_stack_command("list.host.status", [host["host"]])[0]
238+
del host["status"]["host"]
239+
240+
# Add it to the results
241+
result["hosts"].append(host)
242+
243+
except StackCommandError as e:
244+
# Fetching the data failed
245+
module.fail_json(msg=e.message, **result)
246+
247+
# Return our data
248+
module.exit_json(**result)
249+
250+
251+
if __name__ == "__main__":
252+
main()
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
class TestStackiHostInfo:
2+
def test_no_name(self, add_host_with_interface, run_ansible_module, host_os):
3+
result = run_ansible_module("stacki_host_info")
4+
5+
assert result.status == "SUCCESS"
6+
assert result.data["changed"] == False
7+
8+
# The frontend's mac will change each run, so assert it is set then remove it
9+
assert result.data["hosts"][0]["interfaces"][0]["mac"]
10+
del result.data["hosts"][0]["interfaces"][0]["mac"]
11+
12+
# DBS pallet adds to the status, so assert it is set then remove it
13+
assert result.data["hosts"][0]["status"]
14+
del result.data["hosts"][0]["status"]
15+
16+
assert result.data["hosts"][1]["status"]
17+
del result.data["hosts"][1]["status"]
18+
19+
assert result.data["hosts"] == [
20+
{
21+
'appliance': 'frontend',
22+
'boot': {
23+
'action': 'os',
24+
'nukecontroller': False,
25+
'nukedisks': False
26+
},
27+
'box': 'frontend',
28+
'comment': None,
29+
'environment': None,
30+
'groups': [],
31+
'host': 'frontend-0-0',
32+
'installaction': 'default',
33+
'interfaces': [{
34+
'channel': None,
35+
'default': True,
36+
'interface': 'eth1',
37+
'ip': '192.168.0.2',
38+
'module': None,
39+
'name': 'frontend-0-0',
40+
'network': 'private',
41+
'options': None,
42+
'vlan': None
43+
}],
44+
'os': host_os,
45+
'osaction': 'default',
46+
'rack': '0',
47+
'rank': '0'
48+
},
49+
{
50+
'appliance': 'backend',
51+
'boot': {
52+
'action': 'os',
53+
'nukecontroller': False,
54+
'nukedisks': False
55+
},
56+
'box': 'default',
57+
'comment': None,
58+
'environment': None,
59+
'groups': [],
60+
'host': 'backend-0-0',
61+
'installaction': 'default',
62+
'interfaces': [{
63+
'channel': None,
64+
'default': None,
65+
'interface': 'eth0',
66+
'ip': None,
67+
'mac': None,
68+
'module': None,
69+
'name': None,
70+
'network': None,
71+
'options': None,
72+
'vlan': None
73+
}],
74+
'os': host_os,
75+
'osaction': 'default',
76+
'rack': '0',
77+
'rank': '0'
78+
}
79+
]
80+
81+
def test_with_name(self, add_host_with_interface, add_group, host, run_ansible_module, host_os):
82+
# Add our backend to a few groups
83+
result = host.run('stack add host group backend-0-0 group=test')
84+
assert result.rc == 0
85+
86+
add_group("foo")
87+
result = host.run('stack add host group backend-0-0 group=foo')
88+
assert result.rc == 0
89+
90+
# Run out module and check its output
91+
result = run_ansible_module("stacki_host_info", name="backend-0-0")
92+
93+
assert result.status == "SUCCESS"
94+
assert result.data["changed"] == False
95+
96+
97+
# DBS pallet adds to the status, so assert it is set then remove it
98+
assert result.data["hosts"][0]["status"]
99+
del result.data["hosts"][0]["status"]
100+
101+
assert result.data["hosts"] == [{
102+
'appliance': 'backend',
103+
'boot': {
104+
'action': 'os',
105+
'nukecontroller': False,
106+
'nukedisks': False
107+
},
108+
'box': 'default',
109+
'comment': None,
110+
'environment': None,
111+
'groups': ['foo', 'test'],
112+
'host': 'backend-0-0',
113+
'installaction': 'default',
114+
'interfaces': [{
115+
'channel': None,
116+
'default': None,
117+
'interface': 'eth0',
118+
'ip': None,
119+
'mac': None,
120+
'module': None,
121+
'name': None,
122+
'network': None,
123+
'options': None,
124+
'vlan': None
125+
}],
126+
'os': host_os,
127+
'osaction': 'default',
128+
'rack': '0',
129+
'rank': '0'
130+
}]
131+
132+
def test_bad_name(self, run_ansible_module):
133+
result = run_ansible_module("stacki_host_info", name="foo")
134+
135+
assert result.status == "FAILED!"
136+
assert result.data["changed"] == False
137+
138+
assert "error" in result.data["msg"]
139+
assert 'cannot resolve host "foo"' in result.data["msg"]

0 commit comments

Comments
 (0)