rax_files 11.8 KB
Newer Older
Matt Martz committed
1
#!/usr/bin/python
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

# (c) 2013, Paul Durivage <paul.durivage@rackspace.com>
#
# This file is part of Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.

20 21
# This is a DOCUMENTATION stub specific to this module, it extends
# a documentation fragment located in ansible.utils.module_docs_fragments
22 23 24 25 26 27 28 29 30 31 32 33
DOCUMENTATION = '''
---
module: rax_files
short_description: Manipulate Rackspace Cloud Files Containers
description:
  - Manipulate Rackspace Cloud Files Containers
version_added: "1.5"
options:
  clear_meta:
    description:
      - Optionally clear existing metadata when applying metadata to existing containers.
        Selecting this option is only appropriate when setting type=meta
34 35 36
    choices:
      - "yes"
      - "no"
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
    default: "no"
  container:
    description:
      - The container to use for container or metadata operations.
    required: true
  meta:
    description:
      - A hash of items to set as metadata values on a container
  private:
    description:
      - Used to set a container as private, removing it from the CDN.  B(Warning!)
        Private containers, if previously made public, can have live objects
        available until the TTL on cached objects expires
  public:
    description:
      - Used to set a container as public, available via the Cloud Files CDN
  region:
    description:
      - Region to create an instance in
    default: DFW
Paul Durivage committed
57 58 59 60 61
  state:
    description:
      - Indicate desired state of the resource
    choices: ['present', 'absent']
    default: present
62 63 64 65 66 67 68
  ttl:
    description:
      - In seconds, set a container-wide TTL for all objects cached on CDN edge nodes.
        Setting a TTL is only appropriate for containers that are public
  type:
    description:
      - Type of object to do work on, i.e. metadata object or a container object
69 70 71 72
    choices:
      - file
      - meta
    default: file
73 74 75 76 77 78 79
  web_error:
    description:
       - Sets an object to be presented as the HTTP error page when accessed by the CDN URL
  web_index:
    description:
       - Sets an object to be presented as the HTTP index page when accessed by the CDN URL
author: Paul Durivage
80
extends_documentation_fragment: rackspace
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
'''

EXAMPLES = '''
- name: "Test Cloud Files Containers"
  hosts: local
  gather_facts: no
  tasks:
    - name: "List all containers"
      rax_files: state=list

    - name: "Create container called 'mycontainer'"
      rax_files: container=mycontainer

    - name: "Create container 'mycontainer2' with metadata"
      rax_files:
        container: mycontainer2
        meta:
          key: value
Paul Durivage committed
99
          file_for: someuser@example.com
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129

    - name: "Set a container's web index page"
      rax_files: container=mycontainer web_index=index.html

    - name: "Set a container's web error page"
      rax_files: container=mycontainer web_error=error.html

    - name: "Make container public"
      rax_files: container=mycontainer public=yes

    - name: "Make container public with a 24 hour TTL"
      rax_files: container=mycontainer public=yes ttl=86400

    - name: "Make container private"
      rax_files: container=mycontainer private=yes

- name: "Test Cloud Files Containers Metadata Storage"
  hosts: local
  gather_facts: no
  tasks:
    - name: "Get mycontainer2 metadata"
      rax_files:
        container: mycontainer2
        type: meta

    - name: "Set mycontainer2 metadata"
      rax_files:
        container: mycontainer2
        type: meta
        meta:
Paul Durivage committed
130
          uploaded_by: someuser@example.com
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145

    - name: "Remove mycontainer2 metadata"
      rax_files:
        container: "mycontainer2"
        type: meta
        state: absent
        meta:
          key: ""
          file_for: ""
'''

from ansible import __version__

try:
    import pyrax
Matt Martz committed
146
    HAS_PYRAX = True
147
except ImportError, e:
Matt Martz committed
148
    HAS_PYRAX = False
149

