Example #1
0
def delete_instance(module, name, wait, wait_timeout):

    if not name:
        module.fail_json(msg='name is required for the "rax_cdb" module')

    changed = False

    instance = find_instance(name)
    if not instance:
        module.exit_json(changed=False, action='delete')

    try:
        instance.delete()
    except Exception as e:
        module.fail_json(msg='%s' % e.message)
    else:
        changed = True

    if wait:
        pyrax.utils.wait_until(instance,
                               'status',
                               'SHUTDOWN',
                               attempts=wait_timeout)

    if wait and instance.status != 'SHUTDOWN':
        module.fail_json(changed=changed,
                         action='delete',
                         cdb=rax_to_dict(instance),
                         msg='Timeout waiting for "%s" databases instance to '
                         'be deleted' % name)

    module.exit_json(changed=changed,
                     action='delete',
                     cdb=rax_to_dict(instance))
def save_database(module, cdb_id, name, character_set, collate):
    cdb = pyrax.cloud_databases

    try:
        instance = cdb.get(cdb_id)
    except Exception as e:
        module.fail_json(msg='%s' % e.message)

    changed = False

    database = find_database(instance, name)

    if not database:
        try:
            database = instance.create_database(name=name,
                                                character_set=character_set,
                                                collate=collate)
        except Exception as e:
            module.fail_json(msg='%s' % e.message)
        else:
            changed = True

    module.exit_json(changed=changed,
                     action='create',
                     database=rax_to_dict(database))
Example #3
0
def rax_dns(module, comment, email, name, state, ttl):
    changed = False

    dns = pyrax.cloud_dns
    if not dns:
        module.fail_json(msg='Failed to instantiate client. This '
                         'typically indicates an invalid region or an '
                         'incorrectly capitalized region name.')

    if state == 'present':
        if not email:
            module.fail_json(msg='An "email" attribute is required for '
                             'creating a domain')

        try:
            domain = dns.find(name=name)
        except pyrax.exceptions.NoUniqueMatch as e:
            module.fail_json(msg='%s' % e.message)
        except pyrax.exceptions.NotFound:
            try:
                domain = dns.create(name=name,
                                    emailAddress=email,
                                    ttl=ttl,
                                    comment=comment)
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

        update = {}
        if comment != getattr(domain, 'comment', None):
            update['comment'] = comment
        if ttl != getattr(domain, 'ttl', None):
            update['ttl'] = ttl
        if email != getattr(domain, 'emailAddress', None):
            update['emailAddress'] = email

        if update:
            try:
                domain.update(**update)
                changed = True
                domain.get()
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

    elif state == 'absent':
        try:
            domain = dns.find(name=name)
        except pyrax.exceptions.NotFound:
            domain = {}
        except Exception as e:
            module.fail_json(msg='%s' % e.message)

        if domain:
            try:
                domain.delete()
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

    module.exit_json(changed=changed, domain=rax_to_dict(domain))
Example #4
0
def save_user(module, cdb_id, name, password, databases, host):

    for arg, value in dict(cdb_id=cdb_id, name=name).items():
        if not value:
            module.fail_json(msg='%s is required for the "rax_cdb_user" '
                             'module' % arg)

    cdb = pyrax.cloud_databases

    try:
        instance = cdb.get(cdb_id)
    except Exception as e:
        module.fail_json(msg='%s' % e.message)

    changed = False

    user = find_user(instance, name)

    if not user:
        action = 'create'
        try:
            user = instance.create_user(name=name,
                                        password=password,
                                        database_names=databases,
                                        host=host)
        except Exception as e:
            module.fail_json(msg='%s' % e.message)
        else:
            changed = True
    else:
        action = 'update'

        if user.host != host:
            changed = True

        user.update(password=password, host=host)

        former_dbs = set([item.name for item in user.list_user_access()])
        databases = set(databases)

        if databases != former_dbs:
            try:
                revoke_dbs = [db for db in former_dbs if db not in databases]
                user.revoke_user_access(db_names=revoke_dbs)

                new_dbs = [db for db in databases if db not in former_dbs]
                user.grant_user_access(db_names=new_dbs)
            except Exception as e:
                module.fail_json(msg='%s' % e.message)
            else:
                changed = True

    module.exit_json(changed=changed, action=action, user=rax_to_dict(user))
Example #5
0
def cloud_identity(module, state, identity):
    instance = dict(authenticated=identity.authenticated,
                    credentials=identity._creds_file)
    changed = False

    instance.update(rax_to_dict(identity))
    instance['services'] = instance.get('services', {}).keys()

    if state == 'present':
        if not identity.authenticated:
            module.fail_json(msg='Credentials could not be verified!')

    module.exit_json(changed=changed, identity=instance)
Example #6
0
def rax_keypair(module, name, public_key, state):
    changed = False

    cs = pyrax.cloudservers

    if cs is None:
        module.fail_json(msg='Failed to instantiate client. This '
                         'typically indicates an invalid region or an '
                         'incorrectly capitalized region name.')

    keypair = {}

    if state == 'present':
        if public_key and os.path.isfile(public_key):
            try:
                f = open(public_key)
                public_key = f.read()
                f.close()
            except Exception as e:
                module.fail_json(msg='Failed to load %s' % public_key)

        try:
            keypair = cs.keypairs.find(name=name)
        except cs.exceptions.NotFound:
            try:
                keypair = cs.keypairs.create(name, public_key)
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)
        except Exception as e:
            module.fail_json(msg='%s' % e.message)

    elif state == 'absent':
        try:
            keypair = cs.keypairs.find(name=name)
        except Exception:
            pass

        if keypair:
            try:
                keypair.delete()
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

    module.exit_json(changed=changed, keypair=rax_to_dict(keypair))
