1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3# Copyright: Ansible Project
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
10DOCUMENTATION = '''
11---
12module: rax_meta
13short_description: Manipulate metadata for Rackspace Cloud Servers
14description:
15     - Manipulate metadata for Rackspace Cloud Servers
16options:
17  address:
18    type: str
19    description:
20      - Server IP address to modify metadata for, will match any IP assigned to
21        the server
22  id:
23    type: str
24    description:
25      - Server ID to modify metadata for
26  name:
27    type: str
28    description:
29      - Server name to modify metadata for
30  meta:
31    type: dict
32    description:
33      - A hash of metadata to associate with the instance
34author: "Matt Martz (@sivel)"
35extends_documentation_fragment:
36- community.general.rackspace.openstack
37
38'''
39
40EXAMPLES = '''
41- name: Set metadata for a server
42  hosts: all
43  gather_facts: False
44  tasks:
45    - name: Set metadata
46      local_action:
47        module: rax_meta
48        credentials: ~/.raxpub
49        name: "{{ inventory_hostname }}"
50        region: DFW
51        meta:
52          group: primary_group
53          groups:
54            - group_two
55            - group_three
56          app: my_app
57
58    - name: Clear metadata
59      local_action:
60        module: rax_meta
61        credentials: ~/.raxpub
62        name: "{{ inventory_hostname }}"
63        region: DFW
64'''
65
66import json
67
68try:
69    import pyrax
70    HAS_PYRAX = True
71except ImportError:
72    HAS_PYRAX = False
73
74from ansible.module_utils.basic import AnsibleModule
75from ansible_collections.community.general.plugins.module_utils.rax import rax_argument_spec, rax_required_together, setup_rax_module
76from ansible.module_utils.six import string_types
77
78
79def rax_meta(module, address, name, server_id, meta):
80    changed = False
81
82    cs = pyrax.cloudservers
83
84    if cs is None:
85        module.fail_json(msg='Failed to instantiate client. This '
86                             'typically indicates an invalid region or an '
87                             'incorrectly capitalized region name.')
88
89    search_opts = {}
90    if name:
91        search_opts = dict(name='^%s$' % name)
92        try:
93            servers = cs.servers.list(search_opts=search_opts)
94        except Exception as e:
95            module.fail_json(msg='%s' % e.message)
96    elif address:
97        servers = []
98        try:
99            for server in cs.servers.list():
100                for addresses in server.networks.values():
101                    if address in addresses:
102                        servers.append(server)
103                        break
104        except Exception as e:
105            module.fail_json(msg='%s' % e.message)
106    elif server_id:
107        servers = []
108        try:
109            servers.append(cs.servers.get(server_id))
110        except Exception as e:
111            pass
112
113    if len(servers) > 1:
114        module.fail_json(msg='Multiple servers found matching provided '
115                             'search parameters')
116    elif not servers:
117        module.fail_json(msg='Failed to find a server matching provided '
118                             'search parameters')
119
120    # Normalize and ensure all metadata values are strings
121    for k, v in meta.items():
122        if isinstance(v, list):
123            meta[k] = ','.join(['%s' % i for i in v])
124        elif isinstance(v, dict):
125            meta[k] = json.dumps(v)
126        elif not isinstance(v, string_types):
127            meta[k] = '%s' % v
128
129    server = servers[0]
130    if server.metadata == meta:
131        changed = False
132    else:
133        changed = True
134        removed = set(server.metadata.keys()).difference(meta.keys())
135        cs.servers.delete_meta(server, list(removed))
136        cs.servers.set_meta(server, meta)
137        server.get()
138
139    module.exit_json(changed=changed, meta=server.metadata)
140
141
142def main():
143    argument_spec = rax_argument_spec()
144    argument_spec.update(
145        dict(
146            address=dict(),
147            id=dict(),
148            name=dict(),
149            meta=dict(type='dict', default=dict()),
150        )
151    )
152
153    module = AnsibleModule(
154        argument_spec=argument_spec,
155        required_together=rax_required_together(),
156        mutually_exclusive=[['address', 'id', 'name']],
157        required_one_of=[['address', 'id', 'name']],
158    )
159
160    if not HAS_PYRAX:
161        module.fail_json(msg='pyrax is required for this module')
162
163    address = module.params.get('address')
164    server_id = module.params.get('id')
165    name = module.params.get('name')
166    meta = module.params.get('meta')
167
168    setup_rax_module(module, pyrax)
169
170    rax_meta(module, address, name, server_id, meta)
171
172
173if __name__ == '__main__':
174    main()
175