1# -*- coding: utf-8 -*- 2# This code is part of Ansible, but is an independent component. 3# This particular file snippet, and this file snippet only, is BSD licensed. 4# Modules you write using this snippet, which is embedded dynamically by 5# Ansible still belong to the author of the module, and may assign their own 6# license to the complete work. 7# 8# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2012-2013 9# 10# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) 11 12from __future__ import (absolute_import, division, print_function) 13__metaclass__ = type 14 15 16import os 17import re 18from uuid import UUID 19 20from ansible.module_utils.six import text_type, binary_type 21 22FINAL_STATUSES = ('ACTIVE', 'ERROR') 23VOLUME_STATUS = ('available', 'attaching', 'creating', 'deleting', 'in-use', 24 'error', 'error_deleting') 25 26CLB_ALGORITHMS = ['RANDOM', 'LEAST_CONNECTIONS', 'ROUND_ROBIN', 27 'WEIGHTED_LEAST_CONNECTIONS', 'WEIGHTED_ROUND_ROBIN'] 28CLB_PROTOCOLS = ['DNS_TCP', 'DNS_UDP', 'FTP', 'HTTP', 'HTTPS', 'IMAPS', 29 'IMAPv4', 'LDAP', 'LDAPS', 'MYSQL', 'POP3', 'POP3S', 'SMTP', 30 'TCP', 'TCP_CLIENT_FIRST', 'UDP', 'UDP_STREAM', 'SFTP'] 31 32NON_CALLABLES = (text_type, binary_type, bool, dict, int, list, type(None)) 33PUBLIC_NET_ID = "00000000-0000-0000-0000-000000000000" 34SERVICE_NET_ID = "11111111-1111-1111-1111-111111111111" 35 36 37def rax_slugify(value): 38 """Prepend a key with rax_ and normalize the key name""" 39 return 'rax_%s' % (re.sub(r'[^\w-]', '_', value).lower().lstrip('_')) 40 41 42def rax_clb_node_to_dict(obj): 43 """Function to convert a CLB Node object to a dict""" 44 if not obj: 45 return {} 46 node = obj.to_dict() 47 node['id'] = obj.id 48 node['weight'] = obj.weight 49 return node 50 51 52def rax_to_dict(obj, obj_type='standard'): 53 """Generic function to convert a pyrax object to a dict 54 55 obj_type values: 56 standard 57 clb 58 server 59 60 """ 61 instance = {} 62 for key in dir(obj): 63 value = getattr(obj, key) 64 if obj_type == 'clb' and key == 'nodes': 65 instance[key] = [] 66 for node in value: 67 instance[key].append(rax_clb_node_to_dict(node)) 68 elif (isinstance(value, list) and len(value) > 0 and 69 not isinstance(value[0], NON_CALLABLES)): 70 instance[key] = [] 71 for item in value: 72 instance[key].append(rax_to_dict(item)) 73 elif (isinstance(value, NON_CALLABLES) and not key.startswith('_')): 74 if obj_type == 'server': 75 if key == 'image': 76 if not value: 77 instance['rax_boot_source'] = 'volume' 78 else: 79 instance['rax_boot_source'] = 'local' 80 key = rax_slugify(key) 81 instance[key] = value 82 83 if obj_type == 'server': 84 for attr in ['id', 'accessIPv4', 'name', 'status']: 85 instance[attr] = instance.get(rax_slugify(attr)) 86 87 return instance 88 89 90def rax_find_bootable_volume(module, rax_module, server, exit=True): 91 """Find a servers bootable volume""" 92 cs = rax_module.cloudservers 93 cbs = rax_module.cloud_blockstorage 94 server_id = rax_module.utils.get_id(server) 95 volumes = cs.volumes.get_server_volumes(server_id) 96 bootable_volumes = [] 97 for volume in volumes: 98 vol = cbs.get(volume) 99 if module.boolean(vol.bootable): 100 bootable_volumes.append(vol) 101 if not bootable_volumes: 102 if exit: 103 module.fail_json(msg='No bootable volumes could be found for ' 104 'server %s' % server_id) 105 else: 106 return False 107 elif len(bootable_volumes) > 1: 108 if exit: 109 module.fail_json(msg='Multiple bootable volumes found for server ' 110 '%s' % server_id) 111 else: 112 return False 113 114 return bootable_volumes[0] 115 116 117def rax_find_image(module, rax_module, image, exit=True): 118 """Find a server image by ID or Name""" 119 cs = rax_module.cloudservers 120 try: 121 UUID(image) 122 except ValueError: 123 try: 124 image = cs.images.find(human_id=image) 125 except(cs.exceptions.NotFound, 126 cs.exceptions.NoUniqueMatch): 127 try: 128 image = cs.images.find(name=image) 129 except (cs.exceptions.NotFound, 130 cs.exceptions.NoUniqueMatch): 131 if exit: 132 module.fail_json(msg='No matching image found (%s)' % 133 image) 134 else: 135 return False 136 137 return rax_module.utils.get_id(image) 138 139 140def rax_find_volume(module, rax_module, name): 141 """Find a Block storage volume by ID or name""" 142 cbs = rax_module.cloud_blockstorage 143 try: 144 UUID(name) 145 volume = cbs.get(name) 146 except ValueError: 147 try: 148 volume = cbs.find(name=name) 149 except rax_module.exc.NotFound: 150 volume = None 151 except Exception as e: 152 module.fail_json(msg='%s' % e) 153 return volume 154 155 156def rax_find_network(module, rax_module, network): 157 """Find a cloud network by ID or name""" 158 cnw = rax_module.cloud_networks 159 try: 160 UUID(network) 161 except ValueError: 162 if network.lower() == 'public': 163 return cnw.get_server_networks(PUBLIC_NET_ID) 164 elif network.lower() == 'private': 165 return cnw.get_server_networks(SERVICE_NET_ID) 166 else: 167 try: 168 network_obj = cnw.find_network_by_label(network) 169 except (rax_module.exceptions.NetworkNotFound, 170 rax_module.exceptions.NetworkLabelNotUnique): 171 module.fail_json(msg='No matching network found (%s)' % 172 network) 173 else: 174 return cnw.get_server_networks(network_obj) 175 else: 176 return cnw.get_server_networks(network) 177 178 179def rax_find_server(module, rax_module, server): 180 """Find a Cloud Server by ID or name""" 181 cs = rax_module.cloudservers 182 try: 183 UUID(server) 184 server = cs.servers.get(server) 185 except ValueError: 186 servers = cs.servers.list(search_opts=dict(name='^%s$' % server)) 187 if not servers: 188 module.fail_json(msg='No Server was matched by name, ' 189 'try using the Server ID instead') 190 if len(servers) > 1: 191 module.fail_json(msg='Multiple servers matched by name, ' 192 'try using the Server ID instead') 193 194 # We made it this far, grab the first and hopefully only server 195 # in the list 196 server = servers[0] 197 return server 198 199 200def rax_find_loadbalancer(module, rax_module, loadbalancer): 201 """Find a Cloud Load Balancer by ID or name""" 202 clb = rax_module.cloud_loadbalancers 203 try: 204 found = clb.get(loadbalancer) 205 except Exception: 206 found = [] 207 for lb in clb.list(): 208 if loadbalancer == lb.name: 209 found.append(lb) 210 211 if not found: 212 module.fail_json(msg='No loadbalancer was matched') 213 214 if len(found) > 1: 215 module.fail_json(msg='Multiple loadbalancers matched') 216 217 # We made it this far, grab the first and hopefully only item 218 # in the list 219 found = found[0] 220 221 return found 222 223 224def rax_argument_spec(): 225 """Return standard base dictionary used for the argument_spec 226 argument in AnsibleModule 227 228 """ 229 return dict( 230 api_key=dict(type='str', aliases=['password'], no_log=True), 231 auth_endpoint=dict(type='str'), 232 credentials=dict(type='path', aliases=['creds_file']), 233 env=dict(type='str'), 234 identity_type=dict(type='str', default='rackspace'), 235 region=dict(type='str'), 236 tenant_id=dict(type='str'), 237 tenant_name=dict(type='str'), 238 username=dict(type='str'), 239 validate_certs=dict(type='bool', aliases=['verify_ssl']), 240 ) 241 242 243def rax_required_together(): 244 """Return the default list used for the required_together argument to 245 AnsibleModule""" 246 return [['api_key', 'username']] 247 248 249def setup_rax_module(module, rax_module, region_required=True): 250 """Set up pyrax in a standard way for all modules""" 251 rax_module.USER_AGENT = 'ansible/%s %s' % (module.ansible_version, 252 rax_module.USER_AGENT) 253 254 api_key = module.params.get('api_key') 255 auth_endpoint = module.params.get('auth_endpoint') 256 credentials = module.params.get('credentials') 257 env = module.params.get('env') 258 identity_type = module.params.get('identity_type') 259 region = module.params.get('region') 260 tenant_id = module.params.get('tenant_id') 261 tenant_name = module.params.get('tenant_name') 262 username = module.params.get('username') 263 verify_ssl = module.params.get('validate_certs') 264 265 if env is not None: 266 rax_module.set_environment(env) 267 268 rax_module.set_setting('identity_type', identity_type) 269 if verify_ssl is not None: 270 rax_module.set_setting('verify_ssl', verify_ssl) 271 if auth_endpoint is not None: 272 rax_module.set_setting('auth_endpoint', auth_endpoint) 273 if tenant_id is not None: 274 rax_module.set_setting('tenant_id', tenant_id) 275 if tenant_name is not None: 276 rax_module.set_setting('tenant_name', tenant_name) 277 278 try: 279 username = username or os.environ.get('RAX_USERNAME') 280 if not username: 281 username = rax_module.get_setting('keyring_username') 282 if username: 283 api_key = 'USE_KEYRING' 284 if not api_key: 285 api_key = os.environ.get('RAX_API_KEY') 286 credentials = (credentials or os.environ.get('RAX_CREDENTIALS') or 287 os.environ.get('RAX_CREDS_FILE')) 288 region = (region or os.environ.get('RAX_REGION') or 289 rax_module.get_setting('region')) 290 except KeyError as e: 291 module.fail_json(msg='Unable to load %s' % e.message) 292 293 try: 294 if api_key and username: 295 if api_key == 'USE_KEYRING': 296 rax_module.keyring_auth(username, region=region) 297 else: 298 rax_module.set_credentials(username, api_key=api_key, 299 region=region) 300 elif credentials: 301 credentials = os.path.expanduser(credentials) 302 rax_module.set_credential_file(credentials, region=region) 303 else: 304 raise Exception('No credentials supplied!') 305 except Exception as e: 306 if e.message: 307 msg = str(e.message) 308 else: 309 msg = repr(e) 310 module.fail_json(msg=msg) 311 312 if region_required and region not in rax_module.regions: 313 module.fail_json(msg='%s is not a valid region, must be one of: %s' % 314 (region, ','.join(rax_module.regions))) 315 316 return rax_module 317