Skip to content

Commit 4a06903

Browse files
authored
Merge pull request #11 from merizrizal/7-add-disk-block-storage-module
7 add disk block storage module
2 parents 936e626 + 0e0cee9 commit 4a06903

File tree

5 files changed

+234
-8
lines changed

5 files changed

+234
-8
lines changed

.github/workflows/ansible-sanity-test-lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
strategy:
1414
fail-fast: false
1515
matrix:
16-
python-version: ["3.11", "3.12"]
16+
python-version: ["3.10", "3.11", "3.12"]
1717

1818
steps:
1919
- uses: actions/checkout@v4

merizrizal/idcloudhost/plugins/module_utils/base.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def _delete_public_ipv4(self, public_ipv4):
103103

104104
self._module.fail_json(msg='Failed to delete the VM.', **result)
105105

106-
def _get_vm(self, uuid=None, name=None, include_public_ipv4=True) -> dict:
106+
def _get_vm(self, uuid=None, name=None, include_public_ipv4=True, include_storage=False) -> dict:
107107
url, url_headers = self._init_url('user-resource/vm/list')
108108

109109
response = requests.request('GET', url, headers=url_headers, timeout=360)
@@ -112,11 +112,11 @@ def _get_vm(self, uuid=None, name=None, include_public_ipv4=True) -> dict:
112112
if isinstance(data, list) and len(data) > 0:
113113
for value in data:
114114
if value['uuid'] == uuid or value['name'] == name:
115-
return self._construct_vm_data(value, include_public_ipv4)
115+
return self._construct_vm_data(value, include_public_ipv4, include_storage)
116116

117117
return dict()
118118

119-
def _construct_vm_data(self, data, include_public_ipv4=True) -> dict:
119+
def _construct_vm_data(self, data, include_public_ipv4=True, include_storage=False) -> dict:
120120
floating_ip = dict()
121121
if include_public_ipv4:
122122
floating_ip = self._get_public_ipv4(data['uuid'], data['private_ipv4'])
@@ -146,4 +146,7 @@ def _construct_vm_data(self, data, include_public_ipv4=True) -> dict:
146146
changed=False
147147
)
148148

