1#!/usr/local/bin/python3.8 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_snapshot 33description: 34- Represents a Persistent Disk Snapshot resource. 35- Use snapshots to back up data from your persistent disks. Snapshots are different 36 from public images and custom images, which are used primarily to create instances 37 or configure instance templates. Snapshots are useful for periodic backup of the 38 data on your persistent disks. You can create snapshots from persistent disks even 39 while they are attached to running instances. 40- Snapshots are incremental, so you can create regular snapshots on a persistent disk 41 faster and at a much lower cost than if you regularly created a full image of the 42 disk. 43short_description: Creates a GCP Snapshot 44author: Google Inc. (@googlecloudplatform) 45requirements: 46- python >= 2.6 47- requests >= 2.18.4 48- google-auth >= 1.3.0 49options: 50 state: 51 description: 52 - Whether the given object should exist in GCP 53 choices: 54 - present 55 - absent 56 default: present 57 type: str 58 name: 59 description: 60 - Name of the resource; provided by the client when the resource is created. The 61 name must be 1-63 characters long, and comply with RFC1035. Specifically, the 62 name must be 1-63 characters long and match the regular expression `[a-z]([-a-z0-9]*[a-z0-9])?` 63 which means the first character must be a lowercase letter, and all following 64 characters must be a dash, lowercase letter, or digit, except the last character, 65 which cannot be a dash. 66 required: true 67 type: str 68 description: 69 description: 70 - An optional description of this resource. 71 required: false 72 type: str 73 storage_locations: 74 description: 75 - Cloud Storage bucket storage location of the snapshot (regional or multi-regional). 76 elements: str 77 required: false 78 type: list 79 labels: 80 description: 81 - Labels to apply to this Snapshot. 82 required: false 83 type: dict 84 source_disk: 85 description: 86 - A reference to the disk used to create this snapshot. 87 - 'This field represents a link to a Disk resource in GCP. It can be specified 88 in two ways. First, you can place a dictionary with key ''name'' and value of 89 your resource''s name Alternatively, you can add `register: name-of-resource` 90 to a gcp_compute_disk task and then set this source_disk field to "{{ name-of-resource 91 }}"' 92 required: true 93 type: dict 94 zone: 95 description: 96 - A reference to the zone where the disk is hosted. 97 required: false 98 type: str 99 snapshot_encryption_key: 100 description: 101 - The customer-supplied encryption key of the snapshot. Required if the source 102 snapshot is protected by a customer-supplied encryption key. 103 required: false 104 type: dict 105 suboptions: 106 raw_key: 107 description: 108 - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 109 base64 to either encrypt or decrypt this resource. 110 required: false 111 type: str 112 kms_key_name: 113 description: 114 - The name of the encryption key that is stored in Google Cloud KMS. 115 required: false 116 type: str 117 kms_key_service_account: 118 description: 119 - The service account used for the encryption request for the given KMS key. 120 - If absent, the Compute Engine Service Agent service account is used. 121 required: false 122 type: str 123 source_disk_encryption_key: 124 description: 125 - The customer-supplied encryption key of the source snapshot. Required if the 126 source snapshot is protected by a customer-supplied encryption key. 127 required: false 128 type: dict 129 suboptions: 130 raw_key: 131 description: 132 - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 133 base64 to either encrypt or decrypt this resource. 134 required: false 135 type: str 136 kms_key_name: 137 description: 138 - The name of the encryption key that is stored in Google Cloud KMS. 139 required: false 140 type: str 141 kms_key_service_account: 142 description: 143 - The service account used for the encryption request for the given KMS key. 144 - If absent, the Compute Engine Service Agent service account is used. 145 required: false 146 type: str 147 project: 148 description: 149 - The Google Cloud Platform project to use. 150 type: str 151 auth_kind: 152 description: 153 - The type of credential used. 154 type: str 155 required: true 156 choices: 157 - application 158 - machineaccount 159 - serviceaccount 160 service_account_contents: 161 description: 162 - The contents of a Service Account JSON file, either in a dictionary or as a 163 JSON string that represents it. 164 type: jsonarg 165 service_account_file: 166 description: 167 - The path of a Service Account JSON file if serviceaccount is selected as type. 168 type: path 169 service_account_email: 170 description: 171 - An optional service account email address if machineaccount is selected and 172 the user does not wish to use the default email. 173 type: str 174 scopes: 175 description: 176 - Array of scopes to be used 177 type: list 178 elements: str 179 env_type: 180 description: 181 - Specifies which Ansible environment you're running this module within. 182 - This should not be set unless you know what you're doing. 183 - This only alters the User Agent string for any API requests. 184 type: str 185notes: 186- 'API Reference: U(https://cloud.google.com/compute/docs/reference/rest/v1/snapshots)' 187- 'Official Documentation: U(https://cloud.google.com/compute/docs/disks/create-snapshots)' 188- for authentication, you can set service_account_file using the C(gcp_service_account_file) 189 env variable. 190- for authentication, you can set service_account_contents using the C(GCP_SERVICE_ACCOUNT_CONTENTS) 191 env variable. 192- For authentication, you can set service_account_email using the C(GCP_SERVICE_ACCOUNT_EMAIL) 193 env variable. 194- For authentication, you can set auth_kind using the C(GCP_AUTH_KIND) env variable. 195- For authentication, you can set scopes using the C(GCP_SCOPES) env variable. 196- Environment variables values will only be used if the playbook values are not set. 197- The I(service_account_email) and I(service_account_file) options are mutually exclusive. 198''' 199 200EXAMPLES = ''' 201- name: create a disk 202 google.cloud.gcp_compute_disk: 203 name: disk-snapshot 204 zone: us-central1-a 205 project: "{{ gcp_project }}" 206 auth_kind: "{{ gcp_cred_kind }}" 207 service_account_file: "{{ gcp_cred_file }}" 208 state: present 209 register: disk 210 211- name: create a snapshot 212 google.cloud.gcp_compute_snapshot: 213 name: test_object 214 source_disk: "{{ disk }}" 215 zone: us-central1-a 216 labels: 217 my_label: value 218 project: test_project 219 auth_kind: serviceaccount 220 service_account_file: "/tmp/auth.pem" 221 state: present 222''' 223 224RETURN = ''' 225creationTimestamp: 226 description: 227 - Creation timestamp in RFC3339 text format. 228 returned: success 229 type: str 230id: 231 description: 232 - The unique identifier for the resource. 233 returned: success 234 type: int 235diskSizeGb: 236 description: 237 - Size of the snapshot, specified in GB. 238 returned: success 239 type: int 240name: 241 description: 242 - Name of the resource; provided by the client when the resource is created. The 243 name must be 1-63 characters long, and comply with RFC1035. Specifically, the 244 name must be 1-63 characters long and match the regular expression `[a-z]([-a-z0-9]*[a-z0-9])?` 245 which means the first character must be a lowercase letter, and all following 246 characters must be a dash, lowercase letter, or digit, except the last character, 247 which cannot be a dash. 248 returned: success 249 type: str 250description: 251 description: 252 - An optional description of this resource. 253 returned: success 254 type: str 255storageBytes: 256 description: 257 - A size of the storage used by the snapshot. As snapshots share storage, this number 258 is expected to change with snapshot creation/deletion. 259 returned: success 260 type: int 261storageLocations: 262 description: 263 - Cloud Storage bucket storage location of the snapshot (regional or multi-regional). 264 returned: success 265 type: list 266licenses: 267 description: 268 - A list of public visible licenses that apply to this snapshot. This can be because 269 the original image had licenses attached (such as a Windows image). snapshotEncryptionKey 270 nested object Encrypts the snapshot using a customer-supplied encryption key. 271 returned: success 272 type: list 273labels: 274 description: 275 - Labels to apply to this Snapshot. 276 returned: success 277 type: dict 278labelFingerprint: 279 description: 280 - The fingerprint used for optimistic locking of this resource. Used internally 281 during updates. 282 returned: success 283 type: str 284sourceDisk: 285 description: 286 - A reference to the disk used to create this snapshot. 287 returned: success 288 type: dict 289zone: 290 description: 291 - A reference to the zone where the disk is hosted. 292 returned: success 293 type: str 294snapshotEncryptionKey: 295 description: 296 - The customer-supplied encryption key of the snapshot. Required if the source snapshot 297 is protected by a customer-supplied encryption key. 298 returned: success 299 type: complex 300 contains: 301 rawKey: 302 description: 303 - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 304 base64 to either encrypt or decrypt this resource. 305 returned: success 306 type: str 307 sha256: 308 description: 309 - The RFC 4648 base64 encoded SHA-256 hash of the customer-supplied encryption 310 key that protects this resource. 311 returned: success 312 type: str 313 kmsKeyName: 314 description: 315 - The name of the encryption key that is stored in Google Cloud KMS. 316 returned: success 317 type: str 318 kmsKeyServiceAccount: 319 description: 320 - The service account used for the encryption request for the given KMS key. 321 - If absent, the Compute Engine Service Agent service account is used. 322 returned: success 323 type: str 324sourceDiskEncryptionKey: 325 description: 326 - The customer-supplied encryption key of the source snapshot. Required if the source 327 snapshot is protected by a customer-supplied encryption key. 328 returned: success 329 type: complex 330 contains: 331 rawKey: 332 description: 333 - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 334 base64 to either encrypt or decrypt this resource. 335 returned: success 336 type: str 337 kmsKeyName: 338 description: 339 - The name of the encryption key that is stored in Google Cloud KMS. 340 returned: success 341 type: str 342 kmsKeyServiceAccount: 343 description: 344 - The service account used for the encryption request for the given KMS key. 345 - If absent, the Compute Engine Service Agent service account is used. 346 returned: success 347 type: str 348''' 349 350################################################################################ 351# Imports 352################################################################################ 353 354from ansible_collections.google.cloud.plugins.module_utils.gcp_utils import ( 355 navigate_hash, 356 GcpSession, 357 GcpModule, 358 GcpRequest, 359 remove_nones_from_dict, 360 replace_resource_dict, 361) 362import json 363import re 364import time 365 366################################################################################ 367# Main 368################################################################################ 369 370 371def main(): 372 """Main function""" 373 374 module = GcpModule( 375 argument_spec=dict( 376 state=dict(default='present', choices=['present', 'absent'], type='str'), 377 name=dict(required=True, type='str'), 378 description=dict(type='str'), 379 storage_locations=dict(type='list', elements='str'), 380 labels=dict(type='dict'), 381 source_disk=dict(required=True, type='dict'), 382 zone=dict(type='str'), 383 snapshot_encryption_key=dict( 384 type='dict', no_log=True, options=dict(raw_key=dict(type='str'), kms_key_name=dict(type='str'), kms_key_service_account=dict(type='str')) 385 ), 386 source_disk_encryption_key=dict( 387 type='dict', no_log=True, options=dict(raw_key=dict(type='str'), kms_key_name=dict(type='str'), kms_key_service_account=dict(type='str')) 388 ), 389 ) 390 ) 391 392 if not module.params['scopes']: 393 module.params['scopes'] = ['https://www.googleapis.com/auth/compute'] 394 395 state = module.params['state'] 396 kind = 'compute#snapshot' 397 398 fetch = fetch_resource(module, self_link(module), kind) 399 changed = False 400 401 if fetch: 402 if state == 'present': 403 if is_different(module, fetch): 404 update(module, self_link(module), kind, fetch) 405 fetch = fetch_resource(module, self_link(module), kind) 406 changed = True 407 else: 408 delete(module, self_link(module), kind) 409 fetch = {} 410 changed = True 411 else: 412 if state == 'present': 413 fetch = create(module, create_link(module), kind) 414 changed = True 415 else: 416 fetch = {} 417 418 fetch.update({'changed': changed}) 419 420 module.exit_json(**fetch) 421 422 423def create(module, link, kind): 424 auth = GcpSession(module, 'compute') 425 return wait_for_operation(module, auth.post(link, resource_to_request(module))) 426 427 428def update(module, link, kind, fetch): 429 update_fields(module, resource_to_request(module), response_to_hash(module, fetch)) 430 return fetch_resource(module, self_link(module), kind) 431 432 433def update_fields(module, request, response): 434 if response.get('labels') != request.get('labels'): 435 labels_update(module, request, response) 436 437 438def labels_update(module, request, response): 439 auth = GcpSession(module, 'compute') 440 auth.post( 441 ''.join(["https://compute.googleapis.com/compute/v1/", "projects/{project}/global/snapshots/{name}/setLabels"]).format(**module.params), 442 {u'labels': module.params.get('labels'), u'labelFingerprint': response.get('labelFingerprint')}, 443 ) 444 445 446def delete(module, link, kind): 447 auth = GcpSession(module, 'compute') 448 return wait_for_operation(module, auth.delete(link)) 449 450 451def resource_to_request(module): 452 request = { 453 u'kind': 'compute#snapshot', 454 u'sourceDisk': replace_resource_dict(module.params.get(u'source_disk', {}), 'name'), 455 u'zone': module.params.get('zone'), 456 u'name': module.params.get('name'), 457 u'description': module.params.get('description'), 458 u'storageLocations': module.params.get('storage_locations'), 459 u'labels': module.params.get('labels'), 460 } 461 return_vals = {} 462 for k, v in request.items(): 463 if v or v is False: 464 return_vals[k] = v 465 466 return return_vals 467 468 469def fetch_resource(module, link, kind, allow_not_found=True): 470 auth = GcpSession(module, 'compute') 471 return return_if_object(module, auth.get(link), kind, allow_not_found) 472 473 474def self_link(module): 475 return "https://compute.googleapis.com/compute/v1/projects/{project}/global/snapshots/{name}".format(**module.params) 476 477 478def collection(module): 479 return "https://compute.googleapis.com/compute/v1/projects/{project}/global/snapshots".format(**module.params) 480 481 482def create_link(module): 483 res = {'project': module.params['project'], 'zone': module.params['zone'], 'source_disk': replace_resource_dict(module.params['source_disk'], 'name')} 484 return "https://compute.googleapis.com/compute/v1/projects/{project}/zones/{zone}/disks/{source_disk}/createSnapshot".format(**res) 485 486 487def return_if_object(module, response, kind, allow_not_found=False): 488 # If not found, return nothing. 489 if allow_not_found and response.status_code == 404: 490 return None 491 492 # If no content, return nothing. 493 if response.status_code == 204: 494 return None 495 496 try: 497 module.raise_for_status(response) 498 result = response.json() 499 except getattr(json.decoder, 'JSONDecodeError', ValueError): 500 module.fail_json(msg="Invalid JSON response with error: %s" % response.text) 501 502 if navigate_hash(result, ['error', 'errors']): 503 module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) 504 505 return result 506 507 508def is_different(module, response): 509 request = resource_to_request(module) 510 response = response_to_hash(module, response) 511 512 # Remove all output-only from response. 513 response_vals = {} 514 for k, v in response.items(): 515 if k in request: 516 response_vals[k] = v 517 518 request_vals = {} 519 for k, v in request.items(): 520 if k in response: 521 request_vals[k] = v 522 523 return GcpRequest(request_vals) != GcpRequest(response_vals) 524 525 526# Remove unnecessary properties from the response. 527# This is for doing comparisons with Ansible's current parameters. 528def response_to_hash(module, response): 529 return { 530 u'creationTimestamp': response.get(u'creationTimestamp'), 531 u'id': response.get(u'id'), 532 u'diskSizeGb': response.get(u'diskSizeGb'), 533 u'name': module.params.get('name'), 534 u'description': module.params.get('description'), 535 u'storageBytes': response.get(u'storageBytes'), 536 u'storageLocations': response.get(u'storageLocations'), 537 u'licenses': response.get(u'licenses'), 538 u'labels': response.get(u'labels'), 539 u'labelFingerprint': response.get(u'labelFingerprint'), 540 } 541 542 543def license_selflink(name, params): 544 if name is None: 545 return 546 url = r"https://compute.googleapis.com/compute/v1//projects/.*/global/licenses/.*" 547 if not re.match(url, name): 548 name = "https://compute.googleapis.com/compute/v1//projects/{project}/global/licenses/%s".format(**params) % name 549 return name 550 551 552def async_op_url(module, extra_data=None): 553 if extra_data is None: 554 extra_data = {} 555 url = "https://compute.googleapis.com/compute/v1/" 556 combined = extra_data.copy() 557 combined.update(module.params) 558 return url.format(**combined) 559 560 561def wait_for_operation(module, response): 562 op_result = return_if_object(module, response, 'compute#operation') 563 if op_result is None: 564 return {} 565 status = navigate_hash(op_result, ['status']) 566 wait_done = wait_for_completion(status, op_result, module) 567 return fetch_resource(module, navigate_hash(wait_done, ['targetLink']), 'compute#snapshot') 568 569 570def wait_for_completion(status, op_result, module): 571 op_id = navigate_hash(op_result, ['name']) 572 op_uri = navigate_hash(op_result, ['selfLink']) 573 while status != 'DONE': 574 raise_if_errors(op_result, ['error', 'errors'], module) 575 time.sleep(1.0) 576 op_result = fetch_resource(module, op_uri, 'compute#operation', False) 577 status = navigate_hash(op_result, ['status']) 578 return op_result 579 580 581def raise_if_errors(response, err_path, module): 582 errors = navigate_hash(response, err_path) 583 if errors is not None: 584 module.fail_json(msg=errors) 585 586 587class SnapshotSnapshotencryptionkey(object): 588 def __init__(self, request, module): 589 self.module = module 590 if request: 591 self.request = request 592 else: 593 self.request = {} 594 595 def to_request(self): 596 return remove_nones_from_dict( 597 { 598 u'rawKey': self.request.get('raw_key'), 599 u'kmsKeyName': self.request.get('kms_key_name'), 600 u'kmsKeyServiceAccount': self.request.get('kms_key_service_account'), 601 } 602 ) 603 604 def from_response(self): 605 return remove_nones_from_dict( 606 { 607 u'rawKey': self.request.get(u'rawKey'), 608 u'kmsKeyName': self.request.get(u'kmsKeyName'), 609 u'kmsKeyServiceAccount': self.request.get(u'kmsKeyServiceAccount'), 610 } 611 ) 612 613 614class SnapshotSourcediskencryptionkey(object): 615 def __init__(self, request, module): 616 self.module = module 617 if request: 618 self.request = request 619 else: 620 self.request = {} 621 622 def to_request(self): 623 return remove_nones_from_dict( 624 { 625 u'rawKey': self.request.get('raw_key'), 626 u'kmsKeyName': self.request.get('kms_key_name'), 627 u'kmsKeyServiceAccount': self.request.get('kms_key_service_account'), 628 } 629 ) 630 631 def from_response(self): 632 return remove_nones_from_dict( 633 { 634 u'rawKey': self.request.get(u'rawKey'), 635 u'kmsKeyName': self.request.get(u'kmsKeyName'), 636 u'kmsKeyServiceAccount': self.request.get(u'kmsKeyServiceAccount'), 637 } 638 ) 639 640 641if __name__ == '__main__': 642 main() 643