1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3# Copyright: Ansible Project
4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
6from __future__ import absolute_import, division, print_function
7__metaclass__ = type
8
9
10DOCUMENTATION = '''
11---
12module: profitbricks_volume_attachments
13short_description: Attach or detach a volume.
14description:
15     - Allows you to attach or detach a volume from a ProfitBricks server. This module has a dependency on profitbricks >= 1.0.0
16options:
17  datacenter:
18    description:
19      - The datacenter in which to operate.
20    type: str
21  server:
22    description:
23      - The name of the server you wish to detach or attach the volume.
24    type: str
25  volume:
26    description:
27      - The volume name or ID.
28    type: str
29  subscription_user:
30    description:
31      - The ProfitBricks username. Overrides the PB_SUBSCRIPTION_ID environment variable.
32    type: str
33    required: false
34  subscription_password:
35    description:
36      - THe ProfitBricks password. Overrides the PB_PASSWORD environment variable.
37    type: str
38    required: false
39  wait:
40    description:
41      - wait for the operation to complete before returning
42    required: false
43    default: "yes"
44    type: bool
45  wait_timeout:
46    description:
47      - how long before wait gives up, in seconds
48    type: int
49    default: 600
50  state:
51    description:
52      - Indicate desired state of the resource
53      - "The available choices are: C(present), C(absent)."
54    type: str
55    required: false
56    default: 'present'
57
58requirements: [ "profitbricks" ]
59author: Matt Baldwin (@baldwinSPC) <baldwin@stackpointcloud.com>
60'''
61
62EXAMPLES = '''
63- name: Attach a volume
64  community.general.profitbricks_volume_attachments:
65    datacenter: Tardis One
66    server: node002
67    volume: vol01
68    wait_timeout: 500
69    state: present
70
71- name: Detach a volume
72  community.general.profitbricks_volume_attachments:
73    datacenter: Tardis One
74    server: node002
75    volume: vol01
76    wait_timeout: 500
77    state: absent
78'''
79
80import re
81import time
82
83HAS_PB_SDK = True
84try:
85    from profitbricks.client import ProfitBricksService
86except ImportError:
87    HAS_PB_SDK = False
88
89from ansible.module_utils.basic import AnsibleModule
90
91
92uuid_match = re.compile(
93    r'[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}', re.I)
94
95
96def _wait_for_completion(profitbricks, promise, wait_timeout, msg):
97    if not promise:
98        return
99    wait_timeout = time.time() + wait_timeout
100    while wait_timeout > time.time():
101        time.sleep(5)
102        operation_result = profitbricks.get_request(
103            request_id=promise['requestId'],
104            status=True)
105
106        if operation_result['metadata']['status'] == "DONE":
107            return
108        elif operation_result['metadata']['status'] == "FAILED":
109            raise Exception(
110                'Request failed to complete ' + msg + ' "' + str(
111                    promise['requestId']) + '" to complete.')
112
113    raise Exception(
114        'Timed out waiting for async operation ' + msg + ' "' + str(
115            promise['requestId']
116        ) + '" to complete.')
117
118
119def attach_volume(module, profitbricks):
120    """
121    Attaches a volume.
122
123    This will attach a volume to the server.
124
125    module : AnsibleModule object
126    profitbricks: authenticated profitbricks object.
127
128    Returns:
129        True if the volume was attached, false otherwise
130    """
131    datacenter = module.params.get('datacenter')
132    server = module.params.get('server')
133    volume = module.params.get('volume')
134
135    # Locate UUID for Datacenter
136    if not (uuid_match.match(datacenter)):
137        datacenter_list = profitbricks.list_datacenters()
138        for d in datacenter_list['items']:
139            dc = profitbricks.get_datacenter(d['id'])
140            if datacenter == dc['properties']['name']:
141                datacenter = d['id']
142                break
143
144    # Locate UUID for Server
145    if not (uuid_match.match(server)):
146        server_list = profitbricks.list_servers(datacenter)
147        for s in server_list['items']:
148            if server == s['properties']['name']:
149                server = s['id']
150                break
151
152    # Locate UUID for Volume
153    if not (uuid_match.match(volume)):
154        volume_list = profitbricks.list_volumes(datacenter)
155        for v in volume_list['items']:
156            if volume == v['properties']['name']:
157                volume = v['id']
158                break
159
160    return profitbricks.attach_volume(datacenter, server, volume)
161
162
163def detach_volume(module, profitbricks):
164    """
165    Detaches a volume.
166
167    This will remove a volume from the server.
168
169    module : AnsibleModule object
170    profitbricks: authenticated profitbricks object.
171
172    Returns:
173        True if the volume was detached, false otherwise
174    """
175    datacenter = module.params.get('datacenter')
176    server = module.params.get('server')
177    volume = module.params.get('volume')
178
179    # Locate UUID for Datacenter
180    if not (uuid_match.match(datacenter)):
181        datacenter_list = profitbricks.list_datacenters()
182        for d in datacenter_list['items']:
183            dc = profitbricks.get_datacenter(d['id'])
184            if datacenter == dc['properties']['name']:
185                datacenter = d['id']
186                break
187
188    # Locate UUID for Server
189    if not (uuid_match.match(server)):
190        server_list = profitbricks.list_servers(datacenter)
191        for s in server_list['items']:
192            if server == s['properties']['name']:
193                server = s['id']
194                break
195
196    # Locate UUID for Volume
197    if not (uuid_match.match(volume)):
198        volume_list = profitbricks.list_volumes(datacenter)
199        for v in volume_list['items']:
200            if volume == v['properties']['name']:
201                volume = v['id']
202                break
203
204    return profitbricks.detach_volume(datacenter, server, volume)
205
206
207def main():
208    module = AnsibleModule(
209        argument_spec=dict(
210            datacenter=dict(),
211            server=dict(),
212            volume=dict(),
213            subscription_user=dict(),
214            subscription_password=dict(no_log=True),
215            wait=dict(type='bool', default=True),
216            wait_timeout=dict(type='int', default=600),
217            state=dict(default='present'),
218        )
219    )
220
221    if not HAS_PB_SDK:
222        module.fail_json(msg='profitbricks required for this module')
223
224    if not module.params.get('subscription_user'):
225        module.fail_json(msg='subscription_user parameter is required')
226    if not module.params.get('subscription_password'):
227        module.fail_json(msg='subscription_password parameter is required')
228    if not module.params.get('datacenter'):
229        module.fail_json(msg='datacenter parameter is required')
230    if not module.params.get('server'):
231        module.fail_json(msg='server parameter is required')
232    if not module.params.get('volume'):
233        module.fail_json(msg='volume parameter is required')
234
235    subscription_user = module.params.get('subscription_user')
236    subscription_password = module.params.get('subscription_password')
237
238    profitbricks = ProfitBricksService(
239        username=subscription_user,
240        password=subscription_password)
241
242    state = module.params.get('state')
243
244    if state == 'absent':
245        try:
246            (changed) = detach_volume(module, profitbricks)
247            module.exit_json(changed=changed)
248        except Exception as e:
249            module.fail_json(msg='failed to set volume_attach state: %s' % str(e))
250    elif state == 'present':
251        try:
252            attach_volume(module, profitbricks)
253            module.exit_json()
254        except Exception as e:
255            module.fail_json(msg='failed to set volume_attach state: %s' % str(e))
256
257
258if __name__ == '__main__':
259    main()
260