149+
if include_storage:
150+
vm.update(storage_list=data['storage'])
151+
149152
return vm
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
4+
# Copyright (c) 2025, Mei Rizal (@merizrizal) <meriz.rizal@gmail.com>
5+
6+
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
7+
# SPDX-License-Identifier: GPL-3.0-or-later
8+
from __future__ import absolute_import, division, print_function
9+
10+
__metaclass__ = type
11+
12+
DOCUMENTATION = r'''
13+
---
14+
module: block_storage
15+
short_description: Manage VM block storage a.k.a disks
16+
version_added: "1.0.0"
17+
18+
description: Manage block storage from a selected location.
19+
20+
options:
21+
api_key:
22+
description: API of idcloudhost.com uses tokens to allow access to the API.
23+
required: true
24+
type: str
25+
location:
26+
description: The location name of the network to which this floating IP is assigned.
27+
required: true
28+
type: str
29+
choices: [ jkt01, jkt02, jkt03, sgp01 ]
30+
name:
31+
description:
32+
- Name of a block storage that will be managed. It is usually named vdb, vdc, vdd, vde, and so on
33+
- This is required if state is set to absent
34+
default: null
35+
type: str
36+
vm_name:
37+
description: Name of the VM to which this block storage is attached.
38+
required: true
39+
type: str
40+
size:
41+
description:
42+
- Size of the block storage in GB.
43+
- This is required if state is set to present.
44+
default: null
45+
type: int
46+
state:
47+
description:
48+
- Indicates the desired block storage state.
49+
- If present, it will be created and attached to the selected VM.
50+
- If absent, it will be detached and deleted by selecting the disk name which is available on the VM.
51+
default: present
52+
type: str
53+
choices: [ present, absent ]
54+
55+
author:
56+
- Mei Rizal (@merizrizal) <meriz.rizal@gmail.com>
57+
'''
58+
59+
EXAMPLES = r'''
60+
- name: Create block storage
61+
merizrizal.idcloudhost.block_storage:
62+
api_key: "{{ your_api_key }}"
63+
location: jkt01
64+
vm_name: my_ubuntu_vm01
65+
size: 10
66+
# Since the default value of state is set to present, we may exclude the state below
67+
state: present
68+
69+
- name: Delete block storage
70+
merizrizal.idcloudhost.block_storage:
71+
api_key: "{{ your_api_key }}"
72+
location: jkt01
73+
name: vdb
74+
vm_name: my_ubuntu_vm01
75+
state: absent
76+
'''
77+
78+
RETURN = r'''
79+
uuid:
80+
description: UUID of the managed block storage.
81+
type: str
82+
returned: success
83+
name:
84+
description: Name of the managed block storage. It is usually named vdb, vdc, vdd, vde, and so on.
85+
type: str
86+
returned: success
87+
size:
88+
description: Size of the block storage in GB.
89+
type: int
90+
returned: success
91+
vm_name:
92+
description: Name of the VM to which this block storage is attached.
93+
type: str
94+
returned: success
95+
'''
96+
97+
from ansible.module_utils.basic import AnsibleModule
98+
from ansible_collections.merizrizal.idcloudhost.plugins.module_utils.base import \
99+
Base
100+
101+
requests = None
102+
103+
104+
class BlockStorage(Base):
105+
def __init__(self):
106+
super().__init__()
107+
self._endpoint_url = 'user-resource/vm/storage'
108+
109+
def main(self):
110+
global requests
111+
requests = self._ensure_requests()
112+
113+
argument_spec = dict(
114+
api_key=dict(type='str', required=True, no_log=True),
115+
location=dict(type='str', required=True, choices=['jkt01', 'jkt02', 'jkt03', 'sgp01']),
116+
name=dict(type='str', default=None),
117+
vm_name=dict(type='str', required=True),
118+
size=dict(type='int', default=None),
119+
state=dict(type='str', default='present', choices=['absent', 'present'])
120+
)
121+
122+
self._module = AnsibleModule(
123+
argument_spec=argument_spec,
124+
supports_check_mode=True,
125+
required_if=[
126+
('state', 'absent', ('name',)),
127+
('state', 'present', ('size',))
128+
]
129+
)
130+
131+
self._api_key = self._module.params['api_key']
132+
self._location = self._module.params['location']
133+
self._name = self._module.params['name']
134+
self._size = self._module.params['size']
135+
self._state = self._module.params['state']
136+
vm_name = self._module.params['vm_name']
137+
138+
vm = dict()
139+
if vm_name is not None:
140+
vm = self._get_vm(name=vm_name, include_storage=True)
141+
if 'uuid' not in vm:
142+
self._module.fail_json(msg='Failed to create the block storage. The VM name is provided, but no VM was found.')
143+
144+
if self._state == 'present':
145+
self._create_block_storage(vm)
146+
elif self._state == 'absent':
147+
self._delete_block_storage(vm)
148+
149+
def _create_block_storage(self, vm):
150+
url, url_headers = self._init_url()
151+
url_headers.update({'Content-Type': 'application/x-www-form-urlencoded'})
152+
153+
form_data = dict(
154+
uuid=vm['uuid'],
155+
size_gb=self._size
156+
)
157+
158+
response = requests.request('POST', url, headers=url_headers, data=form_data, timeout=360)
159+
data = response.json()
160+
161+
if 'uuid' not in data:
162+
result = dict(
163+
error=data
164+
)
165+
166+
self._module.fail_json(msg='Failed to create the block storage.', **result)
167+
else:
168+
result = dict(
169+
uuid=data['uuid'],
170+
name=data['name'],
171+
size=data['size'],
172+
vm_name=vm['name'],
173+
changed=True
174+
)
175+
176+
self._module.exit_json(**result)
177+
178+
def _delete_block_storage(self, vm):
179+
disk = self._get_disk_from_vm(vm)
180+
if 'uuid' not in disk:
181+
self._module.fail_json(msg='Failed to create the block storage. The block storage name is provided, but no disk was found.')
182+
183+
url, url_headers = self._init_url()
184+
url_headers.update({'Content-Type': 'application/x-www-form-urlencoded'})
185+
186+
disk_uuid = disk['uuid']
187+
form_data = dict(
188+
storage_uuid=disk_uuid,
189+
uuid=vm['uuid']
190+
)
191+
192+
response = requests.request('DELETE', url, headers=url_headers, data=form_data, timeout=360)
193+
data = response.json()
194+
195+
if 'success' not in data:
196+
result = dict(
197+
error=data
198+
)
199+
200+
self._module.fail_json(msg='Failed to delete the block storage.', **result)
201+
else:
202+
url, url_headers = self._init_url(f'storage/disks/{disk_uuid}')
203+
response = requests.request('DELETE', url, headers=url_headers, data=form_data, timeout=360)
204+
if response.status_code != 204:
205+
result = dict(
206+
error='There was a problem with the request when deleting the block storage.'
207+
)
208+
209+
self._module.fail_json(msg='Failed to delete the block storage.', **result)
210+
211+
result = dict(changed=True)
212+
213+
self._module.exit_json(**result)
214+
215+
def _get_disk_from_vm(self, vm) -> dict:
216+
for storage in vm['storage_list']:
217+
if storage['name'] == self._name:
218+
return storage
219+
220+
return dict()
221+
222+
223+
if __name__ == '__main__':
224+
BlockStorage().main()

merizrizal/idcloudhost/plugins/modules/floating_ip.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
type: str
3434
vm_name:
3535
description: The name of the VM to which this IP will be assigned.
36-
required: false
3736
default: null
3837
type: str
3938
state:
@@ -143,9 +142,9 @@ def main(self):
143142
supports_check_mode=True,
144143
)
145144

146-
self._name = self._module.params['name']
147145
self._api_key = self._module.params['api_key']
148146
self._location = self._module.params['location']
147+
self._name = self._module.params['name']
149148
self._state = self._module.params['state']
150149
vm_name = self._module.params['vm_name']
151150

@@ -195,7 +194,7 @@ def main(self):
195194
self._module.exit_json(**floating_ip)
196195

197196
def _create_floating_ip(self, vm):
198-
url, url_headers = self._init_url(self._endpoint_url)
197+
url, url_headers = self._init_url()
199198
url_headers.update({'Content-Type': 'application/json'})
200199

201200
form_data = dict(

merizrizal/idcloudhost/plugins/modules/network.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ def main(self):
108108
supports_check_mode=True,
109109
)
110110

111-
self._name = module.params['name']
112111
self._api_key = module.params['api_key']
113112
self._location = module.params['location']
113+
self._name = module.params['name']
114114
self._state = module.params['state']
115115

116116
network = self._get_existing_network(self._name)

0 commit comments

Comments
 (0)