rax 25.4 KB
Newer Older
1
#!/usr/bin/python -tt
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.

DOCUMENTATION = '''
---
module: rax
20
short_description: create / delete an instance in Rackspace Public Cloud
21
description:
Matt Martz committed
22 23
     - creates / deletes a Rackspace Public Cloud instance and optionally
       waits for it to be 'running'.
24 25
version_added: "1.2"
options:
Matt Martz committed
26
  api_key:
27
    description:
Matt Martz committed
28
      - Rackspace API key (overrides I(credentials))
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
    aliases:
      - password
  auth_endpoint:
    description:
      - The URI of the authentication service
    default: https://identity.api.rackspacecloud.com/v2.0/
    version_added: 1.5
  credentials:
    description:
      - File to find the Rackspace credentials in (ignored if I(api_key) and
        I(username) are provided)
    default: null
    aliases:
      - creds_file
  env:
    description:
      - Environment as configured in ~/.pyrax.cfg,
        see U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#pyrax-configuration)
    version_added: 1.5
  identity_type:
    description:
      - Authentication machanism to use, such as rackspace or keystone
    default: rackspace
    version_added: 1.5
  region:
    description:
      - Region to create an instance in
    default: DFW
  tenant_id:
    description:
      - The tenant ID used for authentication
    version_added: 1.5
  tenant_name:
    description:
      - The tenant name used for authentication
    version_added: 1.5
  username:
    description:
      - Rackspace username (overrides I(credentials))
  verify_ssl:
    description:
      - Whether or not to require SSL validation of API endpoints
    version_added: 1.5
72 73 74 75 76 77 78
  auto_increment:
    description:
      - Whether or not to increment a single number with the name of the
        created servers. Only applicable when used with the I(group) attribute
        or meta key.
    default: yes
    version_added: 1.5
Matt Martz committed
79
  count:
80
    description:
Matt Martz committed
81 82 83 84 85 86 87 88 89
      - number of instances to launch
    default: 1
    version_added: 1.4
  count_offset:
    description:
      - number count to start at
    default: 1
    version_added: 1.4
  disk_config:
90
    description:
Matt Martz committed
91 92 93 94 95
      - Disk partitioning strategy
    choices: ['auto', 'manual']
    version_added: '1.4'
    default: auto
  exact_count:
96
    description:
Matt Martz committed
97 98 99 100
      - Explicitly ensure an exact count of instances, used with
        state=active/present
    default: no
    version_added: 1.4
101 102 103 104 105 106 107 108 109 110 111
  extra_client_args:
    description:
      - A hash of key/value pairs to be used when creating the cloudservers
        client. This is considered an advanced option, use it wisely and
        with caution.
    version_added: 1.6
  extra_create_args:
    description:
      - A hash of key/value pairs to be used when creating a new server.
        This is considered an advanced option, use it wisely and with caution.
    version_added: 1.6
Matt Martz committed
112
  files:
113
    description:
Matt Martz committed
114
      - Files to insert into the instance. remotefilename:localcontent
115 116 117
    default: null
  flavor:
    description:
Matt Martz committed
118
      - flavor to use for the instance
119
    default: null
Matt Martz committed
120 121 122 123 124
  group:
    description:
      - host group to assign to server, is also used for idempotent operations
        to ensure a specific number of instances
    version_added: 1.4
125 126
  image:
    description:
Matt Martz committed
127
      - image to use for the instance. Can be an C(id), C(human_id) or C(name)
128
    default: null
Matt Martz committed
129
  instance_ids:
130
    description:
Matt Martz committed
131 132 133
      - list of instance ids, currently only used when state='absent' to
        remove instances
    version_added: 1.4
134 135
  key_name:
    description:
Matt Martz committed
136
      - key pair to use on the instance
137 138
    default: null
    aliases: ['keypair']
Matt Martz committed
139
  meta:
140
    description:
Matt Martz committed
141
      - A hash of metadata to associate with the instance
142
    default: null
Matt Martz committed
143 144 145 146 147 148 149 150 151 152 153 154
  name:
    description:
      - Name to give the instance
    default: null
  networks:
    description:
      - The network to attach to the instances. If specified, you must include
        ALL networks including the public and private interfaces. Can be C(id)
        or C(label).
    default: ['public', 'private']
    version_added: 1.4
  state:
155
    description:
Matt Martz committed
156 157 158
      - Indicate desired state of the resource
    choices: ['present', 'absent']
    default: present
159 160
  wait:
    description:
Matt Martz committed
161
      - wait for the instance to be in state 'running' before returning
162 163 164 165
    default: "no"
    choices: [ "yes", "no" ]
  wait_timeout:
    description:
Matt Martz committed
166
      - how long before wait gives up, in seconds
167 168
    default: 300
requirements: [ "pyrax" ]
Matt Martz committed
169
author: Jesse Keating, Matt Martz
170
notes:
171
  - The following environment variables can be used, C(RAX_USERNAME),
Matt Martz committed
172 173 174
    C(RAX_API_KEY), C(RAX_CREDS_FILE), C(RAX_CREDENTIALS), C(RAX_REGION).
  - C(RAX_CREDENTIALS) and C(RAX_CREDS_FILE) points to a credentials file
    appropriate for pyrax. See U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#authenticating)
175 176
  - C(RAX_USERNAME) and C(RAX_API_KEY) obviate the use of a credentials file
  - C(RAX_REGION) defines a Rackspace Public Cloud region (DFW, ORD, LON, ...)
177 178
'''

