1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3#
4# Copyright (C) 2017 Google
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6# ----------------------------------------------------------------------------
7#
8#     ***     AUTO GENERATED CODE    ***    AUTO GENERATED CODE     ***
9#
10# ----------------------------------------------------------------------------
11#
12#     This file is automatically generated by Magic Modules and manual
13#     changes will be clobbered when the file is regenerated.
14#
15#     Please read more about how to change this file at
16#     https://www.github.com/GoogleCloudPlatform/magic-modules
17#
18# ----------------------------------------------------------------------------
19
20from __future__ import absolute_import, division, print_function
21
22__metaclass__ = type
23
24################################################################################
25# Documentation
26################################################################################
27
28ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ["preview"], 'supported_by': 'community'}
29
30DOCUMENTATION = '''
31---
32module: gcp_compute_router
33description:
34- Represents a Router resource.
35short_description: Creates a GCP Router
36version_added: 2.7
37author: Google Inc. (@googlecloudplatform)
38requirements:
39- python >= 2.6
40- requests >= 2.18.4
41- google-auth >= 1.3.0
42options:
43  state:
44    description:
45    - Whether the given object should exist in GCP
46    choices:
47    - present
48    - absent
49    default: present
50    type: str
51  name:
52    description:
53    - Name of the resource. The name must be 1-63 characters long, and comply with
54      RFC1035. Specifically, the name must be 1-63 characters long and match the regular
55      expression `[a-z]([-a-z0-9]*[a-z0-9])?` which means the first character must
56      be a lowercase letter, and all following characters must be a dash, lowercase
57      letter, or digit, except the last character, which cannot be a dash.
58    required: true
59    type: str
60  description:
61    description:
62    - An optional description of this resource.
63    required: false
64    type: str
65  network:
66    description:
67    - A reference to the network to which this router belongs.
68    - 'This field represents a link to a Network resource in GCP. It can be specified
69      in two ways. First, you can place a dictionary with key ''selfLink'' and value
70      of your resource''s selfLink Alternatively, you can add `register: name-of-resource`
71      to a gcp_compute_network task and then set this network field to "{{ name-of-resource
72      }}"'
73    required: true
74    type: dict
75  bgp:
76    description:
77    - BGP information specific to this router.
78    required: false
79    type: dict
80    suboptions:
81      asn:
82        description:
83        - Local BGP Autonomous System Number (ASN). Must be an RFC6996 private ASN,
84          either 16-bit or 32-bit. The value will be fixed for this router resource.
85          All VPN tunnels that link to this router will have the same local ASN.
86        required: true
87        type: int
88      advertise_mode:
89        description:
90        - User-specified flag to indicate which mode to use for advertisement.
91        - 'Valid values of this enum field are: DEFAULT, CUSTOM .'
92        - 'Some valid choices include: "DEFAULT", "CUSTOM"'
93        required: false
94        default: DEFAULT
95        type: str
96      advertised_groups:
97        description:
98        - User-specified list of prefix groups to advertise in custom mode.
99        - This field can only be populated if advertiseMode is CUSTOM and is advertised
100          to all peers of the router. These groups will be advertised in addition
101          to any specified prefixes. Leave this field blank to advertise no custom
102          groups.
103        - 'This enum field has the one valid value: ALL_SUBNETS .'
104        required: false
105        type: list
106      advertised_ip_ranges:
107        description:
108        - User-specified list of individual IP ranges to advertise in custom mode.
109          This field can only be populated if advertiseMode is CUSTOM and is advertised
110          to all peers of the router. These IP ranges will be advertised in addition
111          to any specified groups.
112        - Leave this field blank to advertise no custom IP ranges.
113        required: false
114        type: list
115        suboptions:
116          range:
117            description:
118            - The IP range to advertise. The value must be a CIDR-formatted string.
119            required: false
120            type: str
121          description:
122            description:
123            - User-specified description for the IP range.
124            required: false
125            type: str
126  region:
127    description:
128    - Region where the router resides.
129    required: true
130    type: str
131extends_documentation_fragment: gcp
132notes:
133- 'API Reference: U(https://cloud.google.com/compute/docs/reference/rest/v1/routers)'
134- 'Google Cloud Router: U(https://cloud.google.com/router/docs/)'
135'''
136
137EXAMPLES = '''
138- name: create a network
139  gcp_compute_network:
140    name: network-router
141    project: "{{ gcp_project }}"
142    auth_kind: "{{ gcp_cred_kind }}"
143    service_account_file: "{{ gcp_cred_file }}"
144    state: present
145  register: network
146
147- name: create a router
148  gcp_compute_router:
149    name: test_object
150    network: "{{ network }}"
151    bgp:
152      asn: 64514
153      advertise_mode: CUSTOM
154      advertised_groups:
155      - ALL_SUBNETS
156      advertised_ip_ranges:
157      - range: 1.2.3.4
158      - range: 6.7.0.0/16
159    region: us-central1
160    project: test_project
161    auth_kind: serviceaccount
162    service_account_file: "/tmp/auth.pem"
163    state: present
164'''
165
166RETURN = '''
167id:
168  description:
169  - The unique identifier for the resource.
170  returned: success
171  type: int
172creationTimestamp:
173  description:
174  - Creation timestamp in RFC3339 text format.
175  returned: success
176  type: str
177name:
178  description:
179  - Name of the resource. The name must be 1-63 characters long, and comply with RFC1035.
180    Specifically, the name must be 1-63 characters long and match the regular expression
181    `[a-z]([-a-z0-9]*[a-z0-9])?` which means the first character must be a lowercase
182    letter, and all following characters must be a dash, lowercase letter, or digit,
183    except the last character, which cannot be a dash.
184  returned: success
185  type: str
186description:
187  description:
188  - An optional description of this resource.
189  returned: success
190  type: str
191network:
192  description:
193  - A reference to the network to which this router belongs.
194  returned: success
195  type: dict
196bgp:
197  description:
198  - BGP information specific to this router.
199  returned: success
200  type: complex
201  contains:
202    asn:
203      description:
204      - Local BGP Autonomous System Number (ASN). Must be an RFC6996 private ASN,
205        either 16-bit or 32-bit. The value will be fixed for this router resource.
206        All VPN tunnels that link to this router will have the same local ASN.
207      returned: success
208      type: int
209    advertiseMode:
210      description:
211      - User-specified flag to indicate which mode to use for advertisement.
212      - 'Valid values of this enum field are: DEFAULT, CUSTOM .'
213      returned: success
214      type: str
215    advertisedGroups:
216      description:
217      - User-specified list of prefix groups to advertise in custom mode.
218      - This field can only be populated if advertiseMode is CUSTOM and is advertised
219        to all peers of the router. These groups will be advertised in addition to
220        any specified prefixes. Leave this field blank to advertise no custom groups.
221      - 'This enum field has the one valid value: ALL_SUBNETS .'
222      returned: success
223      type: list
224    advertisedIpRanges:
225      description:
226      - User-specified list of individual IP ranges to advertise in custom mode. This
227        field can only be populated if advertiseMode is CUSTOM and is advertised to
228        all peers of the router. These IP ranges will be advertised in addition to
229        any specified groups.
230      - Leave this field blank to advertise no custom IP ranges.
231      returned: success
232      type: complex
233      contains:
234        range:
235          description:
236          - The IP range to advertise. The value must be a CIDR-formatted string.
237          returned: success
238          type: str
239        description:
240          description:
241          - User-specified description for the IP range.
242          returned: success
243          type: str
244region:
245  description:
246  - Region where the router resides.
247  returned: success
248  type: str
249'''
250
251################################################################################
252# Imports
253################################################################################
254
255from ansible.module_utils.gcp_utils import navigate_hash, GcpSession, GcpModule, GcpRequest, remove_nones_from_dict, replace_resource_dict
256import json
257import time
258
259################################################################################
260# Main
261################################################################################
262
263
264def main():
265    """Main function"""
266
267    module = GcpModule(
268        argument_spec=dict(
269            state=dict(default='present', choices=['present', 'absent'], type='str'),
270            name=dict(required=True, type='str'),
271            description=dict(type='str'),
272            network=dict(required=True, type='dict'),
273            bgp=dict(
274                type='dict',
275                options=dict(
276                    asn=dict(required=True, type='int'),
277                    advertise_mode=dict(default='DEFAULT', type='str'),
278                    advertised_groups=dict(type='list', elements='str'),
279                    advertised_ip_ranges=dict(type='list', elements='dict', options=dict(range=dict(type='str'), description=dict(type='str'))),
280                ),
281            ),
282            region=dict(required=True, type='str'),
283        )
284    )
285
286    if not module.params['scopes']:
287        module.params['scopes'] = ['https://www.googleapis.com/auth/compute']
288
289    state = module.params['state']
290    kind = 'compute#router'
291
292    fetch = fetch_resource(module, self_link(module), kind)
293    changed = False
294
295    if fetch:
296        if state == 'present':
297            if is_different(module, fetch):
298                update(module, self_link(module), kind)
299                fetch = fetch_resource(module, self_link(module), kind)
300                changed = True
301        else:
302            delete(module, self_link(module), kind)
303            fetch = {}
304            changed = True
305    else:
306        if state == 'present':
307            fetch = create(module, collection(module), kind)
308            changed = True
309        else:
310            fetch = {}
311
312    fetch.update({'changed': changed})
313
314    module.exit_json(**fetch)
315
316
317def create(module, link, kind):
318    auth = GcpSession(module, 'compute')
319    return wait_for_operation(module, auth.post(link, resource_to_request(module)))
320
321
322def update(module, link, kind):
323    auth = GcpSession(module, 'compute')
324    return wait_for_operation(module, auth.patch(link, resource_to_request(module)))
325
326
327def delete(module, link, kind):
328    auth = GcpSession(module, 'compute')
329    return wait_for_operation(module, auth.delete(link))
330
331
332def resource_to_request(module):
333    request = {
334        u'kind': 'compute#router',
335        u'region': module.params.get('region'),
336        u'name': module.params.get('name'),
337        u'description': module.params.get('description'),
338        u'network': replace_resource_dict(module.params.get(u'network', {}), 'selfLink'),
339        u'bgp': RouterBgp(module.params.get('bgp', {}), module).to_request(),
340    }
341    return_vals = {}
342    for k, v in request.items():
343        if v or v is False:
344            return_vals[k] = v
345
346    return return_vals
347
348
349def fetch_resource(module, link, kind, allow_not_found=True):
350    auth = GcpSession(module, 'compute')
351    return return_if_object(module, auth.get(link), kind, allow_not_found)
352
353
354def self_link(module):
355    return "https://www.googleapis.com/compute/v1/projects/{project}/regions/{region}/routers/{name}".format(**module.params)
356
357
358def collection(module):
359    return "https://www.googleapis.com/compute/v1/projects/{project}/regions/{region}/routers".format(**module.params)
360
361
362def return_if_object(module, response, kind, allow_not_found=False):
363    # If not found, return nothing.
364    if allow_not_found and response.status_code == 404:
365        return None
366
367    # If no content, return nothing.
368    if response.status_code == 204:
369        return None
370
371    try:
372        module.raise_for_status(response)
373        result = response.json()
374    except getattr(json.decoder, 'JSONDecodeError', ValueError):
375        module.fail_json(msg="Invalid JSON response with error: %s" % response.text)
376
377    if navigate_hash(result, ['error', 'errors']):
378        module.fail_json(msg=navigate_hash(result, ['error', 'errors']))
379
380    return result
381
382
383def is_different(module, response):
384    request = resource_to_request(module)
385    response = response_to_hash(module, response)
386
387    # Remove all output-only from response.
388    response_vals = {}
389    for k, v in response.items():
390        if k in request:
391            response_vals[k] = v
392
393    request_vals = {}
394    for k, v in request.items():
395        if k in response:
396            request_vals[k] = v
397
398    return GcpRequest(request_vals) != GcpRequest(response_vals)
399
400
401# Remove unnecessary properties from the response.
402# This is for doing comparisons with Ansible's current parameters.
403def response_to_hash(module, response):
404    return {
405        u'id': response.get(u'id'),
406        u'creationTimestamp': response.get(u'creationTimestamp'),
407        u'name': module.params.get('name'),
408        u'description': response.get(u'description'),
409        u'network': replace_resource_dict(module.params.get(u'network', {}), 'selfLink'),
410        u'bgp': RouterBgp(response.get(u'bgp', {}), module).from_response(),
411    }
412
413
414def async_op_url(module, extra_data=None):
415    if extra_data is None:
416        extra_data = {}
417    url = "https://www.googleapis.com/compute/v1/projects/{project}/regions/{region}/operations/{op_id}"
418    combined = extra_data.copy()
419    combined.update(module.params)
420    return url.format(**combined)
421
422
423def wait_for_operation(module, response):
424    op_result = return_if_object(module, response, 'compute#operation')
425    if op_result is None:
426        return {}
427    status = navigate_hash(op_result, ['status'])
428    wait_done = wait_for_completion(status, op_result, module)
429    return fetch_resource(module, navigate_hash(wait_done, ['targetLink']), 'compute#router')
430
431
432def wait_for_completion(status, op_result, module):
433    op_id = navigate_hash(op_result, ['name'])
434    op_uri = async_op_url(module, {'op_id': op_id})
435    while status != 'DONE':
436        raise_if_errors(op_result, ['error', 'errors'], module)
437        time.sleep(1.0)
438        op_result = fetch_resource(module, op_uri, 'compute#operation', False)
439        status = navigate_hash(op_result, ['status'])
440    return op_result
441
442
443def raise_if_errors(response, err_path, module):
444    errors = navigate_hash(response, err_path)
445    if errors is not None:
446        module.fail_json(msg=errors)
447
448
449class RouterBgp(object):
450    def __init__(self, request, module):
451        self.module = module
452        if request:
453            self.request = request
454        else:
455            self.request = {}
456
457    def to_request(self):
458        return remove_nones_from_dict(
459            {
460                u'asn': self.request.get('asn'),
461                u'advertiseMode': self.request.get('advertise_mode'),
462                u'advertisedGroups': self.request.get('advertised_groups'),
463                u'advertisedIpRanges': RouterAdvertisediprangesArray(self.request.get('advertised_ip_ranges', []), self.module).to_request(),
464            }
465        )
466
467    def from_response(self):
468        return remove_nones_from_dict(
469            {
470                u'asn': self.request.get(u'asn'),
471                u'advertiseMode': self.request.get(u'advertiseMode'),
472                u'advertisedGroups': self.request.get(u'advertisedGroups'),
473                u'advertisedIpRanges': RouterAdvertisediprangesArray(self.request.get(u'advertisedIpRanges', []), self.module).from_response(),
474            }
475        )
476
477
478class RouterAdvertisediprangesArray(object):
479    def __init__(self, request, module):
480        self.module = module
481        if request:
482            self.request = request
483        else:
484            self.request = []
485
486    def to_request(self):
487        items = []
488        for item in self.request:
489            items.append(self._request_for_item(item))
490        return items
491
492    def from_response(self):
493        items = []
494        for item in self.request:
495            items.append(self._response_from_item(item))
496        return items
497
498    def _request_for_item(self, item):
499        return remove_nones_from_dict({u'range': item.get('range'), u'description': item.get('description')})
500
501    def _response_from_item(self, item):
502        return remove_nones_from_dict({u'range': item.get(u'range'), u'description': item.get(u'description')})
503
504
505if __name__ == '__main__':
506    main()
507