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