179
EXAMPLES = '''
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
- name: Build a Cloud Server
  gather_facts: False
  tasks:
    - name: Server build request
      local_action:
        module: rax
        credentials: ~/.raxpub
        name: rax-test1
        flavor: 5
        image: b11d9567-e412-4255-96b9-bd63ab23bcfe
        files:
          /root/.ssh/authorized_keys: /home/localuser/.ssh/id_rsa.pub
          /root/test.txt: /home/localuser/test.txt
        wait: yes
        state: present
Matt Martz committed
195 196 197
        networks:
          - private
          - public
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
      register: rax

- name: Build an exact count of cloud servers with incremented names
  hosts: local
  gather_facts: False
  tasks:
    - name: Server build requests
      local_action:
        module: rax
        credentials: ~/.raxpub
        name: test%03d.example.org
        flavor: performance1-1
        image: ubuntu-1204-lts-precise-pangolin
        state: present
        count: 10
        count_offset: 10
        exact_count: yes
        group: test
        wait: yes
      register: rax
218 219
'''

220 221 222
import sys
import time
import os
Matt Martz committed
223 224 225
import re
from uuid import UUID
from types import NoneType
226 227 228 229

try:
    import pyrax
except ImportError:
Matt Martz committed
230
    print("failed=True msg='pyrax is required for this module'")
231 232
    sys.exit(1)

Matt Martz committed
233 234 235 236 237 238
ACTIVE_STATUSES = ('ACTIVE', 'BUILD', 'HARD_REBOOT', 'MIGRATING', 'PASSWORD',
                   'REBOOT', 'REBUILD', 'RESCUE', 'RESIZE', 'REVERT_RESIZE')
FINAL_STATUSES = ('ACTIVE', 'ERROR')
NON_CALLABLES = (basestring, bool, dict, int, list, NoneType)
PUBLIC_NET_ID = "00000000-0000-0000-0000-000000000000"
SERVICE_NET_ID = "11111111-1111-1111-1111-111111111111"
239 240


Matt Martz committed
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
def rax_slugify(value):
    return 'rax_%s' % (re.sub('[^\w-]', '_', value).lower().lstrip('_'))


def pyrax_object_to_dict(obj):
    instance = {}
    for key in dir(obj):
        value = getattr(obj, key)
        if (isinstance(value, NON_CALLABLES) and not key.startswith('_')):
            key = rax_slugify(key)
            instance[key] = value

    for attr in ['id', 'accessIPv4', 'name', 'status']:
        instance[attr] = instance.get(rax_slugify(attr))

    return instance


