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_vpn_tunnel
33description:
34- VPN tunnel resource.
35short_description: Creates a GCP VpnTunnel
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  target_vpn_gateway:
66    description:
67    - URL of the Target VPN gateway with which this VPN tunnel is associated.
68    - 'This field represents a link to a TargetVpnGateway resource in GCP. It can
69      be specified in two ways. First, you can place a dictionary with key ''selfLink''
70      and value of your resource''s selfLink Alternatively, you can add `register:
71      name-of-resource` to a gcp_compute_target_vpn_gateway task and then set this
72      target_vpn_gateway field to "{{ name-of-resource }}"'
73    required: false
74    type: dict
75  router:
76    description:
77    - URL of router resource to be used for dynamic routing.
78    - 'This field represents a link to a Router resource in GCP. It can be specified
79      in two ways. First, you can place a dictionary with key ''selfLink'' and value
80      of your resource''s selfLink Alternatively, you can add `register: name-of-resource`
81      to a gcp_compute_router task and then set this router field to "{{ name-of-resource
82      }}"'
83    required: false
84    type: dict
85  peer_ip:
86    description:
87    - IP address of the peer VPN gateway. Only IPv4 is supported.
88    required: false
89    type: str
90  shared_secret:
91    description:
92    - Shared secret used to set the secure session between the Cloud VPN gateway and
93      the peer VPN gateway.
94    required: true
95    type: str
96  ike_version:
97    description:
98    - IKE protocol version to use when establishing the VPN tunnel with peer VPN gateway.
99    - Acceptable IKE versions are 1 or 2. Default version is 2.
100    required: false
101    default: '2'
102    type: int
103  local_traffic_selector:
104    description:
105    - Local traffic selector to use when establishing the VPN tunnel with peer VPN
106      gateway. The value should be a CIDR formatted string, for example `192.168.0.0/16`.
107      The ranges should be disjoint.
108    - Only IPv4 is supported.
109    required: false
110    type: list
111  remote_traffic_selector:
112    description:
113    - Remote traffic selector to use when establishing the VPN tunnel with peer VPN
114      gateway. The value should be a CIDR formatted string, for example `192.168.0.0/16`.
115      The ranges should be disjoint.
116    - Only IPv4 is supported.
117    required: false
118    type: list
119  region:
120    description:
121    - The region where the tunnel is located.
122    required: true
123    type: str
124extends_documentation_fragment: gcp
125notes:
126- 'API Reference: U(https://cloud.google.com/compute/docs/reference/rest/v1/vpnTunnels)'
127- 'Cloud VPN Overview: U(https://cloud.google.com/vpn/docs/concepts/overview)'
128- 'Networks and Tunnel Routing: U(https://cloud.google.com/vpn/docs/concepts/choosing-networks-routing)'
129'''
130
131EXAMPLES = '''
132- name: create a network
133  gcp_compute_network:
134    name: network-vpn-tunnel
135    project: "{{ gcp_project }}"
136    auth_kind: "{{ gcp_cred_kind }}"
137    service_account_file: "{{ gcp_cred_file }}"
138    state: present
139  register: network
140
141- name: create a router
142  gcp_compute_router:
143    name: router-vpn-tunnel
144    network: "{{ network }}"
145    bgp:
146      asn: 64514
147      advertise_mode: CUSTOM
148      advertised_groups:
149      - ALL_SUBNETS
150      advertised_ip_ranges:
151      - range: 1.2.3.4
152      - range: 6.7.0.0/16
153    region: us-central1
154    project: "{{ gcp_project }}"
155    auth_kind: "{{ gcp_cred_kind }}"
156    service_account_file: "{{ gcp_cred_file }}"
157    state: present
158  register: router
159
160- name: create a target vpn gateway
161  gcp_compute_target_vpn_gateway:
162    name: gateway-vpn-tunnel
163    region: us-west1
164    network: "{{ network }}"
165    project: "{{ gcp_project }}"
166    auth_kind: "{{ gcp_cred_kind }}"
167    service_account_file: "{{ gcp_cred_file }}"
168    state: present
169  register: gateway
170
171- name: create a vpn tunnel
172  gcp_compute_vpn_tunnel:
173    name: test_object
174    region: us-west1
175    target_vpn_gateway: "{{ gateway }}"
176    router: "{{ router }}"
177    shared_secret: super secret
178    project: test_project
179    auth_kind: serviceaccount
180    service_account_file: "/tmp/auth.pem"
181    state: present
182'''
183
184RETURN = '''
185creationTimestamp:
186  description:
187  - Creation timestamp in RFC3339 text format.
188  returned: success
189  type: str
190name:
191  description:
192  - Name of the resource. The name must be 1-63 characters long, and comply with RFC1035.
193    Specifically, the name must be 1-63 characters long and match the regular expression
194    `[a-z]([-a-z0-9]*[a-z0-9])?` which means the first character must be a lowercase
195    letter, and all following characters must be a dash, lowercase letter, or digit,
196    except the last character, which cannot be a dash.
197  returned: success
198  type: str
199description:
200  description:
201  - An optional description of this resource.
202  returned: success
203  type: str
204targetVpnGateway:
205  description:
206  - URL of the Target VPN gateway with which this VPN tunnel is associated.
207  returned: success
208  type: dict
209router:
210  description:
211  - URL of router resource to be used for dynamic routing.
212  returned: success
213  type: dict
214peerIp:
215  description:
216  - IP address of the peer VPN gateway. Only IPv4 is supported.
217  returned: success
218  type: str
219sharedSecret:
220  description:
221  - Shared secret used to set the secure session between the Cloud VPN gateway and
222    the peer VPN gateway.
223  returned: success
224  type: str
225sharedSecretHash:
226  description:
227  - Hash of the shared secret.
228  returned: success
229  type: str
230ikeVersion:
231  description:
232  - IKE protocol version to use when establishing the VPN tunnel with peer VPN gateway.
233  - Acceptable IKE versions are 1 or 2. Default version is 2.
234  returned: success
235  type: int
236localTrafficSelector:
237  description:
238  - Local traffic selector to use when establishing the VPN tunnel with peer VPN gateway.
239    The value should be a CIDR formatted string, for example `192.168.0.0/16`. The
240    ranges should be disjoint.
241  - Only IPv4 is supported.
242  returned: success
243  type: list
244remoteTrafficSelector:
245  description:
246  - Remote traffic selector to use when establishing the VPN tunnel with peer VPN
247    gateway. The value should be a CIDR formatted string, for example `192.168.0.0/16`.
248    The ranges should be disjoint.
249  - Only IPv4 is supported.
250  returned: success
251  type: list
252region:
253  description:
254  - The region where the tunnel is located.
255  returned: success
256  type: str
257'''
258
259################################################################################
260# Imports
261################################################################################
262
263from ansible.module_utils.gcp_utils import navigate_hash, GcpSession, GcpModule, GcpRequest, replace_resource_dict
264import json
265import time
266
267################################################################################
268# Main
269################################################################################
270
271
272def main():
273    """Main function"""
274
275    module = GcpModule(
276        argument_spec=dict(
277            state=dict(default='present', choices=['present', 'absent'], type='str'),
278            name=dict(required=True, type='str'),
279            description=dict(type='str'),
280            target_vpn_gateway=dict(type='dict'),
281            router=dict(type='dict'),
282            peer_ip=dict(type='str'),
283            shared_secret=dict(required=True, type='str', no_log=True),
284            ike_version=dict(default=2, type='int'),
285            local_traffic_selector=dict(type='list', elements='str'),
286            remote_traffic_selector=dict(type='list', elements='str'),
287            region=dict(required=True, type='str'),
288        )
289    )
290
291    if not module.params['scopes']:
292        module.params['scopes'] = ['https://www.googleapis.com/auth/compute']
293
294    state = module.params['state']
295    kind = 'compute#vpnTunnel'
296
297    fetch = fetch_resource(module, self_link(module), kind)
298    changed = False
299
300    if fetch:
301        if state == 'present':
302            if is_different(module, fetch):
303                update(module, self_link(module), kind)
304                fetch = fetch_resource(module, self_link(module), kind)
305                changed = True
306        else:
307            delete(module, self_link(module), kind)
308            fetch = {}
309            changed = True
310    else:
311        if state == 'present':
312            fetch = create(module, collection(module), kind)
313            changed = True
314        else:
315            fetch = {}
316
317    fetch.update({'changed': changed})
318
319    module.exit_json(**fetch)
320
321
322def create(module, link, kind):
323    auth = GcpSession(module, 'compute')
324    return wait_for_operation(module, auth.post(link, resource_to_request(module)))
325
326
327def update(module, link, kind):
328    delete(module, self_link(module), kind)
329    create(module, collection(module), kind)
330
331
332def delete(module, link, kind):
333    auth = GcpSession(module, 'compute')
334    return wait_for_operation(module, auth.delete(link))
335
336
337def resource_to_request(module):
338    request = {
339        u'kind': 'compute#vpnTunnel',
340        u'name': module.params.get('name'),
341        u'description': module.params.get('description'),
342        u'targetVpnGateway': replace_resource_dict(module.params.get(u'target_vpn_gateway', {}), 'selfLink'),
343        u'router': replace_resource_dict(module.params.get(u'router', {}), 'selfLink'),
344        u'peerIp': module.params.get('peer_ip'),
345        u'sharedSecret': module.params.get('shared_secret'),
346        u'ikeVersion': module.params.get('ike_version'),
347        u'localTrafficSelector': module.params.get('local_traffic_selector'),
348        u'remoteTrafficSelector': module.params.get('remote_traffic_selector'),
349    }
350    return_vals = {}
351    for k, v in request.items():
352        if v or v is False:
353            return_vals[k] = v
354
355    return return_vals
356
357
358def fetch_resource(module, link, kind, allow_not_found=True):
359    auth = GcpSession(module, 'compute')
360    return return_if_object(module, auth.get(link), kind, allow_not_found)
361
362
363def self_link(module):
364    return "https://www.googleapis.com/compute/v1/projects/{project}/regions/{region}/vpnTunnels/{name}".format(**module.params)
365
366
367def collection(module):
368    return "https://www.googleapis.com/compute/v1/projects/{project}/regions/{region}/vpnTunnels".format(**module.params)
369
370
371def return_if_object(module, response, kind, allow_not_found=False):
372    # If not found, return nothing.
373    if allow_not_found and response.status_code == 404:
374        return None
375
376    # If no content, return nothing.
377    if response.status_code == 204:
378        return None
379
380    try:
381        module.raise_for_status(response)
382        result = response.json()
383    except getattr(json.decoder, 'JSONDecodeError', ValueError):
384        module.fail_json(msg="Invalid JSON response with error: %s" % response.text)
385
386    if navigate_hash(result, ['error', 'errors']):
387        module.fail_json(msg=navigate_hash(result, ['error', 'errors']))
388
389    return result
390
391
392def is_different(module, response):
393    request = resource_to_request(module)
394    response = response_to_hash(module, response)
395
396    # Remove all output-only from response.
397    response_vals = {}
398    for k, v in response.items():
399        if k in request:
400            response_vals[k] = v
401
402    request_vals = {}
403    for k, v in request.items():
404        if k in response:
405            request_vals[k] = v
406
407    return GcpRequest(request_vals) != GcpRequest(response_vals)
408
409
410# Remove unnecessary properties from the response.
411# This is for doing comparisons with Ansible's current parameters.
412def response_to_hash(module, response):
413    return {
414        u'creationTimestamp': response.get(u'creationTimestamp'),
415        u'name': response.get(u'name'),
416        u'description': module.params.get('description'),
417        u'targetVpnGateway': replace_resource_dict(module.params.get(u'target_vpn_gateway', {}), 'selfLink'),
418        u'router': replace_resource_dict(module.params.get(u'router', {}), 'selfLink'),
419        u'peerIp': response.get(u'peerIp'),
420        u'sharedSecret': response.get(u'sharedSecret'),
421        u'sharedSecretHash': response.get(u'sharedSecretHash'),
422        u'ikeVersion': response.get(u'ikeVersion'),
423        u'localTrafficSelector': response.get(u'localTrafficSelector'),
424        u'remoteTrafficSelector': response.get(u'remoteTrafficSelector'),
425    }
426
427
428def async_op_url(module, extra_data=None):
429    if extra_data is None:
430        extra_data = {}
431    url = "https://www.googleapis.com/compute/v1/projects/{project}/regions/{region}/operations/{op_id}"
432    combined = extra_data.copy()
433    combined.update(module.params)
434    return url.format(**combined)
435
436
437def wait_for_operation(module, response):
438    op_result = return_if_object(module, response, 'compute#operation')
439    if op_result is None:
440        return {}
441    status = navigate_hash(op_result, ['status'])
442    wait_done = wait_for_completion(status, op_result, module)
443    return fetch_resource(module, navigate_hash(wait_done, ['targetLink']), 'compute#vpnTunnel')
444
445
446def wait_for_completion(status, op_result, module):
447    op_id = navigate_hash(op_result, ['name'])
448    op_uri = async_op_url(module, {'op_id': op_id})
449    while status != 'DONE':
450        raise_if_errors(op_result, ['error', 'errors'], module)
451        time.sleep(1.0)
452        op_result = fetch_resource(module, op_uri, 'compute#operation', False)
453        status = navigate_hash(op_result, ['status'])
454    return op_result
455
456
457def raise_if_errors(response, err_path, module):
458    errors = navigate_hash(response, err_path)
459    if errors is not None:
460        module.fail_json(msg=errors)
461
462
463if __name__ == '__main__':
464    main()
465