def rax_facts(module, address, name, server_id):
    changed = False

    cs = pyrax.cloudservers

    if cs is None:
        module.fail_json(msg='Failed to instantiate client. This '
                         'typically indicates an invalid region or an '
                         'incorrectly capitalized region name.')

    ansible_facts = {}

    search_opts = {}
    if name:
        search_opts = dict(name='^%s$' % name)
        try:
            servers = cs.servers.list(search_opts=search_opts)
        except Exception as e:
            module.fail_json(msg='%s' % e.message)
    elif address:
        servers = []
        try:
            for server in cs.servers.list():
                for addresses in server.networks.values():
                    if address in addresses:
                        servers.append(server)
                        break
        except Exception as e:
            module.fail_json(msg='%s' % e.message)
    elif server_id:
        servers = []
        try:
            servers.append(cs.servers.get(server_id))
        except Exception as e:
            pass

    servers[:] = [server for server in servers if server.status != "DELETED"]

    if len(servers) > 1:
        module.fail_json(msg='Multiple servers found matching provided '
                         'search parameters')
    elif len(servers) == 1:
        ansible_facts = rax_to_dict(servers[0], 'server')

    module.exit_json(changed=changed, ansible_facts=ansible_facts)
def delete_database(module, cdb_id, name):
    cdb = pyrax.cloud_databases

    try:
        instance = cdb.get(cdb_id)
    except Exception as e:
        module.fail_json(msg='%s' % e.message)

    changed = False

    database = find_database(instance, name)

    if database:
        try:
            database.delete()
        except Exception as e:
            module.fail_json(msg='%s' % e.message)
        else:
            changed = True

    module.exit_json(changed=changed, action='delete',
                     database=rax_to_dict(database))
Example #9
0
def cloud_load_balancer(module, state, name, meta, algorithm, port, protocol,
                        vip_type, timeout, wait, wait_timeout, vip_id):
    if int(timeout) < 30:
        module.fail_json(msg='"timeout" must be greater than or equal to 30')

    changed = False
    balancers = []

    clb = pyrax.cloud_loadbalancers
    if not clb:
        module.fail_json(msg='Failed to instantiate client. This '
                         'typically indicates an invalid region or an '
                         'incorrectly capitalized region name.')

    balancer_list = clb.list()
    while balancer_list:
        retrieved = clb.list(marker=balancer_list.pop().id)
        balancer_list.extend(retrieved)
        if len(retrieved) < 2:
            break

    for balancer in balancer_list:
        if name != balancer.name and name != balancer.id:
            continue

        balancers.append(balancer)

    if len(balancers) > 1:
        module.fail_json(msg='Multiple Load Balancers were matched by name, '
                         'try using the Load Balancer ID instead')

    if state == 'present':
        if isinstance(meta, dict):
            metadata = [dict(key=k, value=v) for k, v in meta.items()]

        if not balancers:
            try:
                virtual_ips = [clb.VirtualIP(type=vip_type, id=vip_id)]
                balancer = clb.create(name,
                                      metadata=metadata,
                                      port=port,
                                      algorithm=algorithm,
                                      protocol=protocol,
                                      timeout=timeout,
                                      virtual_ips=virtual_ips)
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)
        else:
            balancer = balancers[0]
            setattr(balancer, 'metadata', [
                dict(key=k, value=v)
                for k, v in balancer.get_metadata().items()
            ])
            atts = {
                'name': name,
                'algorithm': algorithm,
                'port': port,
                'protocol': protocol,
                'timeout': timeout
            }
            for att, value in atts.items():
                current = getattr(balancer, att)
                if current != value:
                    changed = True

            if changed:
                balancer.update(**atts)

            if balancer.metadata != metadata:
                balancer.set_metadata(meta)
                changed = True

            virtual_ips = [clb.VirtualIP(type=vip_type)]
            current_vip_types = set([v.type for v in balancer.virtual_ips])
            vip_types = set([v.type for v in virtual_ips])
            if current_vip_types != vip_types:
                module.fail_json(msg='Load balancer Virtual IP type cannot '
                                 'be changed')

        if wait:
            attempts = wait_timeout // 5
            pyrax.utils.wait_for_build(balancer, interval=5, attempts=attempts)

        balancer.get()
        instance = rax_to_dict(balancer, 'clb')

        result = dict(changed=changed, balancer=instance)

        if balancer.status == 'ERROR':
            result['msg'] = '%s failed to build' % balancer.id
        elif wait and balancer.status not in ('ACTIVE', 'ERROR'):
            result['msg'] = 'Timeout waiting on %s' % balancer.id

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

    elif state == 'absent':
        if balancers:
            balancer = balancers[0]
            try:
                balancer.delete()
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

            instance = rax_to_dict(balancer, 'clb')

            if wait:
                attempts = wait_timeout // 5
                pyrax.utils.wait_until(balancer,
                                       'status', ('DELETED'),
                                       interval=5,
                                       attempts=attempts)
        else:
            instance = {}

    module.exit_json(changed=changed, balancer=instance)