def create(module, names, flavor, image, meta, key_name, files,
260 261
           wait, wait_timeout, disk_config, group, nics,
           extra_create_args):
Matt Martz committed
262 263 264 265 266 267 268 269 270

    cs = pyrax.cloudservers
    changed = False

    # Handle the file contents
    for rpath in files.keys():
        lpath = os.path.expanduser(files[rpath])
        try:
            fileobj = open(lpath, 'r')
271
            files[rpath] = fileobj.read()
Matt Martz committed
272 273 274 275 276 277 278 279 280
        except Exception, e:
            module.fail_json(msg='Failed to load %s' % lpath)
    try:
        servers = []
        for name in names:
            servers.append(cs.servers.create(name=name, image=image,
                                             flavor=flavor, meta=meta,
                                             key_name=key_name,
                                             files=files, nics=nics,
281 282
                                             disk_config=disk_config,
                                             **extra_create_args))
Matt Martz committed
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
    except Exception, e:
        module.fail_json(msg='%s' % e.message)
    else:
        changed = True

    if wait:
        end_time = time.time() + wait_timeout
        infinite = wait_timeout == 0
        while infinite or time.time() < end_time:
            for server in servers:
                try:
                    server.get()
                except:
                    server.status == 'ERROR'

            if not filter(lambda s: s.status not in FINAL_STATUSES,
                          servers):
                break
            time.sleep(5)

    success = []
    error = []
    timeout = []
    for server in servers:
        try:
            server.get()
        except:
            server.status == 'ERROR'
        instance = pyrax_object_to_dict(server)
        if server.status == 'ACTIVE' or not wait:
            success.append(instance)
        elif server.status == 'ERROR':
            error.append(instance)
        elif wait:
            timeout.append(instance)

    results = {
        'changed': changed,
        'action': 'create',
        'instances': success + error + timeout,
        'success': success,
        'error': error,
        'timeout': timeout,
        'instance_ids': {
            'instances': [i['id'] for i in success + error + timeout],
            'success': [i['id'] for i in success],
            'error': [i['id'] for i in error],
            'timeout': [i['id'] for i in timeout]
        }
    }

    if timeout:
        results['msg'] = 'Timeout waiting for all servers to build'
    elif error:
        results['msg'] = 'Failed to build all servers'

    if 'msg' in results:
        module.fail_json(**results)
    else:
        module.exit_json(**results)


def delete(module, instance_ids, wait, wait_timeout):
    cs = pyrax.cloudservers

348
    changed = False
Matt Martz committed
349
    instances = {}
350 351
    servers = []

Matt Martz committed
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
    for instance_id in instance_ids:
        servers.append(cs.servers.get(instance_id))

    for server in servers:
        try:
            server.delete()
        except Exception, e:
            module.fail_json(msg=e.message)
        else:
            changed = True

        instance = pyrax_object_to_dict(server)
        instances[instance['id']] = instance

    # If requested, wait for server deletion
    if wait:
        end_time = time.time() + wait_timeout
        infinite = wait_timeout == 0
        while infinite or time.time() < end_time:
            for server in servers:
                instance_id = server.id
373
                try:
Matt Martz committed
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
                    server.get()
                except:
                    instances[instance_id]['status'] = 'DELETED'

            if not filter(lambda s: s['status'] not in ('', 'DELETED',
                                                        'ERROR'),
                          instances.values()):
                break

            time.sleep(5)

    timeout = filter(lambda s: s['status'] not in ('', 'DELETED', 'ERROR'),
                     instances.values())
    error = filter(lambda s: s['status'] in ('ERROR'),
                   instances.values())
    success = filter(lambda s: s['status'] in ('', 'DELETED'),
                     instances.values())

    results = {
        'changed': changed,
        'action': 'delete',
        'instances': success + error + timeout,
        'success': success,
        'error': error,
        'timeout': timeout,
        'instance_ids': {
            'instances': [i['id'] for i in success + error + timeout],
            'success': [i['id'] for i in success],
            'error': [i['id'] for i in error],
            'timeout': [i['id'] for i in timeout]
        }
    }

    if timeout:
        results['msg'] = 'Timeout waiting for all servers to delete'
    elif error:
        results['msg'] = 'Failed to delete all servers'

    if 'msg' in results:
        module.fail_json(**results)
    else:
        module.exit_json(**results)


