1#!/usr/bin/python
2# Copyright: (c) 2015, Ansible, Inc.
3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4
5from __future__ import absolute_import, division, print_function
6__metaclass__ = type
7
8ANSIBLE_METADATA = {
9    'metadata_version': '1.1',
10    'status': ['preview'],
11    'supported_by': 'community'
12}
13
14DOCUMENTATION = '''
15---
16module: vca_vapp
17short_description: Manages vCloud Air vApp instances.
18description:
19  - This module will actively managed vCloud Air vApp instances.  Instances
20    can be created and deleted as well as both deployed and undeployed.
21version_added: "2.0"
22author:
23- Peter Sprygada (@privateip)
24notes:
25- VMware sold their vCloud Air service in Q2 2017.
26- VMware made significant changes to the pyvcloud interface around this time.  The C(vca_vapp) module relies on now deprecated code.
27- Mileage with C(vca_vapp) may vary as vCloud Director APIs advance.
28- A viable alternative maybe U(https://github.com/vmware/ansible-module-vcloud-director)
29requirements:
30- pyvcloud <= 18.2.2
31options:
32  vapp_name:
33    description:
34      - The name of the vCloud Air vApp instance
35    required: yes
36  template_name:
37    description:
38      - The name of the vApp template to use to create the vApp instance.  If
39        the I(state) is not `absent` then the I(template_name) value must be
40        provided.  The I(template_name) must be previously uploaded to the
41        catalog specified by I(catalog_name)
42  network_name:
43    description:
44      - The name of the network that should be attached to the virtual machine
45        in the vApp.  The virtual network specified must already be created in
46        the vCloud Air VDC.  If the I(state) is not 'absent' then the
47        I(network_name) argument must be provided.
48  network_mode:
49    description:
50      - Configures the mode of the network connection.
51    default: pool
52    choices: ['pool', 'dhcp', 'static']
53  vm_name:
54    description:
55      - The name of the virtual machine instance in the vApp to manage.
56  vm_cpus:
57    description:
58      - The number of vCPUs to configure for the VM in the vApp.   If the
59        I(vm_name) argument is provided, then this becomes a per VM setting
60        otherwise it is applied to all VMs in the vApp.
61  vm_memory:
62    description:
63      - The amount of memory in MB to allocate to VMs in the vApp.  If the
64        I(vm_name) argument is provided, then this becomes a per VM setting
65        otherwise it is applied to all VMs in the vApp.
66  operation:
67    description:
68      - Specifies an operation to be performed on the vApp.
69    default: noop
70    choices: ['noop', 'poweron', 'poweroff', 'suspend', 'shutdown', 'reboot', 'reset']
71  state:
72    description:
73      - Configures the state of the vApp.
74    default: present
75    choices: ['present', 'absent', 'deployed', 'undeployed']
76  username:
77    description:
78      - The vCloud Air username to use during authentication
79  password:
80    description:
81      - The vCloud Air password to use during authentication
82  org:
83    description:
84      - The org to login to for creating vapp, mostly set when the service_type is vdc.
85  instance_id:
86    description:
87      - The instance id in a vchs environment to be used for creating the vapp
88  host:
89    description:
90      - The authentication host to be used when service type  is vcd.
91  api_version:
92    description:
93      - The api version to be used with the vca
94    default: "5.7"
95  service_type:
96    description:
97      - The type of service we are authenticating against
98    default: vca
99    choices: [ "vca", "vchs", "vcd" ]
100  vdc_name:
101    description:
102      - The name of the virtual data center (VDC) where the vm should be created or contains the vAPP.
103extends_documentation_fragment: vca
104'''
105
106EXAMPLES = '''
107- name: Creates a new vApp in a VCA instance
108  vca_vapp:
109    vapp_name: tower
110    state: present
111    template_name: 'Ubuntu Server 12.04 LTS (amd64 20150127)'
112    vdc_name: VDC1
113    instance_id: '<your instance id here>'
114    username: '<your username here>'
115    password: '<your password here>'
116  delegate_to: localhost
117'''
118
119from ansible.module_utils.vca import VcaAnsibleModule, VcaError
120
121DEFAULT_VAPP_OPERATION = 'noop'
122
123VAPP_STATUS = {
124    'Powered off': 'poweroff',
125    'Powered on': 'poweron',
126    'Suspended': 'suspend'
127}
128
129VAPP_STATES = ['present', 'absent', 'deployed', 'undeployed']
130VAPP_OPERATIONS = ['poweron', 'poweroff', 'suspend', 'shutdown',
131                   'reboot', 'reset', 'noop']
132
133
134def get_instance(module):
135    vapp_name = module.params['vapp_name']
136    inst = dict(vapp_name=vapp_name, state='absent')
137    try:
138        vapp = module.get_vapp(vapp_name)
139        if vapp:
140            status = module.vca.get_status(vapp.me.get_status())
141            inst['status'] = VAPP_STATUS.get(status, 'unknown')
142            inst['state'] = 'deployed' if vapp.me.deployed else 'undeployed'
143        return inst
144    except VcaError:
145        return inst
146
147
148def create(module):
149    vdc_name = module.params['vdc_name']
150    vapp_name = module.params['vapp_name']
151    template_name = module.params['template_name']
152    catalog_name = module.params['catalog_name']
153    network_name = module.params['network_name']
154    network_mode = module.params['network_mode']
155    vm_name = module.params['vm_name']
156    vm_cpus = module.params['vm_cpus']
157    vm_memory = module.params['vm_memory']
158    deploy = module.params['state'] == 'deploy'
159    poweron = module.params['operation'] == 'poweron'
160
161    task = module.vca.create_vapp(vdc_name, vapp_name, template_name,
162                                  catalog_name, network_name, 'bridged',
163                                  vm_name, vm_cpus, vm_memory, deploy, poweron)
164
165    if task is False:
166        module.fail('Failed to create vapp: %s' % vapp_name)
167
168    module.vca.block_until_completed(task)
169
170    # Connect the network to the Vapp/VM and return assigned IP
171    if network_name is not None:
172        vm_ip = connect_to_network(module, vdc_name, vapp_name, network_name, network_mode)
173        return vm_ip
174
175
176def delete(module):
177    vdc_name = module.params['vdc_name']
178    vapp_name = module.params['vapp_name']
179    module.vca.delete_vapp(vdc_name, vapp_name)
180
181
182def do_operation(module):
183    vapp_name = module.params['vapp_name']
184    operation = module.params['operation']
185
186    vm_name = module.params.get('vm_name')
187    vm = None
188    if vm_name:
189        vm = module.get_vm(vapp_name, vm_name)
190
191    if operation == 'poweron':
192        operation = 'powerOn'
193    elif operation == 'poweroff':
194        operation = 'powerOff'
195
196    cmd = 'power:%s' % operation
197    module.get_vapp(vapp_name).execute(cmd, 'post', targetVM=vm)
198
199
200def set_state(module):
201    state = module.params['state']
202    vapp = module.get_vapp(module.params['vapp_name'])
203    if state == 'deployed':
204        action = module.params['operation'] == 'poweron'
205        if not vapp.deploy(action):
206            module.fail('unable to deploy vapp')
207    elif state == 'undeployed':
208        action = module.params['operation']
209        if action == 'poweroff':
210            action = 'powerOff'
211        elif action != 'suspend':
212            action = None
213        if not vapp.undeploy(action):
214            module.fail('unable to undeploy vapp')
215
216
217def connect_to_network(module, vdc_name, vapp_name, network_name, network_mode):
218    nets = filter(lambda n: n.name == network_name, module.vca.get_networks(vdc_name))
219    if len(nets) != 1:
220        module.fail_json("Unable to find network %s " % network_name)
221
222    the_vdc = module.vca.get_vdc(vdc_name)
223    the_vapp = module.vca.get_vapp(the_vdc, vapp_name)
224
225    if the_vapp and the_vapp.name != vapp_name:
226        module.fail_json(msg="Failed to find vapp named %s" % the_vapp.name)
227
228    # Connect vApp
229    task = the_vapp.connect_to_network(nets[0].name, nets[0].href)
230    result = module.vca.block_until_completed(task)
231
232    if result is None:
233        module.fail_json(msg="Failed to complete task")
234
235    # Connect VM
236    ip_allocation_mode = None
237    if network_mode == 'pool':
238        ip_allocation_mode = 'POOL'
239    elif network_mode == 'dhcp':
240        ip_allocation_mode = 'DHCP'
241
242    task = the_vapp.connect_vms(nets[0].name, connection_index=0, ip_allocation_mode=ip_allocation_mode)
243    if result is None:
244        module.fail_json(msg="Failed to complete task")
245
246    result = module.vca.block_until_completed(task)
247    if result is None:
248        module.fail_json(msg="Failed to complete task")
249
250    # Update VApp info and get VM IP
251    the_vapp = module.vca.get_vapp(the_vdc, vapp_name)
252    if the_vapp is None:
253        module.fail_json(msg="Failed to get vapp named %s" % vapp_name)
254
255    return get_vm_details(module)
256
257
258def get_vm_details(module):
259    vdc_name = module.params['vdc_name']
260    vapp_name = module.params['vapp_name']
261    vm_name = module.params['vm_name']
262    the_vdc = module.vca.get_vdc(vdc_name)
263    the_vapp = module.vca.get_vapp(the_vdc, vapp_name)
264    if the_vapp and the_vapp.name != vapp_name:
265        module.fail_json(msg="Failed to find vapp named %s" % the_vapp.name)
266    the_vm_details = dict()
267
268    for vm in the_vapp.me.Children.Vm:
269        sections = vm.get_Section()
270
271        customization_section = (
272            filter(lambda section:
273                   section.__class__.__name__ ==
274                   "GuestCustomizationSectionType",
275                   sections)[0])
276        if customization_section.get_AdminPasswordEnabled():
277            the_vm_details["vm_admin_password"] = customization_section.get_AdminPassword()
278
279        virtual_hardware_section = (
280            filter(lambda section:
281                   section.__class__.__name__ ==
282                   "VirtualHardwareSection_Type",
283                   sections)[0])
284        items = virtual_hardware_section.get_Item()
285        ips = []
286        _url = '{http://www.vmware.com/vcloud/v1.5}ipAddress'
287        for item in items:
288            if item.Connection:
289                for c in item.Connection:
290                    if c.anyAttributes_.get(
291                            _url):
292                        ips.append(c.anyAttributes_.get(
293                            _url))
294    if len(ips) > 0:
295        the_vm_details["vm_ip"] = ips[0]
296
297    return the_vm_details
298
299
300def main():
301    argument_spec = dict(
302        vapp_name=dict(required=True),
303        vdc_name=dict(required=True),
304        template_name=dict(),
305        catalog_name=dict(default='Public Catalog'),
306        network_name=dict(),
307        network_mode=dict(default='pool', choices=['dhcp', 'static', 'pool']),
308        vm_name=dict(),
309        vm_cpus=dict(),
310        vm_memory=dict(),
311        operation=dict(default=DEFAULT_VAPP_OPERATION, choices=VAPP_OPERATIONS),
312        state=dict(default='present', choices=VAPP_STATES)
313    )
314
315    module = VcaAnsibleModule(argument_spec=argument_spec,
316                              supports_check_mode=True)
317
318    state = module.params['state']
319    operation = module.params['operation']
320
321    instance = get_instance(module)
322
323    result = dict(changed=False)
324
325    if instance and state == 'absent':
326        if not module.check_mode:
327            delete(module)
328        result['changed'] = True
329
330    elif state != 'absent':
331        if instance['state'] == 'absent':
332            if not module.check_mode:
333                result['ansible_facts'] = create(module)
334            result['changed'] = True
335
336        elif instance['state'] != state and state != 'present':
337            if not module.check_mode:
338                set_state(module)
339            result['changed'] = True
340
341        if operation != instance.get('status') and operation != 'noop':
342            if not module.check_mode:
343                do_operation(module)
344            result['changed'] = True
345        result['ansible_facts'] = get_vm_details(module)
346
347    return module.exit(**result)
348
349
350if __name__ == '__main__':
351    main()
352