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