Skip to content

Commit 1f6ccb6

Browse files
caladdbsanders
authored andcommitted
FEATURE: Ansible module for managing boxes
An Ansible module for returning managing Stacki boxes. The OS of the box can be modified only if no hosts are assigned to the box. Changing the OS of the box involves removing the existing box and re-adding it with the new OS. All enabled pallets, carts, and repos will need to be re-enabled. The module takes three parameters: `name` - The name of the box to manage `os` - The OS of the box to create. Defaults to the OS of the frontend. `state` - If present, then a box will be added if it doesn't exist. If the box does already exist but the OS is different, the box will be removed and re-added with the updated OS. If absent, then the box will be removed (if it exists). Example playbook: ``` --- - hosts: localhost tasks: - name: Add a box stacki_box: name: test os: sles register: result - name: Add box output debug: var: result - name: Remove a box stacki_box: name: test state: absent register: result - name: Remove box output debug: var: result ``` Output of the debug commands, showing the structure of the data returned: ``` TASK [Add box output] ************************************************************************************************** ok: [localhost] => { "result": { "changed": true, "failed": false } } TASK [Remove box output] *********************************************************************************************** ok: [localhost] => { "result": { "changed": true, "failed": false } } ```
1 parent ca4b988 commit 1f6ccb6

2 files changed

Lines changed: 222 additions & 0 deletions