Example #10
0
def cloudservers(module, state=None, name=None, flavor=None, image=None,
                 meta=None, key_name=None, files=None, wait=True, wait_timeout=300,
                 disk_config=None, count=1, group=None, instance_ids=None,
                 exact_count=False, networks=None, count_offset=0,
                 auto_increment=False, extra_create_args=None, user_data=None,
                 config_drive=False, boot_from_volume=False,
                 boot_volume=None, boot_volume_size=None,
                 boot_volume_terminate=False):
    meta = {} if meta is None else meta
    files = {} if files is None else files
    instance_ids = [] if instance_ids is None else instance_ids
    networks = [] if networks is None else networks
    extra_create_args = {} if extra_create_args is None else extra_create_args

    cs = pyrax.cloudservers
    cnw = pyrax.cloud_networks
    if not cnw:
        module.fail_json(msg='Failed to instantiate client. This '
                             'typically indicates an invalid region or an '
                             'incorrectly capitalized region name.')

    if state == 'present' or (state == 'absent' and instance_ids is None):
        if not boot_from_volume and not boot_volume and not image:
            module.fail_json(msg='image is required for the "rax" module')

        for arg, value in dict(name=name, flavor=flavor).items():
            if not value:
                module.fail_json(msg='%s is required for the "rax" module' %
                                     arg)

        if boot_from_volume and not image and not boot_volume:
            module.fail_json(msg='image or boot_volume are required for the '
                                 '"rax" with boot_from_volume')

        if boot_from_volume and image and not boot_volume_size:
            module.fail_json(msg='boot_volume_size is required for the "rax" '
                                 'module with boot_from_volume and image')

        if boot_from_volume and image and boot_volume:
            image = None

    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']

    # Normalize and ensure all metadata values are strings
    for k, v in meta.items():
        if isinstance(v, list):
            meta[k] = ','.join(['%s' % i for i in v])
        elif isinstance(v, dict):
            meta[k] = json.dumps(v)
        elif not isinstance(v, string_types):
            meta[k] = '%s' % v

    # 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

    if image:
        image = rax_find_image(module, pyrax, image)

    nics = []
    if networks:
        for network in networks:
            nics.extend(rax_find_network(module, pyrax, network))

    # act on the state
    if state == 'present':
        # 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"')

            if auto_increment:
                numbers = set()

                # See if the name is a printf like string, if not append
                # %d to the end
                try:
                    name % 0
                except TypeError as e:
                    if e.message.startswith('not all'):
                        name = '%s%%d' % name
                    else:
                        module.fail_json(msg=e.message)

                # regex pattern to match printf formatting
                pattern = re.sub(r'%\d*[sd]', r'(\d+)', name)
                for server in cs.servers.list():
                    # Ignore DELETED servers
                    if server.status == 'DELETED':
                        continue
                    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:  # Not auto incrementing
                for server in cs.servers.list():
                    # Ignore DELETED servers
                    if server.status == 'DELETED':
                        continue
                    if server.metadata.get('group') == group:
                        servers.append(server)
                # available_numbers not needed here, we inspect auto_increment
                # again later

            # 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
                else:
                    count = diff

            if len(servers) > count:
                # We have more servers than we need, set state='absent'
                # and delete the extras, this should delete the oldest
                state = 'absent'
                kept = servers[:count]
                del servers[:count]
                instance_ids = []
                for server in servers:
                    instance_ids.append(server.id)
                delete(module, instance_ids=instance_ids, wait=wait,
                       wait_timeout=wait_timeout, kept=kept)
            elif len(servers) < count:
                # we have fewer servers than we need
                if auto_increment:
                    # auto incrementing server numbers
                    names = []
                    name_slice = count - len(servers)
                    numbers_to_use = available_numbers[:name_slice]
                    for number in numbers_to_use:
                        names.append(name % number)
                else:
                    # We are not auto incrementing server numbers,
                    # create a list of 'name' that matches how many we need
                    names = [name] * (count - len(servers))
            else:
                # we have the right number of servers, just return info
                # about all of the matched servers
                instances = []
                instance_ids = []
                for server in servers:
                    instances.append(rax_to_dict(server, 'server'))
                    instance_ids.append(server.id)
                module.exit_json(changed=False, action=None,
                                 instances=instances,
                                 success=[], error=[], timeout=[],
                                 instance_ids={'instances': instance_ids,
                                               'success': [], 'error': [],
                                               'timeout': []})
        else:  # not called with exact_count=True
            if group is not None:
                if auto_increment:
                    # we are auto incrementing server numbers, but not with
                    # exact_count
                    numbers = set()

                    # See if the name is a printf like string, if not append
                    # %d to the end
                    try:
                        name % 0
                    except TypeError as e:
                        if e.message.startswith('not all'):
                            name = '%s%%d' % name
                        else:
                            module.fail_json(msg=e.message)

                    # regex pattern to match printf formatting
                    pattern = re.sub(r'%\d*[sd]', r'(\d+)', name)
                    for server in cs.servers.list():
                        # Ignore DELETED servers
                        if server.status == 'DELETED':
                            continue
                        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:
                    # Not auto incrementing
                    names = [name] * count
            else:
                # No group was specified, and not using exact_count
                # Perform more simplistic matching
                search_opts = {
                    'name': '^%s$' % name,
                    'flavor': flavor
                }
                servers = []
                for server in cs.servers.list(search_opts=search_opts):
                    # Ignore DELETED servers
                    if server.status == 'DELETED':
                        continue

                    if not rax_find_server_image(module, server, image,
                                                 boot_volume):
                        continue

                    # Ignore servers with non matching metadata
                    if server.metadata != meta:
                        continue
                    servers.append(server)

                if len(servers) >= count:
                    # We have more servers than were requested, don't do
                    # anything. Not running with exact_count=True, so we assume
                    # more is OK
                    instances = []
                    for server in servers:
                        instances.append(rax_to_dict(server, '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': []})

                # We need more servers to reach out target, create names for
                # them, we aren't performing auto_increment here
                names = [name] * (count - len(servers))

        block_device_mapping_v2 = []
        if boot_from_volume:
            mapping = {
                'boot_index': '0',
                'delete_on_termination': boot_volume_terminate,
                'destination_type': 'volume',
            }
            if image:
                mapping.update({
                    'uuid': image,
                    'source_type': 'image',
                    'volume_size': boot_volume_size,
                })
                image = None
            elif boot_volume:
                volume = rax_find_volume(module, pyrax, boot_volume)
                mapping.update({
                    'uuid': pyrax.utils.get_id(volume),
                    'source_type': 'volume',
                })
            block_device_mapping_v2.append(mapping)

        create(module, names=names, flavor=flavor, image=image,
               meta=meta, key_name=key_name, files=files, wait=wait,
               wait_timeout=wait_timeout, disk_config=disk_config, group=group,
               nics=nics, extra_create_args=extra_create_args,
               user_data=user_data, config_drive=config_drive,
               existing=servers,
               block_device_mapping_v2=block_device_mapping_v2)

    elif state == 'absent':
        if instance_ids is None:
            # We weren't given an explicit list of server IDs to delete
            # Let's match instead
            search_opts = {
                'name': '^%s$' % name,
                'flavor': flavor
            }
            for server in cs.servers.list(search_opts=search_opts):
                # Ignore DELETED servers
                if server.status == 'DELETED':
                    continue

                if not rax_find_server_image(module, server, image,
                                             boot_volume):
                    continue

                # Ignore servers with non matching metadata
                if meta != server.metadata:
                    continue

                servers.append(server)

            # Build a list of server IDs to delete
            instance_ids = []
            for server in servers:
                if len(instance_ids) < count:
                    instance_ids.append(server.id)
                else:
                    break

        if not instance_ids:
            # No server IDs were matched for deletion, or no IDs were
            # explicitly provided, just exit and don't do anything
            module.exit_json(changed=False, action=None, instances=[],
                             success=[], error=[], timeout=[],
                             instance_ids={'instances': [],
                                           'success': [], 'error': [],
                                           'timeout': []})

        delete(module, instance_ids=instance_ids, wait=wait,
               wait_timeout=wait_timeout)
