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