def cloudservers(module, state, name, flavor, image, meta, key_name, files,
                 wait, wait_timeout, disk_config, count, group,
420
                 instance_ids, exact_count, networks, count_offset,
421
                 auto_increment, extra_create_args):
Matt Martz committed
422 423 424 425 426 427 428 429 430 431
    cs = pyrax.cloudservers
    cnw = pyrax.cloud_networks
    servers = []

    # Add the group meta key
    if group and 'group' not in meta:
        meta['group'] = group
    elif 'group' in meta and group is None:
        group = meta['group']

432 433 434 435 436 437 438 439 440
    # When using state=absent with group, the absent block won't match the
    # names properly. Use the exact_count functionality to decrease the count
    # to the desired level
    was_absent = False
    if group is not None and state == 'absent':
        exact_count = True
        state = 'present'
        was_absent = True

Matt Martz committed
441 442 443 444 445 446
    # Check if the provided image is a UUID and if not, search for an
    # appropriate image using human_id and name
    if image:
        try:
            UUID(image)
        except ValueError:
447
            try:
Matt Martz committed
448
                image = cs.images.find(human_id=image)
449 450
            except(cs.exceptions.NotFound,
                   cs.exceptions.NoUniqueMatch):
Matt Martz committed
451 452
                try:
                    image = cs.images.find(name=image)
453 454
                except (cs.exceptions.NotFound,
                        cs.exceptions.NoUniqueMatch):
Matt Martz committed
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
                    module.fail_json(msg='No matching image found (%s)' %
                                         image)

        image = pyrax.utils.get_id(image)

    # Check if the provided network is a UUID and if not, search for an
    # appropriate network using label
    nics = []
    if networks:
        for network in networks:
            try:
                UUID(network)
            except ValueError:
                if network.lower() == 'public':
                    nics.extend(cnw.get_server_networks(PUBLIC_NET_ID))
                elif network.lower() == 'private':
                    nics.extend(cnw.get_server_networks(SERVICE_NET_ID))
                else:
473
                    try:
Matt Martz committed
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
                        network_obj = cnw.find_network_by_label(network)
                    except (pyrax.exceptions.NetworkNotFound,
                            pyrax.exceptions.NetworkLabelNotUnique):
                        module.fail_json(msg='No matching network found (%s)' %
                                             network)
                    else:
                        nics.extend(cnw.get_server_networks(network_obj))
            else:
                nics.extend(cnw.get_server_networks(network))

    # act on the state
    if state == 'present':
        for arg, value in dict(name=name, flavor=flavor,
                               image=image).iteritems():
            if not value:
                module.fail_json(msg='%s is required for the "rax" module' %
                                     arg)

        # Idempotent ensurance of a specific count of servers
        if exact_count is not False:
            # See if we can find servers that match our options
            if group is None:
                module.fail_json(msg='"group" must be provided when using '
                                     '"exact_count"')
            else:
499 500
                if auto_increment:
                    numbers = set()
Matt Martz committed
501

502 503 504 505 506 507 508 509
                    try:
                        name % 0
                    except TypeError, e:
                        if e.message.startswith('not all'):
                            name = '%s%%d' % name
                        else:
                            module.fail_json(msg=e.message)

