1#!/usr/bin/python
2# This file is part of Ansible
3#
4# Ansible is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# Ansible is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
16
17from __future__ import absolute_import, division, print_function
18__metaclass__ = type
19
20ANSIBLE_METADATA = {'metadata_version': '1.1',
21                    'status': ['preview'],
22                    'supported_by': 'community'}
23
24DOCUMENTATION = '''
25---
26module: oneandone_public_ip
27short_description: Configure 1&1 public IPs.
28description:
29     - Create, update, and remove public IPs.
30       This module has a dependency on 1and1 >= 1.0
31version_added: "2.5"
32options:
33  state:
34    description:
35      - Define a public ip state to create, remove, or update.
36    required: false
37    default: 'present'
38    choices: [ "present", "absent", "update" ]
39  auth_token:
40    description:
41      - Authenticating API token provided by 1&1.
42    required: true
43  api_url:
44    description:
45      - Custom API URL. Overrides the
46        ONEANDONE_API_URL environement variable.
47    required: false
48  reverse_dns:
49    description:
50      - Reverse DNS name. maxLength=256
51    required: false
52  datacenter:
53    description:
54      - ID of the datacenter where the IP will be created (only for unassigned IPs).
55    required: false
56  type:
57    description:
58      - Type of IP. Currently, only IPV4 is available.
59    choices: ["IPV4", "IPV6"]
60    default: 'IPV4'
61    required: false
62  public_ip_id:
63    description:
64      - The ID of the public IP used with update and delete states.
65    required: true
66  wait:
67    description:
68      - wait for the instance to be in state 'running' before returning
69    required: false
70    default: "yes"
71    type: bool
72  wait_timeout:
73    description:
74      - how long before wait gives up, in seconds
75    default: 600
76  wait_interval:
77    description:
78      - Defines the number of seconds to wait when using the _wait_for methods
79    default: 5
80
81requirements:
82     - "1and1"
83     - "python >= 2.6"
84
85author:
86  - Amel Ajdinovic (@aajdinov)
87  - Ethan Devenport (@edevenport)
88'''
89
90EXAMPLES = '''
91
92# Create a public IP.
93
94- oneandone_public_ip:
95    auth_token: oneandone_private_api_key
96    reverse_dns: example.com
97    datacenter: US
98    type: IPV4
99
100# Update a public IP.
101
102- oneandone_public_ip:
103    auth_token: oneandone_private_api_key
104    public_ip_id: public ip id
105    reverse_dns: secondexample.com
106    state: update
107
108
109# Delete a public IP
110
111- oneandone_public_ip:
112    auth_token: oneandone_private_api_key
113    public_ip_id: public ip id
114    state: absent
115
116'''
117
118RETURN = '''
119public_ip:
120    description: Information about the public ip that was processed
121    type: dict
122    sample: '{"id": "F77CC589EBC120905B4F4719217BFF6D", "ip": "10.5.132.106"}'
123    returned: always
124'''
125
126import os
127from ansible.module_utils.basic import AnsibleModule
128from ansible.module_utils.oneandone import (
129    get_datacenter,
130    get_public_ip,
131    OneAndOneResources,
132    wait_for_resource_creation_completion
133)
134
135HAS_ONEANDONE_SDK = True
136
137try:
138    import oneandone.client
139except ImportError:
140    HAS_ONEANDONE_SDK = False
141
142DATACENTERS = ['US', 'ES', 'DE', 'GB']
143
144TYPES = ['IPV4', 'IPV6']
145
146
147def _check_mode(module, result):
148    if module.check_mode:
149        module.exit_json(
150            changed=result
151        )
152
153
154def create_public_ip(module, oneandone_conn):
155    """
156    Create new public IP
157
158    module : AnsibleModule object
159    oneandone_conn: authenticated oneandone object
160
161    Returns a dictionary containing a 'changed' attribute indicating whether
162    any public IP was added.
163    """
164    reverse_dns = module.params.get('reverse_dns')
165    datacenter = module.params.get('datacenter')
166    ip_type = module.params.get('type')
167    wait = module.params.get('wait')
168    wait_timeout = module.params.get('wait_timeout')
169    wait_interval = module.params.get('wait_interval')
170
171    if datacenter is not None:
172        datacenter_id = get_datacenter(oneandone_conn, datacenter)
173        if datacenter_id is None:
174            _check_mode(module, False)
175            module.fail_json(
176                msg='datacenter %s not found.' % datacenter)
177
178    try:
179        _check_mode(module, True)
180        public_ip = oneandone_conn.create_public_ip(
181            reverse_dns=reverse_dns,
182            ip_type=ip_type,
183            datacenter_id=datacenter_id)
184
185        if wait:
186            wait_for_resource_creation_completion(oneandone_conn,
187                                                  OneAndOneResources.public_ip,
188                                                  public_ip['id'],
189                                                  wait_timeout,
190                                                  wait_interval)
191            public_ip = oneandone_conn.get_public_ip(public_ip['id'])
192
193        changed = True if public_ip else False
194
195        return (changed, public_ip)
196    except Exception as e:
197        module.fail_json(msg=str(e))
198
199
200def update_public_ip(module, oneandone_conn):
201    """
202    Update a public IP
203
204    module : AnsibleModule object
205    oneandone_conn: authenticated oneandone object
206
207    Returns a dictionary containing a 'changed' attribute indicating whether
208    any public IP was changed.
209    """
210    reverse_dns = module.params.get('reverse_dns')
211    public_ip_id = module.params.get('public_ip_id')
212    wait = module.params.get('wait')
213    wait_timeout = module.params.get('wait_timeout')
214    wait_interval = module.params.get('wait_interval')
215
216    public_ip = get_public_ip(oneandone_conn, public_ip_id, True)
217    if public_ip is None:
218        _check_mode(module, False)
219        module.fail_json(
220            msg='public IP %s not found.' % public_ip_id)
221
222    try:
223        _check_mode(module, True)
224        public_ip = oneandone_conn.modify_public_ip(
225            ip_id=public_ip['id'],
226            reverse_dns=reverse_dns)
227
228        if wait:
229            wait_for_resource_creation_completion(oneandone_conn,
230                                                  OneAndOneResources.public_ip,
231                                                  public_ip['id'],
232                                                  wait_timeout,
233                                                  wait_interval)
234            public_ip = oneandone_conn.get_public_ip(public_ip['id'])
235
236        changed = True if public_ip else False
237
238        return (changed, public_ip)
239    except Exception as e:
240        module.fail_json(msg=str(e))
241
242
243def delete_public_ip(module, oneandone_conn):
244    """
245    Delete a public IP
246
247    module : AnsibleModule object
248    oneandone_conn: authenticated oneandone object
249
250    Returns a dictionary containing a 'changed' attribute indicating whether
251    any public IP was deleted.
252    """
253    public_ip_id = module.params.get('public_ip_id')
254
255    public_ip = get_public_ip(oneandone_conn, public_ip_id, True)
256    if public_ip is None:
257        _check_mode(module, False)
258        module.fail_json(
259            msg='public IP %s not found.' % public_ip_id)
260
261    try:
262        _check_mode(module, True)
263        deleted_public_ip = oneandone_conn.delete_public_ip(
264            ip_id=public_ip['id'])
265
266        changed = True if deleted_public_ip else False
267
268        return (changed, {
269            'id': public_ip['id']
270        })
271    except Exception as e:
272        module.fail_json(msg=str(e))
273
274
275def main():
276    module = AnsibleModule(
277        argument_spec=dict(
278            auth_token=dict(
279                type='str',
280                default=os.environ.get('ONEANDONE_AUTH_TOKEN'),
281                no_log=True),
282            api_url=dict(
283                type='str',
284                default=os.environ.get('ONEANDONE_API_URL')),
285            public_ip_id=dict(type='str'),
286            reverse_dns=dict(type='str'),
287            datacenter=dict(
288                choices=DATACENTERS,
289                default='US'),
290            type=dict(
291                choices=TYPES,
292                default='IPV4'),
293            wait=dict(type='bool', default=True),
294            wait_timeout=dict(type='int', default=600),
295            wait_interval=dict(type='int', default=5),
296            state=dict(type='str', default='present', choices=['present', 'absent', 'update']),
297        ),
298        supports_check_mode=True
299    )
300
301    if not HAS_ONEANDONE_SDK:
302        module.fail_json(msg='1and1 required for this module')
303
304    if not module.params.get('auth_token'):
305        module.fail_json(
306            msg='auth_token parameter is required.')
307
308    if not module.params.get('api_url'):
309        oneandone_conn = oneandone.client.OneAndOneService(
310            api_token=module.params.get('auth_token'))
311    else:
312        oneandone_conn = oneandone.client.OneAndOneService(
313            api_token=module.params.get('auth_token'), api_url=module.params.get('api_url'))
314
315    state = module.params.get('state')
316
317    if state == 'absent':
318        if not module.params.get('public_ip_id'):
319            module.fail_json(
320                msg="'public_ip_id' parameter is required to delete a public ip.")
321        try:
322            (changed, public_ip) = delete_public_ip(module, oneandone_conn)
323        except Exception as e:
324            module.fail_json(msg=str(e))
325    elif state == 'update':
326        if not module.params.get('public_ip_id'):
327            module.fail_json(
328                msg="'public_ip_id' parameter is required to update a public ip.")
329        try:
330            (changed, public_ip) = update_public_ip(module, oneandone_conn)
331        except Exception as e:
332            module.fail_json(msg=str(e))
333
334    elif state == 'present':
335        try:
336            (changed, public_ip) = create_public_ip(module, oneandone_conn)
337        except Exception as e:
338            module.fail_json(msg=str(e))
339
340    module.exit_json(changed=changed, public_ip=public_ip)
341
342
343if __name__ == '__main__':
344    main()
345