File tree

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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_box
9+
short_description: Manage Stacki boxes
10+
description:
11+
- Add, edit, and remove Stacki boxes
12+
- The OS of the box can be modified only if no hosts are assigned to the box.
13+
- Changing the OS of the box involves removing the existing box and re-adding it with the new OS. All enabled pallets, carts, and repos will need to be re-enabled.
14+
15+
options:
16+
name:
17+
description:
18+
- The name of the box to manage
19+
required: true
20+
type: str
21+
22+
os:
23+
description:
24+
- The OS of the box to create
25+
required: false
26+
type: str
27+
default: OS of frontend
28+
29+
state:
30+
description:
31+
- If present, then a box will be added if it doesn't exist
32+
- If present and the box exists but the OS is different, the box will be removed and re-added with the updated OS.
33+
- If absent, then the box will be removed
34+
type: str
35+
choices: [ absent, present ]
36+
default: present
37+
"""
38+
39+
EXAMPLES = """
40+
- name: Add a box default
41+
stacki_box:
42+
name: foo
43+
44+
- name: Remove a box
45+
stacki_box:
46+
name: foo
47+
state: absent
48+
"""
49+
50+
RETURN = """ # """
51+
52+
from ansible.module_utils.basic import AnsibleModule
53+
from ansible.module_utils.stacki import run_stack_command, StackCommandError
54+
55+
56+
def main():
57+
# Define the arguments for this module
58+
argument_spec = dict(
59+
name=dict(type="str", required=True),
60+
os=dict(type="str", required=False),
61+
state=dict(type="str", default="present", choices=["absent", "present"])
62+
)
63+
64+
# Create our module object
65+
module = AnsibleModule(
66+
argument_spec=argument_spec,
67+
supports_check_mode=True
68+
)
69+
70+
# Initialize a blank result
71+
result = {
72+
"changed": False
73+
}
74+
75+
# Bail if the user is just checking syntax of their playbook
76+
if module.check_mode:
77+
module.exit_json(**result)
78+
79+
# Fetch our box info from Stacki
80+
try:
81+
boxes = run_stack_command("list.box", [module.params["name"]])
82+
except StackCommandError as e:
83+
# If box doesn't exist, it will raise an error
84+
boxes = []
85+
86+
if len(boxes) > 1:
87+
# No more than one box should match
88+
module.fail_json(msg="error - more than one box matches name", **result)
89+
90+
try:
91+
# Are we adding or removing?
92+
if module.params["state"] == "present":
93+
args = [module.params["name"]]
94+
if module.params["os"]:
95+
args.append("os="+module.params["os"])
96+
97+
if len(boxes) == 0:
98+
# Add a new box
99+
args = [module.params["name"]]
100+
if module.params["os"]:
101+
args.append("os="+module.params["os"])
102+
103+
run_stack_command("add.box", args)
104+
result["changed"] = True
105+
106+
elif module.params["os"] and module.params["os"] != boxes[0]["os"]:
107+
# Try to make the OS match. Might throw an error if the box has hosts attached.
108+
run_stack_command("remove.box", [module.params["name"]])
109+
run_stack_command("add.box", args)
110+
result["changed"] = True
111+
112+
else:
113+
# Only remove a box that actually exists
114+
if len(boxes):
115+
run_stack_command("remove.box", [module.params["name"]])
116+
result["changed"] = True
117+
118+
except StackCommandError as e:
119+
# Fetching the data failed
120+
module.fail_json(msg=e.message, **result)
121+
122+
# Return our data
123+
module.exit_json(**result)
124+
125+
126+
if __name__ == "__main__":
127+
main()
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import json
2+
3+
4+
class TestStackiBox:
5+
def test_add_box(self, host, host_os, run_ansible_module):
6+
# Add the box
7+
result = run_ansible_module("stacki_box", name="test")
8+
9+
assert result.status == "CHANGED"
10+
assert result.data["changed"] == True
11+
12+
# Check that it is there now
13+
result = host.run("stack list box test output-format=json")
14+
assert result.rc == 0
15+
assert json.loads(result.stdout) == [
16+
{
17+
"name": "test",
18+
"os": host_os,
19+
"pallets": "",
20+
"carts": "",
21+
"repos": "",
22+
}
23+
]
24+
25+
# Test idempotency by adding again
26+
result = run_ansible_module("stacki_box", name="test")
27+
28+
assert result.status == "SUCCESS"
29+
assert result.data["changed"] == False
30+
31+
def test_change_os(self, host, host_os, run_ansible_module):
32+
# Add the box
33+
result = run_ansible_module("stacki_box", name="test")
34+
35+
assert result.status == "CHANGED"
36+
assert result.data["changed"] == True
37+
38+
# Check that it is there now
39+
result = host.run("stack list box test output-format=json")
40+
assert result.rc == 0
41+
assert json.loads(result.stdout) == [
42+
{
43+
"name": "test",
44+
"os": host_os,
45+
"pallets": "",
46+
"carts": "",
47+
"repos": "",
48+
}
49+
]
50+
51+
# Now change to OS
52+
result = run_ansible_module("stacki_box", name="test", os="ubuntu")
53+
54+
assert result.status == "CHANGED"
55+
assert result.data["changed"] == True
56+
57+
# Check that the OS is changed
58+
result = host.run("stack list box test output-format=json")
59+
assert result.rc == 0
60+
assert json.loads(result.stdout) == [
61+
{
62+
"name": "test",
63+
"os": "ubuntu",
64+
"pallets": "",
65+
"carts": "",
66+
"repos": "",
67+
}
68+
]
69+
70+
def test_remove_box(self, add_box, host, run_ansible_module):
71+
# Remove the box
72+
result = run_ansible_module("stacki_box", name="test", state="absent")
73+
74+
assert result.status == "CHANGED"
75+
assert result.data["changed"] == True
76+
77+
# And confirm it is gone
78+
result = host.run("stack list box test")
79+
assert result.rc == 255
80+
assert "not a valid box" in result.stderr
81+
82+
# Test idempotency by removing it again
83+
result = run_ansible_module("stacki_box", name="test", state="absent")
84+
85+
assert result.status == "SUCCESS"
86+
assert result.data["changed"] == False
87+
88+
def test_bad_name(self, add_box, run_ansible_module):
89+
result = run_ansible_module("stacki_box", name="%", state="absent")
90+
91+
assert result.status == "FAILED!"
92+
assert result.data["changed"] == False
93+
94+
assert "error" in result.data["msg"]
95+
assert "more than one box matches name" in result.data["msg"]

0 commit comments

Comments
 (0)