From ce38f006a36d0e3b2bc3623336b35b528ed5d4fe Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Tue, 22 Apr 2014 19:20:18 -0500 Subject: [PATCH] Add rax_meta module Also update rax to mirror meta value normalization from rax_meta --- cloud/rax | 12 +++- cloud/rax_meta | 178 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 cloud/rax_meta diff --git a/cloud/rax b/cloud/rax index af533bca126..22d268012ad 100644 --- a/cloud/rax +++ b/cloud/rax @@ -386,15 +386,21 @@ def cloudservers(module, state, name, flavor, image, meta, key_name, files, servers = [] - for key, value in meta.items(): - meta[key] = repr(value) - # 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, basestring): + 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 diff --git a/cloud/rax_meta b/cloud/rax_meta new file mode 100644 index 00000000000..2e1d90f5389 --- /dev/null +++ b/cloud/rax_meta @@ -0,0 +1,178 @@ +#!/usr/bin/python +# 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 . + +# This is a DOCUMENTATION stub specific to this module, it extends +# a documentation fragment located in ansible.utils.module_docs_fragments +DOCUMENTATION = ''' +--- +module: rax_meta +short_description: Manipulate metadata for Rackspace Cloud Servers +description: + - Manipulate metadata for Rackspace Cloud Servers +version_added: 1.7 +options: + address: + description: + - Server IP address to modify metadata for, will match any IP assigned to + the server + id: + description: + - Server ID to modify metadata for + name: + description: + - Server name to modify metadata for + default: null + meta: + description: + - A hash of metadata to associate with the instance + default: null +author: Matt Martz +extends_documentation_fragment: rackspace.openstack +''' + +EXAMPLES = ''' +- name: Set metadata for a server + hosts: all + gather_facts: False + tasks: + - name: Set metadata + local_action: + module: rax_meta + credentials: ~/.raxpub + name: "{{ inventory_hostname }}" + region: DFW + meta: + group: primary_group + groups: + - group_two + - group_three + app: my_app + + - name: Clear metadata + local_action: + module: rax_meta + credentials: ~/.raxpub + name: "{{ inventory_hostname }}" + region: DFW +''' + +try: + import pyrax + HAS_PYRAX = True +except ImportError: + HAS_PYRAX = False + + +def rax_meta(module, address, name, server_id, meta): + 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.') + + search_opts = {} + if name: + search_opts = dict(name='^%s$' % name) + try: + servers = cs.servers.list(search_opts=search_opts) + except Exception, 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, e: + module.fail_json(msg='%s' % e.message) + elif server_id: + servers = [] + try: + servers.append(cs.servers.get(server_id)) + except Exception, e: + pass + + if len(servers) > 1: + module.fail_json(msg='Multiple servers found matching provided ' + 'search parameters') + elif not servers: + module.fail_json(msg='Failed to find a server matching provided ' + 'search parameters') + + # 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, basestring): + meta[k] = '%s' % v + + server = servers[0] + if server.metadata == meta: + changed = False + else: + changed = True + removed = set(server.metadata.keys()).difference(meta.keys()) + cs.servers.delete_meta(server, list(removed)) + cs.servers.set_meta(server, meta) + server.get() + + module.exit_json(changed=changed, meta=server.metadata) + + +def main(): + argument_spec = rax_argument_spec() + argument_spec.update( + dict( + address=dict(), + id=dict(), + name=dict(), + meta=dict(type='dict', default=dict()), + ) + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=rax_required_together(), + mutually_exclusive=[['address', 'id', 'name']], + required_one_of=[['address', 'id', 'name']], + ) + + if not HAS_PYRAX: + module.fail_json(msg='pyrax is required for this module') + + address = module.params.get('address') + server_id = module.params.get('id') + name = module.params.get('name') + meta = module.params.get('meta') + + setup_rax_module(module, pyrax) + + rax_meta(module, address, name, server_id, meta) + + +# import module snippets +from ansible.module_utils.basic import * +from ansible.module_utils.rax import * + +### invoke the module +main()