def cloud_block_storage_attachments(module, state, volume, server, device,
                                    wait, wait_timeout):
    cbs = pyrax.cloud_blockstorage
    cs = pyrax.cloudservers

    if cbs is None or cs is None:
        module.fail_json(msg='Failed to instantiate client. This '
                             'typically indicates an invalid region or an '
                             'incorrectly capitalized region name.')

    changed = False
    instance = {}

    volume = rax_find_volume(module, pyrax, volume)

    if not volume:
        module.fail_json(msg='No matching storage volumes were found')

    if state == 'present':
        server = rax_find_server(module, pyrax, server)

        if (volume.attachments and
                volume.attachments[0]['server_id'] == server.id):
            changed = False
        elif volume.attachments:
            module.fail_json(msg='Volume is attached to another server')
        else:
            try:
                volume.attach_to_instance(server, mountpoint=device)
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

            volume.get()

        for key, value in vars(volume).items():
            if (isinstance(value, NON_CALLABLES) and
                    not key.startswith('_')):
                instance[key] = value

        result = dict(changed=changed)

        if volume.status == 'error':
            result['msg'] = '%s failed to build' % volume.id
        elif wait:
            attempts = wait_timeout // 5
            pyrax.utils.wait_until(volume, 'status', 'in-use',
                                   interval=5, attempts=attempts)

        volume.get()
        result['volume'] = rax_to_dict(volume)

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

    elif state == 'absent':
        server = rax_find_server(module, pyrax, server)

        if (volume.attachments and
                volume.attachments[0]['server_id'] == server.id):
            try:
                volume.detach()
                if wait:
                    pyrax.utils.wait_until(volume, 'status', 'available',
                                           interval=3, attempts=0,
                                           verbose=False)
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

            volume.get()
            changed = True
        elif volume.attachments:
            module.fail_json(msg='Volume is attached to another server')

        result = dict(changed=changed, volume=rax_to_dict(volume))

        if volume.status == 'error':
            result['msg'] = '%s failed to build' % volume.id

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

    module.exit_json(changed=changed, volume=instance)
