1#!/usr/bin/python 2# Copyright 2013 Google 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 7__metaclass__ = type 8 9ANSIBLE_METADATA = {'metadata_version': '1.1', 10 'status': ['deprecated'], 11 'supported_by': 'community'} 12 13DOCUMENTATION = ''' 14--- 15module: gce 16version_added: "1.4" 17short_description: create or terminate GCE instances 18description: 19 - Creates or terminates Google Compute Engine (GCE) instances. See 20 U(https://cloud.google.com/compute) for an overview. 21 Full install/configuration instructions for the gce* modules can 22 be found in the comments of ansible/test/gce_tests.py. 23deprecated: 24 removed_in: "2.12" 25 why: Updated modules released with increased functionality 26 alternative: Use M(gcp_compute_instance) instead. 27options: 28 image: 29 description: 30 - image string to use for the instance (default will follow latest 31 stable debian image) 32 default: "debian-8" 33 image_family: 34 description: 35 - image family from which to select the image. The most recent 36 non-deprecated image in the family will be used. 37 version_added: "2.4" 38 external_projects: 39 description: 40 - A list of other projects (accessible with the provisioning credentials) 41 to be searched for the image. 42 version_added: "2.4" 43 instance_names: 44 description: 45 - a comma-separated list of instance names to create or destroy 46 machine_type: 47 description: 48 - machine type to use for the instance, use 'n1-standard-1' by default 49 default: "n1-standard-1" 50 metadata: 51 description: 52 - a hash/dictionary of custom data for the instance; 53 '{"key":"value", ...}' 54 service_account_email: 55 version_added: "1.5.1" 56 description: 57 - service account email 58 service_account_permissions: 59 version_added: "2.0" 60 description: 61 - service account permissions (see 62 U(https://cloud.google.com/sdk/gcloud/reference/compute/instances/create), 63 --scopes section for detailed information) 64 choices: [ 65 "bigquery", "cloud-platform", "compute-ro", "compute-rw", 66 "useraccounts-ro", "useraccounts-rw", "datastore", "logging-write", 67 "monitoring", "sql-admin", "storage-full", "storage-ro", 68 "storage-rw", "taskqueue", "userinfo-email" 69 ] 70 pem_file: 71 version_added: "1.5.1" 72 description: 73 - path to the pem file associated with the service account email 74 This option is deprecated. Use 'credentials_file'. 75 credentials_file: 76 version_added: "2.1.0" 77 description: 78 - path to the JSON file associated with the service account email 79 project_id: 80 version_added: "1.5.1" 81 description: 82 - your GCE project ID 83 name: 84 description: 85 - either a name of a single instance or when used with 'num_instances', 86 the base name of a cluster of nodes 87 aliases: ['base_name'] 88 num_instances: 89 description: 90 - can be used with 'name', specifies 91 the number of nodes to provision using 'name' 92 as a base name 93 version_added: "2.3" 94 network: 95 description: 96 - name of the network, 'default' will be used if not specified 97 default: "default" 98 subnetwork: 99 description: 100 - name of the subnetwork in which the instance should be created 101 version_added: "2.2" 102 persistent_boot_disk: 103 description: 104 - if set, create the instance with a persistent boot disk 105 type: bool 106 default: 'no' 107 disks: 108 description: 109 - a list of persistent disks to attach to the instance; a string value 110 gives the name of the disk; alternatively, a dictionary value can 111 define 'name' and 'mode' ('READ_ONLY' or 'READ_WRITE'). The first entry 112 will be the boot disk (which must be READ_WRITE). 113 version_added: "1.7" 114 state: 115 description: 116 - desired state of the resource 117 default: "present" 118 choices: ["active", "present", "absent", "deleted", "started", "stopped", "terminated"] 119 tags: 120 description: 121 - a comma-separated list of tags to associate with the instance 122 zone: 123 description: 124 - the GCE zone to use. The list of available zones is at U(https://cloud.google.com/compute/docs/regions-zones/regions-zones#available). 125 required: true 126 default: "us-central1-a" 127 ip_forward: 128 version_added: "1.9" 129 description: 130 - set to C(yes) if the instance can forward ip packets (useful for 131 gateways) 132 type: bool 133 default: 'no' 134 external_ip: 135 version_added: "1.9" 136 description: 137 - type of external ip, ephemeral by default; alternatively, a fixed gce ip or ip name can be given. Specify 'none' if no external ip is desired. 138 default: "ephemeral" 139 disk_auto_delete: 140 version_added: "1.9" 141 description: 142 - if set boot disk will be removed after instance destruction 143 type: bool 144 default: 'yes' 145 preemptible: 146 version_added: "2.1" 147 description: 148 - if set to C(yes), instances will be preemptible and time-limited. 149 (requires libcloud >= 0.20.0) 150 type: bool 151 default: 'no' 152 disk_size: 153 description: 154 - The size of the boot disk created for this instance (in GB) 155 default: 10 156 version_added: "2.3" 157 158requirements: 159 - "python >= 2.6" 160 - "apache-libcloud >= 0.13.3, >= 0.17.0 if using JSON credentials, 161 >= 0.20.0 if using preemptible option" 162notes: 163 - Either I(instance_names) or I(name) is required. 164 - JSON credentials strongly preferred. 165author: 166 - Eric Johnson (@erjohnso) <erjohnso@google.com> 167 - Tom Melendez (@supertom) <supertom@google.com> 168''' 169 170EXAMPLES = ''' 171# Basic provisioning example. Create a single Debian 8 instance in the 172# us-central1-a Zone of the n1-standard-1 machine type. 173# Create multiple instances by specifying multiple names, separated by 174# commas in the instance_names field 175# (e.g. my-test-instance1,my-test-instance2) 176 - gce: 177 instance_names: my-test-instance1 178 zone: us-central1-a 179 machine_type: n1-standard-1 180 image: debian-8 181 state: present 182 service_account_email: "your-sa@your-project-name.iam.gserviceaccount.com" 183 credentials_file: "/path/to/your-key.json" 184 project_id: "your-project-name" 185 disk_size: 32 186 187# Create a single instance of an image from the "my-base-image" image family 188# in the us-central1-a Zone of the n1-standard-1 machine type. 189# This image family is in the "my-other-project" GCP project. 190 - gce: 191 instance_names: my-test-instance1 192 zone: us-central1-a 193 machine_type: n1-standard-1 194 image_family: my-base-image 195 external_projects: 196 - my-other-project 197 state: present 198 service_account_email: "your-sa@your-project-name.iam.gserviceaccount.com" 199 credentials_file: "/path/to/your-key.json" 200 project_id: "your-project-name" 201 disk_size: 32 202 203# Create a single Debian 8 instance in the us-central1-a Zone 204# Use existing disks, custom network/subnetwork, set service account permissions 205# add tags and metadata. 206 - gce: 207 instance_names: my-test-instance 208 zone: us-central1-a 209 machine_type: n1-standard-1 210 state: present 211 metadata: '{"db":"postgres", "group":"qa", "id":500}' 212 tags: 213 - http-server 214 - my-other-tag 215 disks: 216 - name: disk-2 217 mode: READ_WRITE 218 - name: disk-3 219 mode: READ_ONLY 220 disk_auto_delete: false 221 network: foobar-network 222 subnetwork: foobar-subnetwork-1 223 preemptible: true 224 ip_forward: true 225 service_account_permissions: 226 - storage-full 227 - taskqueue 228 - bigquery 229 - https://www.googleapis.com/auth/ndev.clouddns.readwrite 230 service_account_email: "your-sa@your-project-name.iam.gserviceaccount.com" 231 credentials_file: "/path/to/your-key.json" 232 project_id: "your-project-name" 233 234--- 235# Example Playbook 236- name: Compute Engine Instance Examples 237 hosts: localhost 238 vars: 239 service_account_email: "your-sa@your-project-name.iam.gserviceaccount.com" 240 credentials_file: "/path/to/your-key.json" 241 project_id: "your-project-name" 242 tasks: 243 - name: create multiple instances 244 # Basic provisioning example. Create multiple Debian 8 instances in the 245 # us-central1-a Zone of n1-standard-1 machine type. 246 gce: 247 instance_names: test1,test2,test3 248 zone: us-central1-a 249 machine_type: n1-standard-1 250 image: debian-8 251 state: present 252 service_account_email: "{{ service_account_email }}" 253 credentials_file: "{{ credentials_file }}" 254 project_id: "{{ project_id }}" 255 metadata : '{ "startup-script" : "apt-get update" }' 256 register: gce 257 258 - name: Save host data 259 add_host: 260 hostname: "{{ item.public_ip }}" 261 groupname: gce_instances_ips 262 with_items: "{{ gce.instance_data }}" 263 264 - name: Wait for SSH for instances 265 wait_for: 266 delay: 1 267 host: "{{ item.public_ip }}" 268 port: 22 269 state: started 270 timeout: 30 271 with_items: "{{ gce.instance_data }}" 272 273 - name: Configure Hosts 274 hosts: gce_instances_ips 275 become: yes 276 become_method: sudo 277 roles: 278 - my-role-one 279 - my-role-two 280 tags: 281 - config 282 283 - name: delete test-instances 284 # Basic termination of instance. 285 gce: 286 service_account_email: "{{ service_account_email }}" 287 credentials_file: "{{ credentials_file }}" 288 project_id: "{{ project_id }}" 289 instance_names: "{{ gce.instance_names }}" 290 zone: us-central1-a 291 state: absent 292 tags: 293 - delete 294''' 295 296import socket 297import logging 298 299try: 300 from ast import literal_eval 301 302 HAS_PYTHON26 = True 303except ImportError: 304 HAS_PYTHON26 = False 305 306try: 307 import libcloud 308 from libcloud.compute.types import Provider 309 from libcloud.compute.providers import get_driver 310 from libcloud.common.google import GoogleBaseError, QuotaExceededError, \ 311 ResourceExistsError, ResourceInUseError, ResourceNotFoundError 312 from libcloud.compute.drivers.gce import GCEAddress 313 314 _ = Provider.GCE 315 HAS_LIBCLOUD = True 316except ImportError: 317 HAS_LIBCLOUD = False 318 319from ansible.module_utils.basic import AnsibleModule 320from ansible.module_utils.gce import gce_connect, unexpected_error_msg 321from ansible.module_utils.gcp import get_valid_location 322from ansible.module_utils.six.moves import reduce 323 324 325def get_instance_info(inst): 326 """Retrieves instance information from an instance object and returns it 327 as a dictionary. 328 329 """ 330 metadata = {} 331 if 'metadata' in inst.extra and 'items' in inst.extra['metadata']: 332 for md in inst.extra['metadata']['items']: 333 metadata[md['key']] = md['value'] 334 335 try: 336 netname = inst.extra['networkInterfaces'][0]['network'].split('/')[-1] 337 except Exception: 338 netname = None 339 try: 340 subnetname = inst.extra['networkInterfaces'][0]['subnetwork'].split('/')[-1] 341 except Exception: 342 subnetname = None 343 if 'disks' in inst.extra: 344 disk_names = [disk_info['source'].split('/')[-1] 345 for disk_info 346 in sorted(inst.extra['disks'], 347 key=lambda disk_info: disk_info['index'])] 348 else: 349 disk_names = [] 350 351 if len(inst.public_ips) == 0: 352 public_ip = None 353 else: 354 public_ip = inst.public_ips[0] 355 356 return ({ 357 'image': inst.image is not None and inst.image.split('/')[-1] or None, 358 'disks': disk_names, 359 'machine_type': inst.size, 360 'metadata': metadata, 361 'name': inst.name, 362 'network': netname, 363 'subnetwork': subnetname, 364 'private_ip': inst.private_ips[0], 365 'public_ip': public_ip, 366 'status': ('status' in inst.extra) and inst.extra['status'] or None, 367 'tags': ('tags' in inst.extra) and inst.extra['tags'] or [], 368 'zone': ('zone' in inst.extra) and inst.extra['zone'].name or None, 369 }) 370 371 372def create_instances(module, gce, instance_names, number, lc_zone): 373 """Creates new instances. Attributes other than instance_names are picked 374 up from 'module' 375 376 module : AnsibleModule object 377 gce: authenticated GCE libcloud driver 378 instance_names: python list of instance names to create 379 number: number of instances to create 380 lc_zone: GCEZone object 381 382 Returns: 383 A list of dictionaries with instance information 384 about the instances that were launched. 385 386 """ 387 image = module.params.get('image') 388 image_family = module.params.get('image_family') 389 external_projects = module.params.get('external_projects') 390 machine_type = module.params.get('machine_type') 391 metadata = module.params.get('metadata') 392 network = module.params.get('network') 393 subnetwork = module.params.get('subnetwork') 394 persistent_boot_disk = module.params.get('persistent_boot_disk') 395 disks = module.params.get('disks') 396 tags = module.params.get('tags') 397 ip_forward = module.params.get('ip_forward') 398 external_ip = module.params.get('external_ip') 399 disk_auto_delete = module.params.get('disk_auto_delete') 400 preemptible = module.params.get('preemptible') 401 disk_size = module.params.get('disk_size') 402 service_account_permissions = module.params.get('service_account_permissions') 403 404 if external_ip == "none": 405 instance_external_ip = None 406 elif external_ip != "ephemeral": 407 instance_external_ip = external_ip 408 try: 409 # check if instance_external_ip is an ip or a name 410 try: 411 socket.inet_aton(instance_external_ip) 412 instance_external_ip = GCEAddress(id='unknown', name='unknown', address=instance_external_ip, region='unknown', driver=gce) 413 except socket.error: 414 instance_external_ip = gce.ex_get_address(instance_external_ip) 415 except GoogleBaseError as e: 416 module.fail_json(msg='Unexpected error attempting to get a static ip %s, error: %s' % (external_ip, e.value)) 417 else: 418 instance_external_ip = external_ip 419 420 new_instances = [] 421 changed = False 422 423 lc_disks = [] 424 disk_modes = [] 425 for i, disk in enumerate(disks or []): 426 if isinstance(disk, dict): 427 lc_disks.append(gce.ex_get_volume(disk['name'], lc_zone)) 428 disk_modes.append(disk['mode']) 429 else: 430 lc_disks.append(gce.ex_get_volume(disk, lc_zone)) 431 # boot disk is implicitly READ_WRITE 432 disk_modes.append('READ_ONLY' if i > 0 else 'READ_WRITE') 433 lc_network = gce.ex_get_network(network) 434 lc_machine_type = gce.ex_get_size(machine_type, lc_zone) 435 436 # Try to convert the user's metadata value into the format expected 437 # by GCE. First try to ensure user has proper quoting of a 438 # dictionary-like syntax using 'literal_eval', then convert the python 439 # dict into a python list of 'key' / 'value' dicts. Should end up 440 # with: 441 # [ {'key': key1, 'value': value1}, {'key': key2, 'value': value2}, ...] 442 if metadata: 443 if isinstance(metadata, dict): 444 md = metadata 445 else: 446 try: 447 md = literal_eval(str(metadata)) 448 if not isinstance(md, dict): 449 raise ValueError('metadata must be a dict') 450 except ValueError as e: 451 module.fail_json(msg='bad metadata: %s' % str(e)) 452 except SyntaxError as e: 453 module.fail_json(msg='bad metadata syntax') 454 455 if hasattr(libcloud, '__version__') and libcloud.__version__ < '0.15': 456 items = [] 457 for k, v in md.items(): 458 items.append({"key": k, "value": v}) 459 metadata = {'items': items} 460 else: 461 metadata = md 462 463 lc_image = LazyDiskImage(module, gce, image, lc_disks, family=image_family, projects=external_projects) 464 ex_sa_perms = [] 465 bad_perms = [] 466 if service_account_permissions: 467 for perm in service_account_permissions: 468 if perm not in gce.SA_SCOPES_MAP and not perm.startswith('https://www.googleapis.com/auth'): 469 bad_perms.append(perm) 470 if len(bad_perms) > 0: 471 module.fail_json(msg='bad permissions: %s' % str(bad_perms)) 472 ex_sa_perms.append({'email': "default"}) 473 ex_sa_perms[0]['scopes'] = service_account_permissions 474 475 # These variables all have default values but check just in case 476 if not lc_network or not lc_machine_type or not lc_zone: 477 module.fail_json(msg='Missing required create instance variable', 478 changed=False) 479 480 gce_args = dict( 481 location=lc_zone, 482 ex_network=network, ex_tags=tags, ex_metadata=metadata, 483 ex_can_ip_forward=ip_forward, 484 external_ip=instance_external_ip, ex_disk_auto_delete=disk_auto_delete, 485 ex_service_accounts=ex_sa_perms 486 ) 487 if preemptible is not None: 488 gce_args['ex_preemptible'] = preemptible 489 if subnetwork is not None: 490 gce_args['ex_subnetwork'] = subnetwork 491 492 if isinstance(instance_names, str) and not number: 493 instance_names = [instance_names] 494 495 if isinstance(instance_names, str) and number: 496 instance_responses = gce.ex_create_multiple_nodes(instance_names, lc_machine_type, 497 lc_image(), number, **gce_args) 498 for resp in instance_responses: 499 n = resp 500 if isinstance(resp, libcloud.compute.drivers.gce.GCEFailedNode): 501 try: 502 n = gce.ex_get_node(n.name, lc_zone) 503 except ResourceNotFoundError: 504 pass 505 else: 506 # Assure that at least one node has been created to set changed=True 507 changed = True 508 new_instances.append(n) 509 else: 510 for instance in instance_names: 511 pd = None 512 if lc_disks: 513 pd = lc_disks[0] 514 elif persistent_boot_disk: 515 try: 516 pd = gce.ex_get_volume("%s" % instance, lc_zone) 517 except ResourceNotFoundError: 518 pd = gce.create_volume(disk_size, "%s" % instance, image=lc_image()) 519 gce_args['ex_boot_disk'] = pd 520 521 inst = None 522 try: 523 inst = gce.ex_get_node(instance, lc_zone) 524 except ResourceNotFoundError: 525 inst = gce.create_node( 526 instance, lc_machine_type, lc_image(), **gce_args 527 ) 528 changed = True 529 except GoogleBaseError as e: 530 module.fail_json(msg='Unexpected error attempting to create ' + 531 'instance %s, error: %s' % (instance, e.value)) 532 if inst: 533 new_instances.append(inst) 534 535 for inst in new_instances: 536 for i, lc_disk in enumerate(lc_disks): 537 # Check whether the disk is already attached 538 if (len(inst.extra['disks']) > i): 539 attached_disk = inst.extra['disks'][i] 540 if attached_disk['source'] != lc_disk.extra['selfLink']: 541 module.fail_json( 542 msg=("Disk at index %d does not match: requested=%s found=%s" % ( 543 i, lc_disk.extra['selfLink'], attached_disk['source']))) 544 elif attached_disk['mode'] != disk_modes[i]: 545 module.fail_json( 546 msg=("Disk at index %d is in the wrong mode: requested=%s found=%s" % ( 547 i, disk_modes[i], attached_disk['mode']))) 548 else: 549 continue 550 gce.attach_volume(inst, lc_disk, ex_mode=disk_modes[i]) 551 # Work around libcloud bug: attached volumes don't get added 552 # to the instance metadata. get_instance_info() only cares about 553 # source and index. 554 if len(inst.extra['disks']) != i + 1: 555 inst.extra['disks'].append( 556 {'source': lc_disk.extra['selfLink'], 'index': i}) 557 558 instance_names = [] 559 instance_json_data = [] 560 for inst in new_instances: 561 d = get_instance_info(inst) 562 instance_names.append(d['name']) 563 instance_json_data.append(d) 564 565 return (changed, instance_json_data, instance_names) 566 567 568def change_instance_state(module, gce, instance_names, number, zone, state): 569 """Changes the state of a list of instances. For example, 570 change from started to stopped, or started to absent. 571 572 module: Ansible module object 573 gce: authenticated GCE connection object 574 instance_names: a list of instance names to terminate 575 zone: GCEZone object where the instances reside prior to termination 576 state: 'state' parameter passed into module as argument 577 578 Returns a dictionary of instance names that were changed. 579 580 """ 581 changed = False 582 nodes = [] 583 state_instance_names = [] 584 585 if isinstance(instance_names, str) and number: 586 node_names = ['%s-%03d' % (instance_names, i) for i in range(number)] 587 elif isinstance(instance_names, str) and not number: 588 node_names = [instance_names] 589 else: 590 node_names = instance_names 591 592 for name in node_names: 593 inst = None 594 try: 595 inst = gce.ex_get_node(name, zone) 596 except ResourceNotFoundError: 597 state_instance_names.append(name) 598 except Exception as e: 599 module.fail_json(msg=unexpected_error_msg(e), changed=False) 600 else: 601 nodes.append(inst) 602 state_instance_names.append(name) 603 604 if state in ['absent', 'deleted'] and number: 605 changed_nodes = gce.ex_destroy_multiple_nodes(nodes) or [False] 606 changed = reduce(lambda x, y: x or y, changed_nodes) 607 else: 608 for node in nodes: 609 if state in ['absent', 'deleted']: 610 gce.destroy_node(node) 611 changed = True 612 elif state == 'started' and node.state == libcloud.compute.types.NodeState.STOPPED: 613 gce.ex_start_node(node) 614 changed = True 615 elif state in ['stopped', 'terminated'] and node.state == libcloud.compute.types.NodeState.RUNNING: 616 gce.ex_stop_node(node) 617 changed = True 618 619 return (changed, state_instance_names) 620 621 622def main(): 623 module = AnsibleModule( 624 argument_spec=dict( 625 image=dict(default='debian-8'), 626 image_family=dict(), 627 external_projects=dict(type='list'), 628 instance_names=dict(), 629 machine_type=dict(default='n1-standard-1'), 630 metadata=dict(), 631 name=dict(aliases=['base_name']), 632 num_instances=dict(type='int'), 633 network=dict(default='default'), 634 subnetwork=dict(), 635 persistent_boot_disk=dict(type='bool', default=False), 636 disks=dict(type='list'), 637 state=dict(choices=['active', 'present', 'absent', 'deleted', 638 'started', 'stopped', 'terminated'], 639 default='present'), 640 tags=dict(type='list'), 641 zone=dict(default='us-central1-a'), 642 service_account_email=dict(), 643 service_account_permissions=dict(type='list'), 644 pem_file=dict(type='path'), 645 credentials_file=dict(type='path'), 646 project_id=dict(), 647 ip_forward=dict(type='bool', default=False), 648 external_ip=dict(default='ephemeral'), 649 disk_auto_delete=dict(type='bool', default=True), 650 disk_size=dict(type='int', default=10), 651 preemptible=dict(type='bool', default=None), 652 ), 653 mutually_exclusive=[('instance_names', 'name')] 654 ) 655 656 if not HAS_PYTHON26: 657 module.fail_json(msg="GCE module requires python's 'ast' module, python v2.6+") 658 if not HAS_LIBCLOUD: 659 module.fail_json(msg='libcloud with GCE support (0.17.0+) required for this module') 660 661 gce = gce_connect(module) 662 663 image = module.params.get('image') 664 image_family = module.params.get('image_family') 665 external_projects = module.params.get('external_projects') 666 instance_names = module.params.get('instance_names') 667 name = module.params.get('name') 668 number = module.params.get('num_instances') 669 subnetwork = module.params.get('subnetwork') 670 state = module.params.get('state') 671 zone = module.params.get('zone') 672 preemptible = module.params.get('preemptible') 673 changed = False 674 675 inames = None 676 if isinstance(instance_names, list): 677 inames = instance_names 678 elif isinstance(instance_names, str): 679 inames = instance_names.split(',') 680 if name: 681 inames = name 682 if not inames: 683 module.fail_json(msg='Must specify a "name" or "instance_names"', 684 changed=False) 685 if not zone: 686 module.fail_json(msg='Must specify a "zone"', changed=False) 687 688 lc_zone = get_valid_location(module, gce, zone) 689 if preemptible is not None and hasattr(libcloud, '__version__') and libcloud.__version__ < '0.20': 690 module.fail_json(msg="Apache Libcloud 0.20.0+ is required to use 'preemptible' option", 691 changed=False) 692 693 if subnetwork is not None and not hasattr(gce, 'ex_get_subnetwork'): 694 module.fail_json(msg="Apache Libcloud 1.0.0+ is required to use 'subnetwork' option", 695 changed=False) 696 697 json_output = {'zone': zone} 698 if state in ['absent', 'deleted', 'started', 'stopped', 'terminated']: 699 json_output['state'] = state 700 (changed, state_instance_names) = change_instance_state( 701 module, gce, inames, number, lc_zone, state) 702 703 # based on what user specified, return the same variable, although 704 # value could be different if an instance could not be destroyed 705 if instance_names or name and number: 706 json_output['instance_names'] = state_instance_names 707 elif name: 708 json_output['name'] = name 709 710 elif state in ['active', 'present']: 711 json_output['state'] = 'present' 712 (changed, instance_data, instance_name_list) = create_instances( 713 module, gce, inames, number, lc_zone) 714 json_output['instance_data'] = instance_data 715 if instance_names: 716 json_output['instance_names'] = instance_name_list 717 elif name: 718 json_output['name'] = name 719 720 json_output['changed'] = changed 721 module.exit_json(**json_output) 722 723 724class LazyDiskImage: 725 """ 726 Object for lazy instantiation of disk image 727 gce.ex_get_image is a very expensive call, so we want to avoid calling it as much as possible. 728 """ 729 730 def __init__(self, module, gce, name, has_pd, family=None, projects=None): 731 self.image = None 732 self.was_called = False 733 self.gce = gce 734 self.name = name 735 self.has_pd = has_pd 736 self.module = module 737 self.family = family 738 self.projects = projects 739 740 def __call__(self): 741 if not self.was_called: 742 self.was_called = True 743 if not self.has_pd: 744 if self.family: 745 self.image = self.gce.ex_get_image_from_family(self.family, ex_project_list=self.projects) 746 else: 747 self.image = self.gce.ex_get_image(self.name, ex_project_list=self.projects) 748 if not self.image: 749 self.module.fail_json(msg='image or disks missing for create instance', changed=False) 750 return self.image 751 752 753if __name__ == '__main__': 754 main() 755