510
                    pattern = re.sub(r'%\d*[sd]', r'(\d+)', name)
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
                    for server in cs.servers.list():
                        if server.metadata.get('group') == group:
                            servers.append(server)
                        match = re.search(pattern, server.name)
                        if match:
                            number = int(match.group(1))
                            numbers.add(number)

                    number_range = xrange(count_offset, count_offset + count)
                    available_numbers = list(set(number_range)
                                             .difference(numbers))
                else:
                    for server in cs.servers.list():
                        if server.metadata.get('group') == group:
                            servers.append(server)

                # If state was absent but the count was changed,
                # assume we only wanted to remove that number of instances
                if was_absent:
                    diff = len(servers) - count
                    if diff < 0:
                        count = 0
Matt Martz committed
533
                    else:
534 535
                        count = diff

Matt Martz committed
536 537 538 539 540 541 542 543
                if len(servers) > count:
                    state = 'absent'
                    del servers[:count]
                    instance_ids = []
                    for server in servers:
                        instance_ids.append(server.id)
                    delete(module, instance_ids, wait, wait_timeout)
                elif len(servers) < count:
544 545 546 547 548 549 550 551
                    if auto_increment:
                        names = []
                        name_slice = count - len(servers)
                        numbers_to_use = available_numbers[:name_slice]
                        for number in numbers_to_use:
                            names.append(name % number)
                    else:
                        names = [name] * (count - len(servers))
Matt Martz committed
552 553 554 555 556 557 558 559
                else:
                    module.exit_json(changed=False, action=None, instances=[],
                                     success=[], error=[], timeout=[],
                                     instance_ids={'instances': [],
                                                   'success': [], 'error': [],
                                                   'timeout': []})
        else:
            if group is not None:
560 561
                if auto_increment:
                    numbers = set()
Matt Martz committed
562

563 564 565 566 567 568 569 570
                    try:
                        name % 0
                    except TypeError, e:
                        if e.message.startswith('not all'):
                            name = '%s%%d' % name
                        else:
                            module.fail_json(msg=e.message)

571
                    pattern = re.sub(r'%\d*[sd]', r'(\d+)', name)
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
                    for server in cs.servers.list():
                        if server.metadata.get('group') == group:
                            servers.append(server)
                        match = re.search(pattern, server.name)
                        if match:
                            number = int(match.group(1))
                            numbers.add(number)

                    number_range = xrange(count_offset,
                                          count_offset + count + len(numbers))
                    available_numbers = list(set(number_range)
                                             .difference(numbers))
                    names = []
                    numbers_to_use = available_numbers[:count]
                    for number in numbers_to_use:
                        names.append(name % number)
                else:
                    names = [name] * count
Matt Martz committed
590 591
            else:
                search_opts = {
592
                    'name': '^%s$' % name,
Matt Martz committed
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
                    'image': image,
                    'flavor': flavor
                }
                servers = []
                for server in cs.servers.list(search_opts=search_opts):
                    if server.metadata != meta:
                        continue
                    servers.append(server)

                if len(servers) >= count:
                    instances = []
                    for server in servers:
                        instances.append(pyrax_object_to_dict(server))

                    instance_ids = [i['id'] for i in instances]
                    module.exit_json(changed=False, action=None,
                                     instances=instances, success=[], error=[],
                                     timeout=[],
                                     instance_ids={'instances': instance_ids,
                                                   'success': [], 'error': [],
                                                   'timeout': []})

                names = [name] * (count - len(servers))

        create(module, names, flavor, image, meta, key_name, files,
618
               wait, wait_timeout, disk_config, group, nics, extra_create_args)
Matt Martz committed
619 620 621 622 623 624 625 626 627

    elif state == 'absent':
        if instance_ids is None:
            for arg, value in dict(name=name, flavor=flavor,
                                   image=image).iteritems():
                if not value:
                    module.fail_json(msg='%s is required for the "rax" '
                                         'module' % arg)
            search_opts = {
628
                'name': '^%s$' % name,
Matt Martz committed
629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652
                'image': image,
                'flavor': flavor
            }
            for server in cs.servers.list(search_opts=search_opts):
                if meta != server.metadata:
                    continue
                servers.append(server)

            instance_ids = []
            for server in servers:
                if len(instance_ids) < count:
                    instance_ids.append(server.id)
                else:
                    break

        if not instance_ids:
            module.exit_json(changed=False, action=None, instances=[],
                             success=[], error=[], timeout=[],
                             instance_ids={'instances': [],
                                           'success': [], 'error': [],
                                           'timeout': []})

        delete(module, instance_ids, wait, wait_timeout)