150
EXIT_DICT = dict(success=True)
151 152 153 154 155 156 157 158 159 160 161 162
META_PREFIX = 'x-container-meta-'
USER_AGENT = "Ansible/%s via pyrax" % __version__


def _get_container(module, cf, container):
    try:
        return cf.get_container(container)
    except pyrax.exc.NoSuchContainer, e:
        module.fail_json(msg=e.message)


def _fetch_meta(module, container):
163
    EXIT_DICT['meta'] = dict()
164
    try:
165 166 167
        for k, v in container.get_metadata().items():
            split_key = k.split(META_PREFIX)[-1]
            EXIT_DICT['meta'][split_key] = v
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
    except Exception, e:
        module.fail_json(msg=e.message)


def meta(cf, module, container_, state, meta_, clear_meta):
    c = _get_container(module, cf, container_)

    if meta_ and state == 'present':
        try:
            meta_set = c.set_metadata(meta_, clear=clear_meta)
        except Exception, e:
            module.fail_json(msg=e.message)
    elif meta_ and state == 'absent':
        remove_results = []
        for k, v in meta_.items():
            c.remove_metadata_key(k)
            remove_results.append(k)
            EXIT_DICT['deleted_meta_keys'] = remove_results
    elif state == 'absent':
        remove_results = []
        for k, v in c.get_metadata().items():
            c.remove_metadata_key(k)
            remove_results.append(k)
            EXIT_DICT['deleted_meta_keys'] = remove_results

    _fetch_meta(module, c)
    _locals = locals().keys()

    EXIT_DICT['container'] = c.name
    if 'meta_set' in _locals or 'remove_results' in _locals:
        EXIT_DICT['changed'] = True

    module.exit_json(**EXIT_DICT)


Matt Martz committed
203 204
def container(cf, module, container_, state, meta_, clear_meta, ttl, public,
              private, web_index, web_error):
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
    if public and private:
        module.fail_json(msg='container cannot be simultaneously '
                             'set to public and private')

    if state == 'absent' and (meta_ or clear_meta or public or private or web_index or web_error):
        module.fail_json(msg='state cannot be omitted when setting/removing '
                             'attributes on a container')

    if state == 'list':
        # We don't care if attributes are specified, let's list containers
        EXIT_DICT['containers'] = cf.list_containers()
        module.exit_json(**EXIT_DICT)

    try:
        c = cf.get_container(container_)
    except pyrax.exc.NoSuchContainer, e:
        # Make the container if state=present, otherwise bomb out
        if state == 'present':
            try:
                c = cf.create_container(container_)
            except Exception, e:
                module.fail_json(msg=e.message)
            else:
228
                EXIT_DICT['changed'] = True
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
                EXIT_DICT['created'] = True
        else:
            module.fail_json(msg=e.message)
    else:
        # Successfully grabbed a container object
        # Delete if state is absent
        if state == 'absent':
            try:
                cont_deleted = c.delete()
            except Exception, e:
                module.fail_json(msg=e.message)
            else:
                EXIT_DICT['deleted'] = True

    if meta_:
        try:
            meta_set = c.set_metadata(meta_, clear=clear_meta)
        except Exception, e:
            module.fail_json(msg=e.message)
        finally:
            _fetch_meta(module, c)

    if ttl:
        try:
            c.cdn_ttl = ttl
        except Exception, e:
            module.fail_json(msg=e.message)
        else:
            EXIT_DICT['ttl'] = c.cdn_ttl

    if public:
        try:
            cont_public = c.make_public()
        except Exception, e:
            module.fail_json(msg=e.message)
        else:
