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