653 654

def main():
655 656 657
    argument_spec = rax_argument_spec()
    argument_spec.update(
        dict(
658
            auto_increment=dict(default=True, type='bool'),
Matt Martz committed
659 660
            count=dict(default=1, type='int'),
            count_offset=dict(default=1, type='int'),
661
            disk_config=dict(choices=['auto', 'manual']),
662
            exact_count=dict(default=False, type='bool'),
663 664
            extra_client_args=dict(type='dict', default={}),
            extra_create_args=dict(type='dict', default={}),
Matt Martz committed
665 666 667 668 669 670 671 672 673 674 675
            files=dict(type='dict', default={}),
            flavor=dict(),
            group=dict(),
            image=dict(),
            instance_ids=dict(type='list'),
            key_name=dict(aliases=['keypair']),
            meta=dict(type='dict', default={}),
            name=dict(),
            networks=dict(type='list', default=['public', 'private']),
            service=dict(),
            state=dict(default='present', choices=['present', 'absent']),
676
            wait=dict(default=False, type='bool'),
Matt Martz committed
677
            wait_timeout=dict(default=300),
678 679 680 681 682 683
        )
    )

    module = AnsibleModule(
        argument_spec=argument_spec,
        required_together=rax_required_together(),
684 685 686
    )

    service = module.params.get('service')
Matt Martz committed
687 688 689 690 691 692

    if service is not None:
        module.fail_json(msg='The "service" attribute has been deprecated, '
                             'please remove "service: cloudservers" from your '
                             'playbook pertaining to the "rax" module')

693
    auto_increment = module.params.get('auto_increment')
Matt Martz committed
694 695
    count = module.params.get('count')
    count_offset = module.params.get('count_offset')
696 697 698
    disk_config = module.params.get('disk_config')
    if disk_config:
        disk_config = disk_config.upper()
Matt Martz committed
699
    exact_count = module.params.get('exact_count', False)
700 701
    extra_client_args = module.params.get('extra_client_args')
    extra_create_args = module.params.get('extra_create_args')
Matt Martz committed
702
    files = module.params.get('files')
703
    flavor = module.params.get('flavor')
Matt Martz committed
704
    group = module.params.get('group')
705
    image = module.params.get('image')
Matt Martz committed
706
    instance_ids = module.params.get('instance_ids')
707
    key_name = module.params.get('key_name')
Matt Martz committed
708 709 710 711
    meta = module.params.get('meta')
    name = module.params.get('name')
    networks = module.params.get('networks')
    state = module.params.get('state')
712 713 714
    wait = module.params.get('wait')
    wait_timeout = int(module.params.get('wait_timeout'))

715
    setup_rax_module(module, pyrax)
716

717 718 719 720 721 722 723 724 725 726 727 728 729
    if pyrax.cloudservers is None:
        module.fail_json(msg='Failed to instantiate client. This '
                             'typically indicates an invalid region or an '
                             'incorrectly capitalized region name.')

    if extra_client_args:
        pyrax.cloudservers = pyrax.connect_to_cloudservers(
            region=pyrax.cloudservers.client.region_name,
            **extra_client_args)
        client = pyrax.cloudservers.client
        if 'bypass_url' in extra_client_args:
            client.management_url = extra_client_args['bypass_url']

Matt Martz committed
730 731
    cloudservers(module, state, name, flavor, image, meta, key_name, files,
                 wait, wait_timeout, disk_config, count, group,
732
                 instance_ids, exact_count, networks, count_offset,
733
                 auto_increment, extra_create_args)
734 735


736
# import module snippets
Matt Martz committed
737
from ansible.module_utils.basic import *
738
from ansible.module_utils.rax import *
739

Matt Martz committed
740
### invoke the module
741
main()