1#!/usr/local/bin/python3.8
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
8
9DOCUMENTATION = r'''
10---
11module: vca_vapp
12deprecated:
13  removed_at_date: '2022-06-01'
14  why: Module depends upon deprecated version of Pyvcloud library.
15  alternative: Use U(https://github.com/vmware/ansible-module-vcloud-director) instead.
16short_description: Manages vCloud Air vApp instances.
17description:
18  - This module will actively managed vCloud Air vApp instances.  Instances
19    can be created and deleted as well as both deployed and undeployed.
20author:
21- Peter Sprygada (@privateip)
22notes:
23- VMware sold their vCloud Air service in Q2 2017.
24- VMware made significant changes to the pyvcloud interface around this time.  The C(vca_vapp) module relies on now deprecated code.
25- Mileage with C(vca_vapp) may vary as vCloud Director APIs advance.
26- A viable alternative maybe U(https://github.com/vmware/ansible-module-vcloud-director)
27requirements:
28- pyvcloud <= 18.2.2
29options:
30  vapp_name:
31    description:
32      - The name of the vCloud Air vApp instance
33    required: true
34  template_name:
35    description:
36      - The name of the vApp template to use to create the vApp instance.  If
37        the I(state) is not `absent` then the I(template_name) value must be
38        provided.  The I(template_name) must be previously uploaded to the
39        catalog specified by I(catalog_name)
40  network_name:
41    description:
42      - The name of the network that should be attached to the virtual machine
43        in the vApp.  The virtual network specified must already be created in
44        the vCloud Air VDC.  If the I(state) is not 'absent' then the
45        I(network_name) argument must be provided.
46  network_mode:
47    description:
48      - Configures the mode of the network connection.
49    default: pool
50    choices: ['pool', 'dhcp', 'static']
51  vm_name:
52    description:
53      - The name of the virtual machine instance in the vApp to manage.
54  vm_cpus:
55    description:
56      - The number of vCPUs to configure for the VM in the vApp.   If the
57        I(vm_name) argument is provided, then this becomes a per VM setting
58        otherwise it is applied to all VMs in the vApp.
59  vm_memory:
60    description:
61      - The amount of memory in MB to allocate to VMs in the vApp.  If the
62        I(vm_name) argument is provided, then this becomes a per VM setting
63        otherwise it is applied to all VMs in the vApp.
64  operation:
65    description:
66      - Specifies an operation to be performed on the vApp.
67    default: noop
68    choices: ['noop', 'poweron', 'poweroff', 'suspend', 'shutdown', 'reboot', 'reset']
69  state:
70    description:
71      - Configures the state of the vApp.
72    default: present
73    choices: ['present', 'absent', 'deployed', 'undeployed']
74  username:
75    description:
76      - The vCloud Air username to use during authentication
77  password:
78    description:
79      - The vCloud Air password to use during authentication
80  org:
81    description:
82      - The org to login to for creating vapp, mostly set when the service_type is vdc.
83  instance_id:
84    description:
85      - The instance id in a vchs environment to be used for creating the vapp
86  host:
87    description:
88      - The authentication host to be used when service type  is vcd.
89  api_version:
90    description:
91      - The api version to be used with the vca
92    default: "5.7"
93  service_type:
94    description:
95      - The type of service we are authenticating against
96    default: vca
97    choices: [ "vca", "vchs", "vcd" ]
98  vdc_name:
99    description:
100      - The name of the virtual data center (VDC) where the vm should be created or contains the vAPP.
101extends_documentation_fragment:
102- community.vmware.vca
103
104'''
105
106EXAMPLES = r'''
107- name: Creates a new vApp in a VCA instance
108  community.vmware.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_collections.community.vmware.plugins.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    the_vdc = module.vca.get_vdc(vdc_name)
262    the_vapp = module.vca.get_vapp(the_vdc, vapp_name)
263    if the_vapp and the_vapp.name != vapp_name:
264        module.fail_json(msg="Failed to find vapp named %s" % the_vapp.name)
265    the_vm_details = dict()
266
267    for vm in the_vapp.me.Children.Vm:
268        sections = vm.get_Section()
269
270        customization_section = (
271            filter(lambda section:
272                   section.__class__.__name__ == "GuestCustomizationSectionType",
273                   sections)[0])
274        if customization_section.get_AdminPasswordEnabled():
275            the_vm_details["vm_admin_password"] = customization_section.get_AdminPassword()
276
277        virtual_hardware_section = (
278            filter(lambda section:
279                   section.__class__.__name__ == "VirtualHardwareSection_Type",
280                   sections)[0])
281        items = virtual_hardware_section.get_Item()
282        ips = []
283        _url = '{http://www.vmware.com/vcloud/v1.5}ipAddress'
284        for item in items:
285            if item.Connection:
286                for c in item.Connection:
287                    if c.anyAttributes_.get(
288                            _url):
289                        ips.append(c.anyAttributes_.get(
290                            _url))
291    if len(ips) > 0:
292        the_vm_details["vm_ip"] = ips[0]
293
294    return the_vm_details
295
296
297def main():
298    argument_spec = dict(
299        vapp_name=dict(required=True),
300        vdc_name=dict(required=True),
301        template_name=dict(),
302        catalog_name=dict(default='Public Catalog'),
303        network_name=dict(),
304        network_mode=dict(default='pool', choices=['dhcp', 'static', 'pool']),
305        vm_name=dict(),
306        vm_cpus=dict(),
307        vm_memory=dict(),
308        operation=dict(default=DEFAULT_VAPP_OPERATION, choices=VAPP_OPERATIONS),
309        state=dict(default='present', choices=VAPP_STATES)
310    )
311
312    module = VcaAnsibleModule(argument_spec=argument_spec,
313                              supports_check_mode=True)
314
315    module.deprecate(
316        msg="The 'vca_fw' module is deprecated, Please use https://github.com/vmware/ansible-module-vcloud-director instead",
317        date="2022-06-01",
318        collection_name="community.vmware"
319    )
320
321    state = module.params['state']
322    operation = module.params['operation']
323
324    instance = get_instance(module)
325
326    result = dict(changed=False)
327
328    if instance and state == 'absent':
329        if not module.check_mode:
330            delete(module)
331        result['changed'] = True
332
333    elif state != 'absent':
334        if instance['state'] == 'absent':
335            if not module.check_mode:
336                result['ansible_facts'] = create(module)
337            result['changed'] = True
338
339        elif instance['state'] != state and state != 'present':
340            if not module.check_mode:
341                set_state(module)
342            result['changed'] = True
343
344        if operation != instance.get('status') and operation != 'noop':
345            if not module.check_mode:
346                do_operation(module)
347            result['changed'] = True
348        result['ansible_facts'] = get_vm_details(module)
349
350    return module.exit(**result)
351
352
353if __name__ == '__main__':
354    main()
355