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