Example #12
0
def create(module, names=None, flavor=None, image=None, meta=None, key_name=None,
           files=None, wait=True, wait_timeout=300, disk_config=None,
           group=None, nics=None, extra_create_args=None, user_data=None,
           config_drive=False, existing=None, block_device_mapping_v2=None):
    names = [] if names is None else names
    meta = {} if meta is None else meta
    files = {} if files is None else files
    nics = [] if nics is None else nics
    extra_create_args = {} if extra_create_args is None else extra_create_args
    existing = [] if existing is None else existing
    block_device_mapping_v2 = [] if block_device_mapping_v2 is None else block_device_mapping_v2

    cs = pyrax.cloudservers
    changed = False

    if user_data:
        config_drive = True

    if user_data and os.path.isfile(os.path.expanduser(user_data)):
        try:
            user_data = os.path.expanduser(user_data)
            f = open(user_data)
            user_data = f.read()
            f.close()
        except Exception as e:
            module.fail_json(msg='Failed to load %s' % user_data)

    # Handle the file contents
    for rpath in files.keys():
        lpath = os.path.expanduser(files[rpath])
        try:
            fileobj = open(lpath, 'r')
            files[rpath] = fileobj.read()
            fileobj.close()
        except Exception as e:
            module.fail_json(msg='Failed to load %s' % lpath)
    try:
        servers = []
        bdmv2 = block_device_mapping_v2
        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,
                                             disk_config=disk_config,
                                             config_drive=config_drive,
                                             userdata=user_data,
                                             block_device_mapping_v2=bdmv2,
                                             **extra_create_args))
    except Exception as e:
        if e.message:
            msg = str(e.message)
        else:
            msg = repr(e)
        module.fail_json(msg=msg)
    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 Exception:
                    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 Exception:
            server.status = 'ERROR'
        instance = rax_to_dict(server, 'server')
        if server.status == 'ACTIVE' or not wait:
            success.append(instance)
        elif server.status == 'ERROR':
            error.append(instance)
        elif wait:
            timeout.append(instance)

    untouched = [rax_to_dict(s, 'server') for s in existing]
    instances = success + untouched

    results = {
        'changed': changed,
        'action': 'create',
        'instances': instances,
        'success': success,
        'error': error,
        'timeout': timeout,
        'instance_ids': {
            'instances': [i['id'] for i in instances],
            '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)
Example #13
0
def delete(module, instance_ids=None, wait=True, wait_timeout=300, kept=None):
    instance_ids = [] if instance_ids is None else instance_ids
    kept = [] if kept is None else kept

    cs = pyrax.cloudservers

    changed = False
    instances = {}
    servers = []

    for instance_id in instance_ids:
        servers.append(cs.servers.get(instance_id))

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

        instance = rax_to_dict(server, '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
                try:
                    server.get()
                except Exception:
                    instances[instance_id]['status'] = 'DELETED'
                    instances[instance_id]['rax_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())

    instances = [rax_to_dict(s, 'server') for s in kept]

    results = {
        'changed': changed,
        'action': 'delete',
        'instances': instances,
        'success': success,
        'error': error,
        'timeout': timeout,
        'instance_ids': {
            'instances': [i['id'] for i in instances],
            '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 cloud_load_balancer_ssl(module, loadbalancer, state, enabled, private_key,
                            certificate, intermediate_certificate, secure_port,
                            secure_traffic_only, https_redirect, wait,
                            wait_timeout):
    # Validate arguments.

    if state == 'present':
        if not private_key:
            module.fail_json(msg="private_key must be provided.")
        else:
            private_key = private_key.strip()

        if not certificate:
            module.fail_json(msg="certificate must be provided.")
        else:
            certificate = certificate.strip()

    attempts = wait_timeout // 5

    # Locate the load balancer.

    balancer = rax_find_loadbalancer(module, pyrax, loadbalancer)
    existing_ssl = balancer.get_ssl_termination()

    changed = False

    if state == 'present':
        # Apply or reconfigure SSL termination on the load balancer.
        ssl_attrs = dict(securePort=secure_port,
                         privatekey=private_key,
                         certificate=certificate,
                         intermediateCertificate=intermediate_certificate,
                         enabled=enabled,
                         secureTrafficOnly=secure_traffic_only)

        needs_change = False

        if existing_ssl:
            for ssl_attr, value in ssl_attrs.items():
                if ssl_attr == 'privatekey':
                    # The private key is not included in get_ssl_termination's
                    # output (as it shouldn't be). Also, if you're changing the
                    # private key, you'll also be changing the certificate,
                    # so we don't lose anything by not checking it.
                    continue

                if value is not None and existing_ssl.get(ssl_attr) != value:
                    # module.fail_json(msg='Unnecessary change', attr=ssl_attr, value=value, existing=existing_ssl.get(ssl_attr))
                    needs_change = True
        else:
            needs_change = True

        if needs_change:
            try:
                balancer.add_ssl_termination(**ssl_attrs)
            except pyrax.exceptions.PyraxException as e:
                module.fail_json(msg='%s' % e.message)
            changed = True
    elif state == 'absent':
        # Remove SSL termination if it's already configured.
        if existing_ssl:
            try:
                balancer.delete_ssl_termination()
            except pyrax.exceptions.PyraxException as e:
                module.fail_json(msg='%s' % e.message)
            changed = True

    if https_redirect is not None and balancer.httpsRedirect != https_redirect:
        if changed:
            # This wait is unavoidable because load balancers are immutable
            # while the SSL termination changes above are being applied.
            pyrax.utils.wait_for_build(balancer, interval=5, attempts=attempts)

        try:
            balancer.update(httpsRedirect=https_redirect)
        except pyrax.exceptions.PyraxException as e:
            module.fail_json(msg='%s' % e.message)
        changed = True

    if changed and wait:
        pyrax.utils.wait_for_build(balancer, interval=5, attempts=attempts)

    balancer.get()
    new_ssl_termination = balancer.get_ssl_termination()

    # Intentionally omit the private key from the module output, so you don't
    # accidentally echo it with `ansible-playbook -v` or `debug`, and the
    # certificate, which is just long. Convert other attributes to snake_case
    # and include https_redirect at the top-level.
    if new_ssl_termination:
        new_ssl = dict(
            enabled=new_ssl_termination['enabled'],
            secure_port=new_ssl_termination['securePort'],
            secure_traffic_only=new_ssl_termination['secureTrafficOnly'])
    else:
        new_ssl = None

    result = dict(changed=changed,
                  https_redirect=balancer.httpsRedirect,
                  ssl_termination=new_ssl,
                  balancer=rax_to_dict(balancer, 'clb'))
    success = True

    if balancer.status == 'ERROR':
        result['msg'] = '%s failed to build' % balancer.id
        success = False
    elif wait and balancer.status not in ('ACTIVE', 'ERROR'):
        result['msg'] = 'Timeout waiting on %s' % balancer.id
        success = False

    if success:
        module.exit_json(**result)
    else:
        module.fail_json(**result)
Example #15
0
def cloud_block_storage(module, state, name, description, meta, size,
                        snapshot_id, volume_type, wait, wait_timeout,
                        image):
    changed = False
    volume = None
    instance = {}

    cbs = pyrax.cloud_blockstorage

    if cbs is None:
        module.fail_json(msg='Failed to instantiate client. This '
                             'typically indicates an invalid region or an '
                             'incorrectly capitalized region name.')

    if image:
        # pyrax<1.9.3 did not have support for specifying an image when
        # creating a volume which is required for bootable volumes
        if LooseVersion(pyrax.version.version) < LooseVersion('1.9.3'):
            module.fail_json(msg='Creating a bootable volume requires '
                                 'pyrax>=1.9.3')
        image = rax_find_image(module, pyrax, image)

    volume = rax_find_volume(module, pyrax, name)

    if state == 'present':
        if not volume:
            kwargs = dict()
            if image:
                kwargs['image'] = image
            try:
                volume = cbs.create(name, size=size, volume_type=volume_type,
                                    description=description,
                                    metadata=meta,
                                    snapshot_id=snapshot_id, **kwargs)
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)
            else:
                if wait:
                    attempts = wait_timeout // 5
                    pyrax.utils.wait_for_build(volume, interval=5,
                                               attempts=attempts)

        volume.get()
        instance = rax_to_dict(volume)

        result = dict(changed=changed, volume=instance)

        if volume.status == 'error':
            result['msg'] = '%s failed to build' % volume.id
        elif wait and volume.status not in VOLUME_STATUS:
            result['msg'] = 'Timeout waiting on %s' % volume.id

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

    elif state == 'absent':
        if volume:
            instance = rax_to_dict(volume)
            try:
                volume.delete()
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

    module.exit_json(changed=changed, volume=instance)
Example #16
0
def rax_dns_record(module,
                   comment=None,
                   data=None,
                   domain=None,
                   name=None,
                   overwrite=True,
                   priority=None,
                   record_type='A',
                   state='present',
                   ttl=7200):
    """Function for manipulating record types other than PTR"""

    changed = False

    dns = pyrax.cloud_dns
    if not dns:
        module.fail_json(msg='Failed to instantiate client. This '
                         'typically indicates an invalid region or an '
                         'incorrectly capitalized region name.')

    if state == 'present':
        if not priority and record_type in ['MX', 'SRV']:
            module.fail_json(msg='A "priority" attribute is required for '
                             'creating a MX or SRV record')

        try:
            domain = dns.find(name=domain)
        except Exception as e:
            module.fail_json(msg='%s' % e.message)

        try:
            if overwrite:
                record = domain.find_record(record_type, name=name)
            else:
                record = domain.find_record(record_type, name=name, data=data)
        except pyrax.exceptions.DomainRecordNotUnique as e:
            module.fail_json(
                msg='overwrite=true and there are multiple matching records')
        except pyrax.exceptions.DomainRecordNotFound as e:
            try:
                record_data = {
                    'type': record_type,
                    'name': name,
                    'data': data,
                    'ttl': ttl
                }
                if comment:
                    record_data.update(dict(comment=comment))
                if priority and record_type.upper() in ['MX', 'SRV']:
                    record_data.update(dict(priority=priority))

                record = domain.add_records([record_data])[0]
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

        update = {}
        if comment != getattr(record, 'comment', None):
            update['comment'] = comment
        if ttl != getattr(record, 'ttl', None):
            update['ttl'] = ttl
        if priority != getattr(record, 'priority', None):
            update['priority'] = priority
        if data != getattr(record, 'data', None):
            update['data'] = data

        if update:
            try:
                record.update(**update)
                changed = True
                record.get()
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

    elif state == 'absent':
        try:
            domain = dns.find(name=domain)
        except Exception as e:
            module.fail_json(msg='%s' % e.message)

        try:
            record = domain.find_record(record_type, name=name, data=data)
        except pyrax.exceptions.DomainRecordNotFound as e:
            record = {}
        except pyrax.exceptions.DomainRecordNotUnique as e:
            module.fail_json(msg='%s' % e.message)

        if record:
            try:
                record.delete()
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

    module.exit_json(changed=changed, record=rax_to_dict(record))
def rax_asg(module,
            cooldown=300,
            disk_config=None,
            files=None,
            flavor=None,
            image=None,
            key_name=None,
            loadbalancers=None,
            meta=None,
            min_entities=0,
            max_entities=0,
            name=None,
            networks=None,
            server_name=None,
            state='present',
            user_data=None,
            config_drive=False,
            wait=True,
            wait_timeout=300):
    files = {} if files is None else files
    loadbalancers = [] if loadbalancers is None else loadbalancers
    meta = {} if meta is None else meta
    networks = [] if networks is None else networks

    changed = False

    au = pyrax.autoscale
    if not au:
        module.fail_json(msg='Failed to instantiate clients. This '
                         'typically indicates an invalid region or an '
                         'incorrectly capitalized region name.')

    if user_data:
        config_drive = True

    if user_data and os.path.isfile(user_data):
        try:
            f = open(user_data)
            user_data = f.read()
            f.close()
        except Exception as e:
            module.fail_json(msg='Failed to load %s' % user_data)

    if state == 'present':
        # Normalize and ensure all metadata values are strings
        if meta:
            for k, v in meta.items():
                if isinstance(v, list):
                    meta[k] = ','.join(['%s' % i for i in v])
                elif isinstance(v, dict):
                    meta[k] = json.dumps(v)
                elif not isinstance(v, string_types):
                    meta[k] = '%s' % v

        if image:
            image = rax_find_image(module, pyrax, image)

        nics = []
        if networks:
            for network in networks:
                nics.extend(rax_find_network(module, pyrax, network))

            for nic in nics:
                # pyrax is currently returning net-id, but we need uuid
                # this check makes this forward compatible for a time when
                # pyrax uses uuid instead
                if nic.get('net-id'):
                    nic.update(uuid=nic['net-id'])
                    del nic['net-id']

        # Handle the file contents
        personality = []
        if files:
            for rpath in files.keys():
                lpath = os.path.expanduser(files[rpath])
                try:
                    f = open(lpath, 'r')
                    personality.append({'path': rpath, 'contents': f.read()})
                    f.close()
                except Exception as e:
                    module.fail_json(msg='Failed to load %s' % lpath)

        lbs = []
        if loadbalancers:
            for lb in loadbalancers:
                try:
                    lb_id = int(lb.get('id'))
                except (ValueError, TypeError):
                    module.fail_json(msg='Load balancer ID is not an integer: '
                                     '%s' % lb.get('id'))
                try:
                    port = int(lb.get('port'))
                except (ValueError, TypeError):
                    module.fail_json(msg='Load balancer port is not an '
                                     'integer: %s' % lb.get('port'))
                if not lb_id or not port:
                    continue
                lbs.append((lb_id, port))

        try:
            sg = au.find(name=name)
        except pyrax.exceptions.NoUniqueMatch as e:
            module.fail_json(msg='%s' % e.message)
        except pyrax.exceptions.NotFound:
            try:
                sg = au.create(name,
                               cooldown=cooldown,
                               min_entities=min_entities,
                               max_entities=max_entities,
                               launch_config_type='launch_server',
                               server_name=server_name,
                               image=image,
                               flavor=flavor,
                               disk_config=disk_config,
                               metadata=meta,
                               personality=personality,
                               networks=nics,
                               load_balancers=lbs,
                               key_name=key_name,
                               config_drive=config_drive,
                               user_data=user_data)
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

        if not changed:
            # Scaling Group Updates
            group_args = {}
            if cooldown != sg.cooldown:
                group_args['cooldown'] = cooldown

            if min_entities != sg.min_entities:
                group_args['min_entities'] = min_entities

            if max_entities != sg.max_entities:
                group_args['max_entities'] = max_entities

            if group_args:
                changed = True
                sg.update(**group_args)

            # Launch Configuration Updates
            lc = sg.get_launch_config()
            lc_args = {}
            if server_name != lc.get('name'):
                lc_args['server_name'] = server_name

            if image != lc.get('image'):
                lc_args['image'] = image

            if flavor != lc.get('flavor'):
                lc_args['flavor'] = flavor

            disk_config = disk_config or 'AUTO'
            if ((disk_config or lc.get('disk_config'))
                    and disk_config != lc.get('disk_config', 'AUTO')):
                lc_args['disk_config'] = disk_config

            if (meta or lc.get('meta')) and meta != lc.get('metadata'):
                lc_args['metadata'] = meta

            test_personality = []
            for p in personality:
                test_personality.append({
                    'path':
                    p['path'],
                    'contents':
                    base64.b64encode(p['contents'])
                })
            if ((test_personality or lc.get('personality'))
                    and test_personality != lc.get('personality')):
                lc_args['personality'] = personality

            if nics != lc.get('networks'):
                lc_args['networks'] = nics

            if lbs != lc.get('load_balancers'):
                # Work around for https://github.com/rackspace/pyrax/pull/393
                lc_args['load_balancers'] = sg.manager._resolve_lbs(lbs)

            if key_name != lc.get('key_name'):
                lc_args['key_name'] = key_name

            if config_drive != lc.get('config_drive', False):
                lc_args['config_drive'] = config_drive

            if (user_data
                    and base64.b64encode(user_data) != lc.get('user_data')):
                lc_args['user_data'] = user_data

            if lc_args:
                # Work around for https://github.com/rackspace/pyrax/pull/389
                if 'flavor' not in lc_args:
                    lc_args['flavor'] = lc.get('flavor')
                changed = True
                sg.update_launch_config(**lc_args)

            sg.get()

        if wait:
            end_time = time.time() + wait_timeout
            infinite = wait_timeout == 0
            while infinite or time.time() < end_time:
                state = sg.get_state()
                if state["pending_capacity"] == 0:
                    break

                time.sleep(5)

        module.exit_json(changed=changed, autoscale_group=rax_to_dict(sg))

    else:
        try:
            sg = au.find(name=name)
            sg.delete()
            changed = True
        except pyrax.exceptions.NotFound as e:
            sg = {}
        except Exception as e:
            module.fail_json(msg='%s' % e.message)

        module.exit_json(changed=changed, autoscale_group=rax_to_dict(sg))
Example #18
0
def save_instance(module, name, flavor, volume, cdb_type, cdb_version, wait,
                  wait_timeout):

    for arg, value in dict(name=name,
                           flavor=flavor,
                           volume=volume,
                           type=cdb_type,
                           version=cdb_version).items():
        if not value:
            module.fail_json(msg='%s is required for the "rax_cdb"'
                             ' module' % arg)

    if not (volume >= 1 and volume <= 150):
        module.fail_json(msg='volume is required to be between 1 and 150')

    cdb = pyrax.cloud_databases

    flavors = []
    for item in cdb.list_flavors():
        flavors.append(item.id)

    if not (flavor in flavors):
        module.fail_json(msg='unexisting flavor reference "%s"' % str(flavor))

    changed = False

    instance = find_instance(name)

    if not instance:
        action = 'create'
        try:
            instance = cdb.create(name=name,
                                  flavor=flavor,
                                  volume=volume,
                                  type=cdb_type,
                                  version=cdb_version)
        except Exception as e:
            module.fail_json(msg='%s' % e.message)
        else:
            changed = True

    else:
        action = None

        if instance.volume.size != volume:
            action = 'resize'
            if instance.volume.size > volume:
                module.fail_json(changed=False,
                                 action=action,
                                 msg='The new volume size must be larger than '
                                 'the current volume size',
                                 cdb=rax_to_dict(instance))
            instance.resize_volume(volume)
            changed = True

        if int(instance.flavor.id) != flavor:
            action = 'resize'
            pyrax.utils.wait_until(instance,
                                   'status',
                                   'ACTIVE',
                                   attempts=wait_timeout)
            instance.resize(flavor)
            changed = True

    if wait:
        pyrax.utils.wait_until(instance,
                               'status',
                               'ACTIVE',
                               attempts=wait_timeout)

    if wait and instance.status != 'ACTIVE':
        module.fail_json(changed=changed,
                         action=action,
                         cdb=rax_to_dict(instance),
                         msg='Timeout waiting for "%s" databases instance to '
                         'be created' % name)

    module.exit_json(changed=changed, action=action, cdb=rax_to_dict(instance))
def rax_asp(module,
            at=None,
            change=0,
            cron=None,
            cooldown=300,
            desired_capacity=0,
            is_percent=False,
            name=None,
            policy_type=None,
            scaling_group=None,
            state='present'):
    changed = False

    au = pyrax.autoscale
    if not au:
        module.fail_json(msg='Failed to instantiate client. This '
                         'typically indicates an invalid region or an '
                         'incorrectly capitalized region name.')

    try:
        UUID(scaling_group)
    except ValueError:
        try:
            sg = au.find(name=scaling_group)
        except Exception as e:
            module.fail_json(msg='%s' % e.message)
    else:
        try:
            sg = au.get(scaling_group)
        except Exception as e:
            module.fail_json(msg='%s' % e.message)

    if state == 'present':
        policies = filter(lambda p: name == p.name, sg.list_policies())
        if len(policies) > 1:
            module.fail_json(msg='No unique policy match found by name')
        if at:
            args = dict(at=at)
        elif cron:
            args = dict(cron=cron)
        else:
            args = None

        if not policies:
            try:
                policy = sg.add_policy(name,
                                       policy_type=policy_type,
                                       cooldown=cooldown,
                                       change=change,
                                       is_percent=is_percent,
                                       desired_capacity=desired_capacity,
                                       args=args)
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

        else:
            policy = policies[0]
            kwargs = {}
            if policy_type != policy.type:
                kwargs['policy_type'] = policy_type

            if cooldown != policy.cooldown:
                kwargs['cooldown'] = cooldown

            if hasattr(policy, 'change') and change != policy.change:
                kwargs['change'] = change

            if hasattr(policy, 'changePercent') and is_percent is False:
                kwargs['change'] = change
                kwargs['is_percent'] = False
            elif hasattr(policy, 'change') and is_percent is True:
                kwargs['change'] = change
                kwargs['is_percent'] = True

            if hasattr(policy, 'desiredCapacity') and change:
                kwargs['change'] = change
            elif (
                (hasattr(policy, 'change') or hasattr(policy, 'changePercent'))
                    and desired_capacity):
                kwargs['desired_capacity'] = desired_capacity

            if hasattr(policy, 'args') and args != policy.args:
                kwargs['args'] = args

            if kwargs:
                policy.update(**kwargs)
                changed = True

        policy.get()

        module.exit_json(changed=changed, autoscale_policy=rax_to_dict(policy))

    else:
        try:
            policies = filter(lambda p: name == p.name, sg.list_policies())
            if len(policies) > 1:
                module.fail_json(msg='No unique policy match found by name')
            elif not policies:
                policy = {}
            else:
                policy.delete()
                changed = True
        except Exception as e:
            module.fail_json(msg='%s' % e.message)

        module.exit_json(changed=changed, autoscale_policy=rax_to_dict(policy))
Example #20
0
def rax_dns_record_ptr(module,
                       data=None,
                       comment=None,
                       loadbalancer=None,
                       name=None,
                       server=None,
                       state='present',
                       ttl=7200):
    changed = False
    results = []

    dns = pyrax.cloud_dns

    if not dns:
        module.fail_json(msg='Failed to instantiate client. This '
                         'typically indicates an invalid region or an '
                         'incorrectly capitalized region name.')

    if loadbalancer:
        item = rax_find_loadbalancer(module, pyrax, loadbalancer)
    elif server:
        item = rax_find_server(module, pyrax, server)

    if state == 'present':
        current = dns.list_ptr_records(item)
        for record in current:
            if record.data == data:
                if record.ttl != ttl or record.name != name:
                    try:
                        dns.update_ptr_record(item, record, name, data, ttl)
                        changed = True
                    except Exception as e:
                        module.fail_json(msg='%s' % e.message)
                    record.ttl = ttl
                    record.name = name
                    results.append(rax_to_dict(record))
                    break
                else:
                    results.append(rax_to_dict(record))
                    break

        if not results:
            record = dict(name=name,
                          type='PTR',
                          data=data,
                          ttl=ttl,
                          comment=comment)
            try:
                results = dns.add_ptr_records(item, [record])
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

        module.exit_json(changed=changed, records=results)

    elif state == 'absent':
        current = dns.list_ptr_records(item)
        for record in current:
            if record.data == data:
                results.append(rax_to_dict(record))
                break

        if results:
            try:
                dns.delete_ptr_records(item, data)
                changed = True
            except Exception as e:
                module.fail_json(msg='%s' % e.message)

        module.exit_json(changed=changed, records=results)