265 266 267 268
            EXIT_DICT['container_urls'] = dict(url=c.cdn_uri,
                                               ssl_url=c.cdn_ssl_uri,
                                               streaming_url=c.cdn_streaming_uri,
                                               ios_uri=c.cdn_ios_uri)
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300

    if private:
        try:
            cont_private = c.make_private()
        except Exception, e:
            module.fail_json(msg=e.message)
        else:
            EXIT_DICT['set_private'] = True

    if web_index:
        try:
            cont_web_index = c.set_web_index_page(web_index)
        except Exception, e:
            module.fail_json(msg=e.message)
        else:
            EXIT_DICT['set_index'] = True
        finally:
            _fetch_meta(module, c)

    if web_error:
        try:
            cont_err_index = c.set_web_error_page(web_error)
        except Exception, e:
            module.fail_json(msg=e.message)
        else:
            EXIT_DICT['set_error'] = True
        finally:
            _fetch_meta(module, c)

    EXIT_DICT['container'] = c.name
    EXIT_DICT['objs_in_container'] = c.object_count
    EXIT_DICT['total_bytes'] = c.total_bytes
Matt Martz committed
301

302
    _locals = locals().keys()
303
    if ('cont_deleted' in _locals
304 305 306 307 308 309 310 311 312 313
            or 'meta_set' in _locals
            or 'cont_public' in _locals
            or 'cont_private' in _locals
            or 'cont_web_index' in _locals
            or 'cont_err_index' in _locals):
        EXIT_DICT['changed'] = True

    module.exit_json(**EXIT_DICT)


Matt Martz committed
314 315 316 317
def cloudfiles(module, container_, state, meta_, clear_meta, typ, ttl, public,
               private, web_index, web_error):
    """ Dispatch from here to work with metadata or file objects """
    cf = pyrax.cloudfiles
318

Matt Martz committed
319 320 321 322 323 324 325 326 327 328 329 330
    if cf is None:
        module.fail_json(msg='Failed to instantiate client. This '
                             'typically indicates an invalid region or an '
                             'incorrectly capitalized region name.')

    cf.user_agent = USER_AGENT

    if typ == "container":
        container(cf, module, container_, state, meta_, clear_meta, ttl,
                  public, private, web_index, web_error)
    else:
        meta(cf, module, container_, state, meta_, clear_meta)
331 332 333 334 335 336 337


def main():
    argument_spec = rax_argument_spec()
    argument_spec.update(
        dict(
            container=dict(),
Matt Martz committed
338 339
            state=dict(choices=['present', 'absent', 'list'],
                       default='present'),
340
            meta=dict(type='dict', default=dict()),
341
            clear_meta=dict(default=False, type='bool'),
342 343
            type=dict(choices=['container', 'meta'], default='container'),
            ttl=dict(type='int'),
344 345
            public=dict(default=False, type='bool'),
            private=dict(default=False, type='bool'),
346 347 348 349 350 351 352 353 354 355
            web_index=dict(),
            web_error=dict()
        )
    )

    module = AnsibleModule(
        argument_spec=argument_spec,
        required_together=rax_required_together()
    )

Matt Martz committed
356 357 358
    if not HAS_PYRAX:
        module.fail_json(msg='pyrax is required for this module')

359 360 361 362 363 364 365 366 367 368 369 370 371 372
    container_ = module.params.get('container')
    state = module.params.get('state')
    meta_ = module.params.get('meta')
    clear_meta = module.params.get('clear_meta')
    typ = module.params.get('type')
    ttl = module.params.get('ttl')
    public = module.params.get('public')
    private = module.params.get('private')
    web_index = module.params.get('web_index')
    web_error = module.params.get('web_error')

    if state in ['present', 'absent'] and not container_:
        module.fail_json(msg='please specify a container name')
    if clear_meta and not typ == 'meta':
Matt Martz committed
373 374
        module.fail_json(msg='clear_meta can only be used when setting '
                             'metadata')
375 376

    setup_rax_module(module, pyrax)
Matt Martz committed
377 378
    cloudfiles(module, container_, state, meta_, clear_meta, typ, ttl, public,
               private, web_index, web_error)
379 380 381 382 383 384


from ansible.module_utils.basic import *
from ansible.module_utils.rax import *

main()