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_resourcemanager_project
33description:
34- Represents a GCP Project. A project is a container for ACLs, APIs, App Engine Apps,
35  VMs, and other Google Cloud Platform resources.
36short_description: Creates a GCP Project
37version_added: 2.8
38author: Google Inc. (@googlecloudplatform)
39requirements:
40- python >= 2.6
41- requests >= 2.18.4
42- google-auth >= 1.3.0
43options:
44  state:
45    description:
46    - Whether the given object should exist in GCP
47    choices:
48    - present
49    - absent
50    default: present
51    type: str
52  name:
53    description:
54    - 'The user-assigned display name of the Project. It must be 4 to 30 characters.
55      Allowed characters are: lowercase and uppercase letters, numbers, hyphen, single-quote,
56      double-quote, space, and exclamation point.'
57    required: false
58    type: str
59  labels:
60    description:
61    - The labels associated with this Project.
62    - 'Label keys must be between 1 and 63 characters long and must conform to the
63      following regular expression: `[a-z]([-a-z0-9]*[a-z0-9])?`.'
64    - Label values must be between 0 and 63 characters long and must conform to the
65      regular expression `([a-z]([-a-z0-9]*[a-z0-9])?)?`.
66    - No more than 256 labels can be associated with a given resource.
67    - Clients should store labels in a representation such as JSON that does not depend
68      on specific characters being disallowed .
69    required: false
70    type: dict
71  parent:
72    description:
73    - A parent organization.
74    required: false
75    type: dict
76    suboptions:
77      type:
78        description:
79        - Must be organization.
80        required: false
81        type: str
82      id:
83        description:
84        - Id of the organization.
85        required: false
86        type: str
87  id:
88    description:
89    - The unique, user-assigned ID of the Project. It must be 6 to 30 lowercase letters,
90      digits, or hyphens. It must start with a letter.
91    - Trailing hyphens are prohibited.
92    required: true
93    type: str
94extends_documentation_fragment: gcp
95'''
96
97EXAMPLES = '''
98- name: create a project
99  gcp_resourcemanager_project:
100    name: My Sample Project
101    id: alextest-{{ 10000000000 | random }}
102    auth_kind: serviceaccount
103    service_account_file: "/tmp/auth.pem"
104    parent:
105      type: organization
106      id: 636173955921
107    state: present
108'''
109
110RETURN = '''
111number:
112  description:
113  - Number uniquely identifying the project.
114  returned: success
115  type: int
116lifecycleState:
117  description:
118  - The Project lifecycle state.
119  returned: success
120  type: str
121name:
122  description:
123  - 'The user-assigned display name of the Project. It must be 4 to 30 characters.
124    Allowed characters are: lowercase and uppercase letters, numbers, hyphen, single-quote,
125    double-quote, space, and exclamation point.'
126  returned: success
127  type: str
128createTime:
129  description:
130  - Time of creation.
131  returned: success
132  type: str
133labels:
134  description:
135  - The labels associated with this Project.
136  - 'Label keys must be between 1 and 63 characters long and must conform to the following
137    regular expression: `[a-z]([-a-z0-9]*[a-z0-9])?`.'
138  - Label values must be between 0 and 63 characters long and must conform to the
139    regular expression `([a-z]([-a-z0-9]*[a-z0-9])?)?`.
140  - No more than 256 labels can be associated with a given resource.
141  - Clients should store labels in a representation such as JSON that does not depend
142    on specific characters being disallowed .
143  returned: success
144  type: dict
145parent:
146  description:
147  - A parent organization.
148  returned: success
149  type: complex
150  contains:
151    type:
152      description:
153      - Must be organization.
154      returned: success
155      type: str
156    id:
157      description:
158      - Id of the organization.
159      returned: success
160      type: str
161id:
162  description:
163  - The unique, user-assigned ID of the Project. It must be 6 to 30 lowercase letters,
164    digits, or hyphens. It must start with a letter.
165  - Trailing hyphens are prohibited.
166  returned: success
167  type: str
168'''
169
170################################################################################
171# Imports
172################################################################################
173
174from ansible.module_utils.gcp_utils import navigate_hash, GcpSession, GcpModule, GcpRequest, remove_nones_from_dict, replace_resource_dict
175import json
176import time
177
178################################################################################
179# Main
180################################################################################
181
182
183def main():
184    """Main function"""
185
186    module = GcpModule(
187        argument_spec=dict(
188            state=dict(default='present', choices=['present', 'absent'], type='str'),
189            name=dict(type='str'),
190            labels=dict(type='dict'),
191            parent=dict(type='dict', options=dict(type=dict(type='str'), id=dict(type='str'))),
192            id=dict(required=True, type='str'),
193        )
194    )
195
196    if not module.params['scopes']:
197        module.params['scopes'] = ['https://www.googleapis.com/auth/cloud-platform']
198
199    state = module.params['state']
200
201    fetch = fetch_resource(module, self_link(module))
202    changed = False
203
204    if fetch:
205        if state == 'present':
206            if is_different(module, fetch):
207                update(module, self_link(module))
208                fetch = fetch_resource(module, self_link(module))
209                changed = True
210        else:
211            delete(module, self_link(module))
212            fetch = {}
213            changed = True
214    else:
215        if state == 'present':
216            fetch = create(module, collection(module))
217            changed = True
218        else:
219            fetch = {}
220
221    fetch.update({'changed': changed})
222
223    module.exit_json(**fetch)
224
225
226def create(module, link):
227    auth = GcpSession(module, 'resourcemanager')
228    return wait_for_operation(module, auth.post(link, resource_to_request(module)))
229
230
231def update(module, link):
232    auth = GcpSession(module, 'resourcemanager')
233    return wait_for_operation(module, auth.put(link, resource_to_request(module)))
234
235
236def delete(module, link):
237    auth = GcpSession(module, 'resourcemanager')
238    return wait_for_operation(module, auth.delete(link))
239
240
241def resource_to_request(module):
242    request = {
243        u'projectId': module.params.get('id'),
244        u'name': module.params.get('name'),
245        u'labels': module.params.get('labels'),
246        u'parent': ProjectParent(module.params.get('parent', {}), module).to_request(),
247    }
248    return_vals = {}
249    for k, v in request.items():
250        if v or v is False:
251            return_vals[k] = v
252
253    return return_vals
254
255
256def fetch_resource(module, link, allow_not_found=True):
257    auth = GcpSession(module, 'resourcemanager')
258    return return_if_object(module, auth.get(link), allow_not_found)
259
260
261def self_link(module):
262    return "https://cloudresourcemanager.googleapis.com/v1/projects/{id}".format(**module.params)
263
264
265def collection(module):
266    return "https://cloudresourcemanager.googleapis.com/v1/projects".format(**module.params)
267
268
269def return_if_object(module, response, allow_not_found=False):
270    # If not found, return nothing.
271    if allow_not_found and response.status_code == 404:
272        return None
273
274    # If no content, return nothing.
275    if response.status_code == 204:
276        return None
277
278    # SQL only: return on 403 if not exist
279    if allow_not_found and response.status_code == 403:
280        return None
281
282    try:
283        result = response.json()
284    except getattr(json.decoder, 'JSONDecodeError', ValueError) as inst:
285        module.fail_json(msg="Invalid JSON response with error: %s" % inst)
286
287    if navigate_hash(result, ['error', 'message']):
288        module.fail_json(msg=navigate_hash(result, ['error', 'message']))
289
290    return result
291
292
293def is_different(module, response):
294    request = resource_to_request(module)
295    response = response_to_hash(module, response)
296
297    # Remove all output-only from response.
298    response_vals = {}
299    for k, v in response.items():
300        if k in request:
301            response_vals[k] = v
302
303    request_vals = {}
304    for k, v in request.items():
305        if k in response:
306            request_vals[k] = v
307
308    return GcpRequest(request_vals) != GcpRequest(response_vals)
309
310
311# Remove unnecessary properties from the response.
312# This is for doing comparisons with Ansible's current parameters.
313def response_to_hash(module, response):
314    return {
315        u'projectNumber': response.get(u'projectNumber'),
316        u'lifecycleState': response.get(u'lifecycleState'),
317        u'name': response.get(u'name'),
318        u'createTime': response.get(u'createTime'),
319        u'labels': response.get(u'labels'),
320        u'parent': ProjectParent(response.get(u'parent', {}), module).from_response(),
321    }
322
323
324def async_op_url(module, extra_data=None):
325    if extra_data is None:
326        extra_data = {}
327    url = "https://cloudresourcemanager.googleapis.com/v1/{op_id}"
328    combined = extra_data.copy()
329    combined.update(module.params)
330    return url.format(**combined)
331
332
333def wait_for_operation(module, response):
334    op_result = return_if_object(module, response)
335    if op_result is None:
336        return {}
337    status = navigate_hash(op_result, ['done'])
338    wait_done = wait_for_completion(status, op_result, module)
339    raise_if_errors(wait_done, ['error'], module)
340    return navigate_hash(wait_done, ['response'])
341
342
343def wait_for_completion(status, op_result, module):
344    op_id = navigate_hash(op_result, ['name'])
345    op_uri = async_op_url(module, {'op_id': op_id})
346    while not status:
347        raise_if_errors(op_result, ['error'], module)
348        time.sleep(1.0)
349        op_result = fetch_resource(module, op_uri, False)
350        status = navigate_hash(op_result, ['done'])
351    return op_result
352
353
354def raise_if_errors(response, err_path, module):
355    errors = navigate_hash(response, err_path)
356    if errors is not None:
357        module.fail_json(msg=errors)
358
359
360class ProjectParent(object):
361    def __init__(self, request, module):
362        self.module = module
363        if request:
364            self.request = request
365        else:
366            self.request = {}
367
368    def to_request(self):
369        return remove_nones_from_dict({u'type': self.request.get('type'), u'id': self.request.get('id')})
370
371    def from_response(self):
372        return remove_nones_from_dict({u'type': self.request.get(u'type'), u'id': self.request.get(u'id')})
373
374
375if __name__ == '__main__':
376    main()
377