1#!/usr/bin/python 2 3# (c) 2013, Paul Durivage <paul.durivage@rackspace.com> 4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 6from __future__ import absolute_import, division, print_function 7__metaclass__ = type 8 9 10ANSIBLE_METADATA = {'metadata_version': '1.1', 11 'status': ['preview'], 12 'supported_by': 'community'} 13 14 15DOCUMENTATION = ''' 16--- 17module: rax_files 18short_description: Manipulate Rackspace Cloud Files Containers 19description: 20 - Manipulate Rackspace Cloud Files Containers 21version_added: "1.5" 22options: 23 clear_meta: 24 description: 25 - Optionally clear existing metadata when applying metadata to existing containers. 26 Selecting this option is only appropriate when setting type=meta 27 type: bool 28 default: "no" 29 container: 30 description: 31 - The container to use for container or metadata operations. 32 required: true 33 meta: 34 description: 35 - A hash of items to set as metadata values on a container 36 private: 37 description: 38 - Used to set a container as private, removing it from the CDN. B(Warning!) 39 Private containers, if previously made public, can have live objects 40 available until the TTL on cached objects expires 41 type: bool 42 public: 43 description: 44 - Used to set a container as public, available via the Cloud Files CDN 45 type: bool 46 region: 47 description: 48 - Region to create an instance in 49 default: DFW 50 state: 51 description: 52 - Indicate desired state of the resource 53 choices: ['present', 'absent'] 54 default: present 55 ttl: 56 description: 57 - In seconds, set a container-wide TTL for all objects cached on CDN edge nodes. 58 Setting a TTL is only appropriate for containers that are public 59 type: 60 description: 61 - Type of object to do work on, i.e. metadata object or a container object 62 choices: 63 - file 64 - meta 65 default: file 66 web_error: 67 description: 68 - Sets an object to be presented as the HTTP error page when accessed by the CDN URL 69 web_index: 70 description: 71 - Sets an object to be presented as the HTTP index page when accessed by the CDN URL 72author: "Paul Durivage (@angstwad)" 73extends_documentation_fragment: 74 - rackspace 75 - rackspace.openstack 76''' 77 78EXAMPLES = ''' 79- name: "Test Cloud Files Containers" 80 hosts: local 81 gather_facts: no 82 tasks: 83 - name: "List all containers" 84 rax_files: 85 state: list 86 87 - name: "Create container called 'mycontainer'" 88 rax_files: 89 container: mycontainer 90 91 - name: "Create container 'mycontainer2' with metadata" 92 rax_files: 93 container: mycontainer2 94 meta: 95 key: value 96 file_for: someuser@example.com 97 98 - name: "Set a container's web index page" 99 rax_files: 100 container: mycontainer 101 web_index: index.html 102 103 - name: "Set a container's web error page" 104 rax_files: 105 container: mycontainer 106 web_error: error.html 107 108 - name: "Make container public" 109 rax_files: 110 container: mycontainer 111 public: yes 112 113 - name: "Make container public with a 24 hour TTL" 114 rax_files: 115 container: mycontainer 116 public: yes 117 ttl: 86400 118 119 - name: "Make container private" 120 rax_files: 121 container: mycontainer 122 private: yes 123 124- name: "Test Cloud Files Containers Metadata Storage" 125 hosts: local 126 gather_facts: no 127 tasks: 128 - name: "Get mycontainer2 metadata" 129 rax_files: 130 container: mycontainer2 131 type: meta 132 133 - name: "Set mycontainer2 metadata" 134 rax_files: 135 container: mycontainer2 136 type: meta 137 meta: 138 uploaded_by: someuser@example.com 139 140 - name: "Remove mycontainer2 metadata" 141 rax_files: 142 container: "mycontainer2" 143 type: meta 144 state: absent 145 meta: 146 key: "" 147 file_for: "" 148''' 149 150try: 151 import pyrax 152 HAS_PYRAX = True 153except ImportError as e: 154 HAS_PYRAX = False 155 156from ansible.module_utils.basic import AnsibleModule 157from ansible.module_utils.rax import rax_argument_spec, rax_required_together, setup_rax_module 158 159 160EXIT_DICT = dict(success=True) 161META_PREFIX = 'x-container-meta-' 162 163 164def _get_container(module, cf, container): 165 try: 166 return cf.get_container(container) 167 except pyrax.exc.NoSuchContainer as e: 168 module.fail_json(msg=e.message) 169 170 171def _fetch_meta(module, container): 172 EXIT_DICT['meta'] = dict() 173 try: 174 for k, v in container.get_metadata().items(): 175 split_key = k.split(META_PREFIX)[-1] 176 EXIT_DICT['meta'][split_key] = v 177 except Exception as e: 178 module.fail_json(msg=e.message) 179 180 181def meta(cf, module, container_, state, meta_, clear_meta): 182 c = _get_container(module, cf, container_) 183 184 if meta_ and state == 'present': 185 try: 186 meta_set = c.set_metadata(meta_, clear=clear_meta) 187 except Exception as e: 188 module.fail_json(msg=e.message) 189 elif meta_ and state == 'absent': 190 remove_results = [] 191 for k, v in meta_.items(): 192 c.remove_metadata_key(k) 193 remove_results.append(k) 194 EXIT_DICT['deleted_meta_keys'] = remove_results 195 elif state == 'absent': 196 remove_results = [] 197 for k, v in c.get_metadata().items(): 198 c.remove_metadata_key(k) 199 remove_results.append(k) 200 EXIT_DICT['deleted_meta_keys'] = remove_results 201 202 _fetch_meta(module, c) 203 _locals = locals().keys() 204 205 EXIT_DICT['container'] = c.name 206 if 'meta_set' in _locals or 'remove_results' in _locals: 207 EXIT_DICT['changed'] = True 208 209 module.exit_json(**EXIT_DICT) 210 211 212def container(cf, module, container_, state, meta_, clear_meta, ttl, public, 213 private, web_index, web_error): 214 if public and private: 215 module.fail_json(msg='container cannot be simultaneously ' 216 'set to public and private') 217 218 if state == 'absent' and (meta_ or clear_meta or public or private or web_index or web_error): 219 module.fail_json(msg='state cannot be omitted when setting/removing ' 220 'attributes on a container') 221 222 if state == 'list': 223 # We don't care if attributes are specified, let's list containers 224 EXIT_DICT['containers'] = cf.list_containers() 225 module.exit_json(**EXIT_DICT) 226 227 try: 228 c = cf.get_container(container_) 229 except pyrax.exc.NoSuchContainer as e: 230 # Make the container if state=present, otherwise bomb out 231 if state == 'present': 232 try: 233 c = cf.create_container(container_) 234 except Exception as e: 235 module.fail_json(msg=e.message) 236 else: 237 EXIT_DICT['changed'] = True 238 EXIT_DICT['created'] = True 239 else: 240 module.fail_json(msg=e.message) 241 else: 242 # Successfully grabbed a container object 243 # Delete if state is absent 244 if state == 'absent': 245 try: 246 cont_deleted = c.delete() 247 except Exception as e: 248 module.fail_json(msg=e.message) 249 else: 250 EXIT_DICT['deleted'] = True 251 252 if meta_: 253 try: 254 meta_set = c.set_metadata(meta_, clear=clear_meta) 255 except Exception as e: 256 module.fail_json(msg=e.message) 257 finally: 258 _fetch_meta(module, c) 259 260 if ttl: 261 try: 262 c.cdn_ttl = ttl 263 except Exception as e: 264 module.fail_json(msg=e.message) 265 else: 266 EXIT_DICT['ttl'] = c.cdn_ttl 267 268 if public: 269 try: 270 cont_public = c.make_public() 271 except Exception as e: 272 module.fail_json(msg=e.message) 273 else: 274 EXIT_DICT['container_urls'] = dict(url=c.cdn_uri, 275 ssl_url=c.cdn_ssl_uri, 276 streaming_url=c.cdn_streaming_uri, 277 ios_uri=c.cdn_ios_uri) 278 279 if private: 280 try: 281 cont_private = c.make_private() 282 except Exception as e: 283 module.fail_json(msg=e.message) 284 else: 285 EXIT_DICT['set_private'] = True 286 287 if web_index: 288 try: 289 cont_web_index = c.set_web_index_page(web_index) 290 except Exception as e: 291 module.fail_json(msg=e.message) 292 else: 293 EXIT_DICT['set_index'] = True 294 finally: 295 _fetch_meta(module, c) 296 297 if web_error: 298 try: 299 cont_err_index = c.set_web_error_page(web_error) 300 except Exception as e: 301 module.fail_json(msg=e.message) 302 else: 303 EXIT_DICT['set_error'] = True 304 finally: 305 _fetch_meta(module, c) 306 307 EXIT_DICT['container'] = c.name 308 EXIT_DICT['objs_in_container'] = c.object_count 309 EXIT_DICT['total_bytes'] = c.total_bytes 310 311 _locals = locals().keys() 312 if ('cont_deleted' in _locals 313 or 'meta_set' in _locals 314 or 'cont_public' in _locals 315 or 'cont_private' in _locals 316 or 'cont_web_index' in _locals 317 or 'cont_err_index' in _locals): 318 EXIT_DICT['changed'] = True 319 320 module.exit_json(**EXIT_DICT) 321 322 323def cloudfiles(module, container_, state, meta_, clear_meta, typ, ttl, public, 324 private, web_index, web_error): 325 """ Dispatch from here to work with metadata or file objects """ 326 cf = pyrax.cloudfiles 327 328 if cf is None: 329 module.fail_json(msg='Failed to instantiate client. This ' 330 'typically indicates an invalid region or an ' 331 'incorrectly capitalized region name.') 332 333 if typ == "container": 334 container(cf, module, container_, state, meta_, clear_meta, ttl, 335 public, private, web_index, web_error) 336 else: 337 meta(cf, module, container_, state, meta_, clear_meta) 338 339 340def main(): 341 argument_spec = rax_argument_spec() 342 argument_spec.update( 343 dict( 344 container=dict(), 345 state=dict(choices=['present', 'absent', 'list'], 346 default='present'), 347 meta=dict(type='dict', default=dict()), 348 clear_meta=dict(default=False, type='bool'), 349 type=dict(choices=['container', 'meta'], default='container'), 350 ttl=dict(type='int'), 351 public=dict(default=False, type='bool'), 352 private=dict(default=False, type='bool'), 353 web_index=dict(), 354 web_error=dict() 355 ) 356 ) 357 358 module = AnsibleModule( 359 argument_spec=argument_spec, 360 required_together=rax_required_together() 361 ) 362 363 if not HAS_PYRAX: 364 module.fail_json(msg='pyrax is required for this module') 365 366 container_ = module.params.get('container') 367 state = module.params.get('state') 368 meta_ = module.params.get('meta') 369 clear_meta = module.params.get('clear_meta') 370 typ = module.params.get('type') 371 ttl = module.params.get('ttl') 372 public = module.params.get('public') 373 private = module.params.get('private') 374 web_index = module.params.get('web_index') 375 web_error = module.params.get('web_error') 376 377 if state in ['present', 'absent'] and not container_: 378 module.fail_json(msg='please specify a container name') 379 if clear_meta and not typ == 'meta': 380 module.fail_json(msg='clear_meta can only be used when setting ' 381 'metadata') 382 383 setup_rax_module(module, pyrax) 384 cloudfiles(module, container_, state, meta_, clear_meta, typ, ttl, public, 385 private, web_index, web_error) 386 387 388if __name__ == '__main__': 389 main() 390