From 18a088c64e4dc27955d18128a9978a9e24f63fb1 Mon Sep 17 00:00:00 2001 From: Ryan Brown Date: Thu, 18 Oct 2018 10:55:42 -0400 Subject: [PATCH] GCP MagicModules bug fixes (#47285) Closes: #46930 #46929 #46928 #46927 #46926 #46925 #46924 #46923 #46922 #46921 #46920 #46919 #46918 #46917 #46916 #46915 #46914 #46913 #46912 #46911 #46910 #46909 #46908 #46907 #46906 #46905 #46903 #46902 #46901 #46900 #46899 #46898 #46897 #46896 #46895 #46894 #46893 #46892 #46891 #46890 #46889 #46888 #46887 #46886 #46885 #46884 #46883 #46882 #46881 #46880 #46879 #46878 #46877 #46876 #46875 #46874 #46873 #46872 #46871 #46870 #46869 #46868 #46867 #46866 #46865 #46864 #46863 #46862 #46861 #46860 #46859 #46858 #46857 #46856 #46855 #46854 #46853 #46852 --- .../cloud/google/gcp_compute_address.py | 45 +++- .../cloud/google/gcp_compute_address_facts.py | 13 +- .../google/gcp_compute_backend_bucket.py | 21 +- .../gcp_compute_backend_bucket_facts.py | 10 +- .../google/gcp_compute_backend_service.py | 72 ++--- .../gcp_compute_backend_service_facts.py | 83 ++++-- .../modules/cloud/google/gcp_compute_disk.py | 111 +++++--- .../cloud/google/gcp_compute_disk_facts.py | 36 +-- .../cloud/google/gcp_compute_firewall.py | 253 ++++++++++++++++-- .../google/gcp_compute_firewall_facts.py | 89 +++++- .../google/gcp_compute_forwarding_rule.py | 114 ++++++-- .../gcp_compute_forwarding_rule_facts.py | 29 +- .../google/gcp_compute_global_address.py | 73 +++-- .../gcp_compute_global_address_facts.py | 21 +- .../gcp_compute_global_forwarding_rule.py | 44 +-- ...cp_compute_global_forwarding_rule_facts.py | 18 +- .../cloud/google/gcp_compute_health_check.py | 98 ++++--- .../google/gcp_compute_health_check_facts.py | 42 +-- .../google/gcp_compute_http_health_check.py | 27 +- .../gcp_compute_http_health_check_facts.py | 16 +- .../google/gcp_compute_https_health_check.py | 27 +- .../gcp_compute_https_health_check_facts.py | 16 +- .../modules/cloud/google/gcp_compute_image.py | 48 ++-- .../cloud/google/gcp_compute_image_facts.py | 32 +-- .../cloud/google/gcp_compute_instance.py | 128 +++++---- .../google/gcp_compute_instance_facts.py | 66 ++--- .../google/gcp_compute_instance_group.py | 117 +++++++- .../gcp_compute_instance_group_facts.py | 15 +- .../gcp_compute_instance_group_manager.py | 38 +-- ...cp_compute_instance_group_manager_facts.py | 20 +- .../google/gcp_compute_instance_template.py | 100 ++++--- .../gcp_compute_instance_template_facts.py | 60 ++--- .../cloud/google/gcp_compute_network.py | 140 +++++++--- .../cloud/google/gcp_compute_network_facts.py | 23 +- .../modules/cloud/google/gcp_compute_route.py | 35 +-- .../cloud/google/gcp_compute_route_facts.py | 16 +- .../cloud/google/gcp_compute_router.py | 28 +- .../cloud/google/gcp_compute_router_facts.py | 10 +- .../google/gcp_compute_ssl_certificate.py | 42 +-- .../gcp_compute_ssl_certificate_facts.py | 10 +- .../cloud/google/gcp_compute_ssl_policy.py | 23 +- .../google/gcp_compute_ssl_policy_facts.py | 12 +- .../cloud/google/gcp_compute_subnetwork.py | 189 +++++++++++-- .../google/gcp_compute_subnetwork_facts.py | 44 ++- .../google/gcp_compute_target_http_proxy.py | 47 +++- .../gcp_compute_target_http_proxy_facts.py | 8 +- .../google/gcp_compute_target_https_proxy.py | 81 +++++- .../gcp_compute_target_https_proxy_facts.py | 12 +- .../cloud/google/gcp_compute_target_pool.py | 35 ++- .../google/gcp_compute_target_pool_facts.py | 12 +- .../gcp_compute_target_ssl_proxy_facts.py | 10 +- .../google/gcp_compute_target_tcp_proxy.py | 62 ++++- .../gcp_compute_target_tcp_proxy_facts.py | 8 +- .../google/gcp_compute_target_vpn_gateway.py | 27 +- .../gcp_compute_target_vpn_gateway_facts.py | 6 +- .../cloud/google/gcp_compute_url_map.py | 94 ++++--- .../cloud/google/gcp_compute_url_map_facts.py | 24 +- .../cloud/google/gcp_compute_vpn_tunnel.py | 83 ++++-- .../google/gcp_compute_vpn_tunnel_facts.py | 26 +- .../cloud/google/gcp_container_cluster.py | 65 ++--- .../cloud/google/gcp_container_node_pool.py | 44 +-- .../cloud/google/gcp_dns_managed_zone.py | 23 +- .../google/gcp_dns_resource_record_set.py | 26 +- .../cloud/google/gcp_pubsub_subscription.py | 26 +- .../modules/cloud/google/gcp_pubsub_topic.py | 13 +- .../cloud/google/gcp_spanner_database.py | 20 +- .../cloud/google/gcp_spanner_instance.py | 17 +- .../modules/cloud/google/gcp_sql_database.py | 16 +- .../modules/cloud/google/gcp_sql_instance.py | 91 ++++--- .../modules/cloud/google/gcp_sql_user.py | 16 +- .../cloud/google/gcp_storage_bucket.py | 73 ++--- .../gcp_storage_bucket_access_control.py | 26 +- .../gcp_container_node_pool/tasks/main.yml | 4 +- .../gcp_dns_managed_zone/tasks/main.yml | 24 +- .../tasks/main.yml | 26 ++ .../gcp_pubsub_subscription/tasks/main.yml | 22 +- .../targets/gcp_pubsub_topic/tasks/main.yml | 22 +- .../targets/gcp_sql_user/tasks/main.yml | 4 +- 78 files changed, 2452 insertions(+), 1095 deletions(-) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_address.py b/lib/ansible/modules/cloud/google/gcp_compute_address.py index c7d40c9838d..7ac0cbec694 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_address.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_address.py @@ -81,11 +81,24 @@ options: letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash. required: true + network_tier: + description: + - 'The networking tier used for configuring this address. This field can take the + following values: PREMIUM or STANDARD. If this field is not specified, it is assumed + to be PREMIUM.' + required: false + version_added: 2.8 + choices: ['PREMIUM', 'STANDARD'] subnetwork: description: - The URL of the subnetwork in which to reserve the address. If an IP address is specified, it must be within the subnetwork's IP range. - This field can only be used with INTERNAL type with GCE_ENDPOINT/DNS_RESOLVER purposes. + - 'This field represents a link to a Subnetwork resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_subnetwork + task and then set this subnetwork field to "{{ name-of-resource }}" Alternatively, + you can set this subnetwork to a dictionary with the selfLink key where the value + is the selfLink of your Subnetwork.' required: false version_added: 2.7 region: @@ -106,7 +119,7 @@ EXAMPLES = ''' name: test-address1 region: us-west1 project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -119,13 +132,13 @@ RETURN = ''' be inside the specified subnetwork, if any. returned: success type: str - address_type: + addressType: description: - The type of address to reserve, either INTERNAL or EXTERNAL. - If unspecified, defaults to EXTERNAL. returned: success type: str - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -149,6 +162,13 @@ RETURN = ''' except the last character, which cannot be a dash. returned: success type: str + networkTier: + description: + - 'The networking tier used for configuring this address. This field can take the + following values: PREMIUM or STANDARD. If this field is not specified, it is assumed + to be PREMIUM.' + returned: success + type: str subnetwork: description: - The URL of the subnetwork in which to reserve the address. If an IP address is specified, @@ -192,6 +212,7 @@ def main(): address_type=dict(default='EXTERNAL', type='str', choices=['INTERNAL', 'EXTERNAL']), description=dict(type='str'), name=dict(required=True, type='str'), + network_tier=dict(type='str', choices=['PREMIUM', 'STANDARD']), subnetwork=dict(type='dict'), region=dict(required=True, type='str') ) @@ -209,7 +230,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -233,8 +255,7 @@ def create(module, link, kind): def update(module, link, kind): - auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + module.fail_json(msg="Address cannot be edited") def delete(module, link, kind): @@ -249,6 +270,7 @@ def resource_to_request(module): u'addressType': module.params.get('address_type'), u'description': module.params.get('description'), u'name': module.params.get('name'), + u'networkTier': module.params.get('network_tier'), u'subnetwork': replace_resource_dict(module.params.get(u'subnetwork', {}), 'selfLink') } return_vals = {} @@ -259,9 +281,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -272,9 +294,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/regions/{region}/addresses".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -289,8 +311,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result @@ -323,6 +343,7 @@ def response_to_hash(module, response): u'description': response.get(u'description'), u'id': response.get(u'id'), u'name': response.get(u'name'), + u'networkTier': response.get(u'networkTier'), u'subnetwork': response.get(u'subnetwork'), u'users': response.get(u'users') } diff --git a/lib/ansible/modules/cloud/google/gcp_compute_address_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_address_facts.py index a011ffc904c..4e6ed2792df 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_address_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_address_facts.py @@ -62,7 +62,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -79,13 +79,13 @@ items: be inside the specified subnetwork, if any. returned: success type: str - address_type: + addressType: description: - The type of address to reserve, either INTERNAL or EXTERNAL. - If unspecified, defaults to EXTERNAL. returned: success type: str - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -109,6 +109,13 @@ items: except the last character, which cannot be a dash. returned: success type: str + networkTier: + description: + - 'The networking tier used for configuring this address. This field can take the + following values: PREMIUM or STANDARD. If this field is not specified, it is assumed + to be PREMIUM.' + returned: success + type: str subnetwork: description: - The URL of the subnetwork in which to reserve the address. If an IP address is specified, diff --git a/lib/ansible/modules/cloud/google/gcp_compute_backend_bucket.py b/lib/ansible/modules/cloud/google/gcp_compute_backend_bucket.py index 8cd3e419557..0ef41ad3c86 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_backend_bucket.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_backend_bucket.py @@ -96,18 +96,18 @@ EXAMPLES = ''' description: A BackendBucket to connect LNB w/ Storage Bucket enable_cdn: true project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - bucket_name: + bucketName: description: - Cloud Storage bucket name. returned: success type: str - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -118,7 +118,7 @@ RETURN = ''' resource is created. returned: success type: str - enable_cdn: + enableCdn: description: - If true, enable Cloud CDN for this BackendBucket. returned: success @@ -178,7 +178,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -227,9 +228,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -240,9 +241,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/backendBuckets".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -257,8 +258,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_backend_bucket_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_backend_bucket_facts.py index 6686db14d99..29a7dea6039 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_backend_bucket_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_backend_bucket_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,12 +66,12 @@ items: returned: always type: complex contains: - bucket_name: + bucketName: description: - Cloud Storage bucket name. returned: success type: str - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -82,7 +82,7 @@ items: resource is created. returned: success type: str - enable_cdn: + enableCdn: description: - If true, enable Cloud CDN for this BackendBucket. returned: success @@ -118,7 +118,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_backend_service.py b/lib/ansible/modules/cloud/google/gcp_compute_backend_service.py index e5a796f3f38..56c77ac5568 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_backend_service.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_backend_service.py @@ -89,6 +89,11 @@ options: - No two backends in a backend service are allowed to use same Instance Group resource. - When the BackendService has load balancing scheme INTERNAL, the instance group must be in a zone within the same region as the BackendService. + - 'This field represents a link to a InstanceGroup resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_instance_group + task and then set this group field to "{{ name-of-resource }}" Alternatively, you + can set this group to a dictionary with the selfLink key where the value is the + selfLink of your InstanceGroup.' required: false max_connections: description: @@ -305,13 +310,13 @@ EXAMPLES = ''' - "{{ healthcheck.selfLink }}" enable_cdn: true project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - affinity_cookie_ttl_sec: + affinityCookieTtlSec: description: - Lifetime of cookies in seconds if session_affinity is GENERATED_COOKIE. If set to 0, the cookie is non-persistent and lasts only until the end of the browser session @@ -325,7 +330,7 @@ RETURN = ''' returned: success type: complex contains: - balancing_mode: + balancingMode: description: - Specifies the balancing mode for this backend. - For global HTTP(S) or TCP/SSL load balancing, the default is UTILIZATION. Valid @@ -333,7 +338,7 @@ RETURN = ''' - This cannot be used for internal load balancing. returned: success type: str - capacity_scaler: + capacityScaler: description: - A multiplier applied to the group's maximum servicing capacity (based on UTILIZATION, RATE or CONNECTION). @@ -359,7 +364,7 @@ RETURN = ''' be in a zone within the same region as the BackendService. returned: success type: dict - max_connections: + maxConnections: description: - The max number of simultaneous connections for the group. Can be used with either CONNECTION or UTILIZATION balancing modes. @@ -368,7 +373,7 @@ RETURN = ''' - This cannot be used for internal load balancing. returned: success type: int - max_connections_per_instance: + maxConnectionsPerInstance: description: - The max number of simultaneous connections that a single backend instance can handle. This is used to calculate the capacity of the group. Can be used in either CONNECTION @@ -378,7 +383,7 @@ RETURN = ''' - This cannot be used for internal load balancing. returned: success type: int - max_rate: + maxRate: description: - The max requests per second (RPS) of the group. - Can be used with either RATE or UTILIZATION balancing modes, but required if RATE @@ -386,7 +391,7 @@ RETURN = ''' - This cannot be used for internal load balancing. returned: success type: int - max_rate_per_instance: + maxRatePerInstance: description: - The max requests per second (RPS) that a single backend instance can handle. This is used to calculate the capacity of the group. Can be used in either balancing @@ -394,43 +399,43 @@ RETURN = ''' - This cannot be used for internal load balancing. returned: success type: str - max_utilization: + maxUtilization: description: - Used when balancingMode is UTILIZATION. This ratio defines the CPU utilization target for the group. The default is 0.8. Valid range is [0.0, 1.0]. - This cannot be used for internal load balancing. returned: success type: str - cdn_policy: + cdnPolicy: description: - Cloud CDN configuration for this BackendService. returned: success type: complex contains: - cache_key_policy: + cacheKeyPolicy: description: - The CacheKeyPolicy for this CdnPolicy. returned: success type: complex contains: - include_host: + includeHost: description: - If true requests to different hosts will be cached separately. returned: success type: bool - include_protocol: + includeProtocol: description: - If true, http and https requests will be cached separately. returned: success type: bool - include_query_string: + includeQueryString: description: - If true, include query string parameters in the cache key according to query_string_whitelist and query_string_blacklist. If neither is set, the entire query string will be included. - If false, the query string will be excluded from the cache key entirely. returned: success type: bool - query_string_blacklist: + queryStringBlacklist: description: - Names of query string parameters to exclude in cache keys. - All other parameters will be included. Either specify query_string_whitelist or @@ -438,7 +443,7 @@ RETURN = ''' - "'&' and '=' will be percent encoded and not treated as delimiters." returned: success type: list - query_string_whitelist: + queryStringWhitelist: description: - Names of query string parameters to include in cache keys. - All other parameters will be excluded. Either specify query_string_whitelist or @@ -446,19 +451,19 @@ RETURN = ''' - "'&' and '=' will be percent encoded and not treated as delimiters." returned: success type: list - connection_draining: + connectionDraining: description: - Settings for connection draining. returned: success type: complex contains: - draining_timeout_sec: + drainingTimeoutSec: description: - Time for which instance will be drained (not accept new connections, but still work to finish started). returned: success type: int - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -468,13 +473,13 @@ RETURN = ''' - An optional description of this resource. returned: success type: str - enable_cdn: + enableCDN: description: - If true, enable Cloud CDN for this BackendService. - When the load balancing scheme is INTERNAL, this field is not used. returned: success type: bool - health_checks: + healthChecks: description: - The list of URLs to the HttpHealthCheck or HttpsHealthCheck resource for health checking this BackendService. Currently at most one health check can be specified, @@ -498,22 +503,22 @@ RETURN = ''' - Enables IAP. returned: success type: bool - oauth2_client_id: + oauth2ClientId: description: - OAuth2 Client ID for IAP. returned: success type: str - oauth2_client_secret: + oauth2ClientSecret: description: - OAuth2 Client Secret for IAP. returned: success type: str - oauth2_client_secret_sha256: + oauth2ClientSecretSha256: description: - OAuth2 Client Secret SHA-256 for IAP. returned: success type: str - load_balancing_scheme: + loadBalancingScheme: description: - Indicates whether the backend service will be used with internal or external load balancing. A backend service created for one type of load balancing cannot be used @@ -530,7 +535,7 @@ RETURN = ''' be a dash. returned: success type: str - port_name: + portName: description: - Name of backend port. The same name should appear in the instance groups referenced by this service. Required when the load balancing scheme is EXTERNAL. @@ -551,7 +556,7 @@ RETURN = ''' - This field is not applicable to global backend services. returned: success type: str - session_affinity: + sessionAffinity: description: - Type of session affinity to use. The default is NONE. - When the load balancing scheme is EXTERNAL, can be NONE, CLIENT_IP, or GENERATED_COOKIE. @@ -560,7 +565,7 @@ RETURN = ''' - When the protocol is UDP, this field is not used. returned: success type: str - timeout_sec: + timeoutSec: description: - How many seconds to wait for the backend before considering it a failed request. Default is 30 seconds. Valid range is [1, 86400]. @@ -643,7 +648,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -703,9 +709,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -716,9 +722,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/backendServices".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. diff --git a/lib/ansible/modules/cloud/google/gcp_compute_backend_service_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_backend_service_facts.py index 614f4f2d7ae..c303c756866 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_backend_service_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_backend_service_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,7 +66,7 @@ items: returned: always type: complex contains: - affinity_cookie_ttl_sec: + affinityCookieTtlSec: description: - Lifetime of cookies in seconds if session_affinity is GENERATED_COOKIE. If set to 0, the cookie is non-persistent and lasts only until the end of the browser session @@ -80,7 +80,7 @@ items: returned: success type: complex contains: - balancing_mode: + balancingMode: description: - Specifies the balancing mode for this backend. - For global HTTP(S) or TCP/SSL load balancing, the default is UTILIZATION. Valid @@ -88,7 +88,7 @@ items: - This cannot be used for internal load balancing. returned: success type: str - capacity_scaler: + capacityScaler: description: - A multiplier applied to the group's maximum servicing capacity (based on UTILIZATION, RATE or CONNECTION). @@ -114,7 +114,7 @@ items: be in a zone within the same region as the BackendService. returned: success type: dict - max_connections: + maxConnections: description: - The max number of simultaneous connections for the group. Can be used with either CONNECTION or UTILIZATION balancing modes. @@ -123,7 +123,7 @@ items: - This cannot be used for internal load balancing. returned: success type: int - max_connections_per_instance: + maxConnectionsPerInstance: description: - The max number of simultaneous connections that a single backend instance can handle. This is used to calculate the capacity of the group. Can be used in either CONNECTION @@ -133,7 +133,7 @@ items: - This cannot be used for internal load balancing. returned: success type: int - max_rate: + maxRate: description: - The max requests per second (RPS) of the group. - Can be used with either RATE or UTILIZATION balancing modes, but required if RATE @@ -141,7 +141,7 @@ items: - This cannot be used for internal load balancing. returned: success type: int - max_rate_per_instance: + maxRatePerInstance: description: - The max requests per second (RPS) that a single backend instance can handle. This is used to calculate the capacity of the group. Can be used in either balancing @@ -149,43 +149,43 @@ items: - This cannot be used for internal load balancing. returned: success type: str - max_utilization: + maxUtilization: description: - Used when balancingMode is UTILIZATION. This ratio defines the CPU utilization target for the group. The default is 0.8. Valid range is [0.0, 1.0]. - This cannot be used for internal load balancing. returned: success type: str - cdn_policy: + cdnPolicy: description: - Cloud CDN configuration for this BackendService. returned: success type: complex contains: - cache_key_policy: + cacheKeyPolicy: description: - The CacheKeyPolicy for this CdnPolicy. returned: success type: complex contains: - include_host: + includeHost: description: - If true requests to different hosts will be cached separately. returned: success type: bool - include_protocol: + includeProtocol: description: - If true, http and https requests will be cached separately. returned: success type: bool - include_query_string: + includeQueryString: description: - If true, include query string parameters in the cache key according to query_string_whitelist and query_string_blacklist. If neither is set, the entire query string will be included. - If false, the query string will be excluded from the cache key entirely. returned: success type: bool - query_string_blacklist: + queryStringBlacklist: description: - Names of query string parameters to exclude in cache keys. - All other parameters will be included. Either specify query_string_whitelist or @@ -193,7 +193,7 @@ items: - "'&' and '=' will be percent encoded and not treated as delimiters." returned: success type: list - query_string_whitelist: + queryStringWhitelist: description: - Names of query string parameters to include in cache keys. - All other parameters will be excluded. Either specify query_string_whitelist or @@ -201,19 +201,19 @@ items: - "'&' and '=' will be percent encoded and not treated as delimiters." returned: success type: list - connection_draining: + connectionDraining: description: - Settings for connection draining. returned: success type: complex contains: - draining_timeout_sec: + drainingTimeoutSec: description: - Time for which instance will be drained (not accept new connections, but still work to finish started). returned: success type: int - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -223,13 +223,13 @@ items: - An optional description of this resource. returned: success type: str - enable_cdn: + enableCDN: description: - If true, enable Cloud CDN for this BackendService. - When the load balancing scheme is INTERNAL, this field is not used. returned: success type: bool - health_checks: + healthChecks: description: - The list of URLs to the HttpHealthCheck or HttpsHealthCheck resource for health checking this BackendService. Currently at most one health check can be specified, @@ -242,6 +242,39 @@ items: - The unique identifier for the resource. returned: success type: int + iap: + description: + - Settings for enabling Cloud Identity Aware Proxy. + returned: success + type: complex + contains: + enabled: + description: + - Enables IAP. + returned: success + type: bool + oauth2ClientId: + description: + - OAuth2 Client ID for IAP. + returned: success + type: str + oauth2ClientSecret: + description: + - OAuth2 Client Secret for IAP. + returned: success + type: str + oauth2ClientSecretSha256: + description: + - OAuth2 Client Secret SHA-256 for IAP. + returned: success + type: str + loadBalancingScheme: + description: + - Indicates whether the backend service will be used with internal or external load + balancing. A backend service created for one type of load balancing cannot be used + with the other. + returned: success + type: str name: description: - Name of the resource. Provided by the client when the resource is created. The name @@ -252,7 +285,7 @@ items: be a dash. returned: success type: str - port_name: + portName: description: - Name of backend port. The same name should appear in the instance groups referenced by this service. Required when the load balancing scheme is EXTERNAL. @@ -273,7 +306,7 @@ items: - This field is not applicable to global backend services. returned: success type: str - session_affinity: + sessionAffinity: description: - Type of session affinity to use. The default is NONE. - When the load balancing scheme is EXTERNAL, can be NONE, CLIENT_IP, or GENERATED_COOKIE. @@ -282,7 +315,7 @@ items: - When the protocol is UDP, this field is not used. returned: success type: str - timeout_sec: + timeoutSec: description: - How many seconds to wait for the backend before considering it a failed request. Default is 30 seconds. Valid range is [1, 86400]. @@ -304,7 +337,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_disk.py b/lib/ansible/modules/cloud/google/gcp_compute_disk.py index 8dfc0d1ed1b..ff840386d24 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_disk.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_disk.py @@ -155,6 +155,11 @@ options: full URL to the resource. For example, the following are valid values: * `U(https://www.googleapis.com/compute/v1/projects/project/global/snapshots/snapshot`) * `projects/project/global/snapshots/snapshot` * `global/snapshots/snapshot` .' + - 'This field represents a link to a Snapshot resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_snapshot + task and then set this source_snapshot field to "{{ name-of-resource }}" Alternatively, + you can set this source_snapshot to a dictionary with the selfLink key where the + value is the selfLink of your Snapshot.' required: false source_snapshot_encryption_key: description: @@ -187,13 +192,19 @@ EXAMPLES = ''' raw_key: SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0= zone: us-central1-a project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - creation_timestamp: + labelFingerprint: + description: + - The fingerprint used for optimistic locking of this resource. Used internally during + updates. + returned: success + type: str + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -209,12 +220,12 @@ RETURN = ''' - The unique identifier for the resource. returned: success type: int - last_attach_timestamp: + lastAttachTimestamp: description: - Last attach timestamp in RFC3339 text format. returned: success type: str - last_detach_timestamp: + lastDetachTimestamp: description: - Last dettach timestamp in RFC3339 text format. returned: success @@ -239,7 +250,7 @@ RETURN = ''' be a dash. returned: success type: str - size_gb: + sizeGb: description: - Size of the persistent disk, specified in GB. You can specify this field when creating a persistent disk using the sourceImage or sourceSnapshot parameter, or specify @@ -248,19 +259,19 @@ RETURN = ''' sizeGb must not be less than the size of the sourceImage or the size of the snapshot. returned: success type: int - type: - description: - - URL of the disk type resource describing which disk type to use to create the disk. - Provide this when creating the disk. - returned: success - type: str users: description: - 'Links to the users of the disk (attached instances) in form: project/zones/zone/instances/instance .' returned: success type: list - source_image: + type: + description: + - URL of the disk type resource describing which disk type to use to create the disk. + Provide this when creating the disk. + returned: success + type: str + sourceImage: description: - The source image used to create this disk. If the source image is deleted, this field will not be set. @@ -280,14 +291,14 @@ RETURN = ''' - A reference to the zone where the disk resides. returned: success type: str - source_image_encryption_key: + sourceImageEncryptionKey: description: - The customer-supplied encryption key of the source image. Required if the source image is protected by a customer-supplied encryption key. returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. @@ -299,7 +310,7 @@ RETURN = ''' that protects this resource. returned: success type: str - source_image_id: + sourceImageId: description: - The ID value of the image used to create this disk. This value identifies the exact image that was used to create this persistent disk. For example, if you created @@ -308,7 +319,7 @@ RETURN = ''' was used. returned: success type: str - disk_encryption_key: + diskEncryptionKey: description: - Encrypts the disk using a customer-supplied encryption key. - After you encrypt a disk with a customer-supplied key, you must provide the same @@ -321,7 +332,7 @@ RETURN = ''' returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. @@ -333,7 +344,7 @@ RETURN = ''' that protects this resource. returned: success type: str - source_snapshot: + sourceSnapshot: description: - 'The source snapshot used to create this disk. You can provide this as a partial or full URL to the resource. For example, the following are valid values: * @@ -341,14 +352,14 @@ RETURN = ''' * `projects/project/global/snapshots/snapshot` * `global/snapshots/snapshot` .' returned: success type: dict - source_snapshot_encryption_key: + sourceSnapshotEncryptionKey: description: - The customer-supplied encryption key of the source snapshot. Required if the source snapshot is protected by a customer-supplied encryption key. returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. @@ -360,7 +371,7 @@ RETURN = ''' that protects this resource. returned: success type: str - source_snapshot_id: + sourceSnapshotId: description: - The unique ID of the snapshot used to create this disk. This value identifies the exact snapshot that was used to create this persistent disk. For example, if you @@ -427,7 +438,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind, fetch) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -450,8 +462,44 @@ def create(module, link, kind): return wait_for_operation(module, auth.post(link, resource_to_request(module))) -def update(module, link, kind): - module.fail_json(msg="Disk cannot be edited") +def update(module, link, kind, fetch): + update_fields(module, resource_to_request(module), + response_to_hash(module, fetch)) + return fetch_resource(module, self_link(module), kind) + + +def update_fields(module, request, response): + if response.get('labels') != request.get('labels'): + label_fingerprint_update(module, request, response) + if response.get('sizeGb') != request.get('sizeGb'): + size_gb_update(module, request, response) + + +def label_fingerprint_update(module, request, response): + auth = GcpSession(module, 'compute') + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/zones/{zone}/disks/{name}/setLabels" + ]).format(**module.params), + { + u'labelFingerprint': response.get('labelFingerprint'), + u'labels': module.params.get('labels') + } + ) + + +def size_gb_update(module, request, response): + auth = GcpSession(module, 'compute') + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/zones/{zone}/disks/{name}/resize" + ]).format(**module.params), + { + u'sizeGb': module.params.get('size_gb') + } + ) def delete(module, link, kind): @@ -481,9 +529,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -494,9 +542,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/disks".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -511,8 +559,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result @@ -539,6 +585,7 @@ def is_different(module, response): # This is for doing comparisons with Ansible's current parameters. def response_to_hash(module, response): return { + u'labelFingerprint': response.get(u'labelFingerprint'), u'creationTimestamp': response.get(u'creationTimestamp'), u'description': response.get(u'description'), u'id': response.get(u'id'), @@ -548,8 +595,8 @@ def response_to_hash(module, response): u'licenses': response.get(u'licenses'), u'name': module.params.get('name'), u'sizeGb': response.get(u'sizeGb'), - u'type': response.get(u'type'), u'users': response.get(u'users'), + u'type': response.get(u'type'), u'sourceImage': module.params.get('source_image') } @@ -557,7 +604,7 @@ def response_to_hash(module, response): def disk_type_selflink(name, params): if name is None: return - url = r"https://www.googleapis.com/compute/v1/projects/.*/zones/{zone}/diskTypes/[a-z1-9\-]*" + url = r"https://www.googleapis.com/compute/v1/projects/.*/zones/[a-z1-9\-]*/diskTypes/[a-z1-9\-]*" if not re.match(url, name): name = "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/diskTypes/%s".format(**params) % name return name diff --git a/lib/ansible/modules/cloud/google/gcp_compute_disk_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_disk_facts.py index 13f01c8d88f..5c7795d013a 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_disk_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_disk_facts.py @@ -61,7 +61,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -71,7 +71,13 @@ items: returned: always type: complex contains: - creation_timestamp: + labelFingerprint: + description: + - The fingerprint used for optimistic locking of this resource. Used internally during + updates. + returned: success + type: str + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -87,12 +93,12 @@ items: - The unique identifier for the resource. returned: success type: int - last_attach_timestamp: + lastAttachTimestamp: description: - Last attach timestamp in RFC3339 text format. returned: success type: str - last_detach_timestamp: + lastDetachTimestamp: description: - Last dettach timestamp in RFC3339 text format. returned: success @@ -117,7 +123,7 @@ items: be a dash. returned: success type: str - size_gb: + sizeGb: description: - Size of the persistent disk, specified in GB. You can specify this field when creating a persistent disk using the sourceImage or sourceSnapshot parameter, or specify @@ -138,7 +144,7 @@ items: Provide this when creating the disk. returned: success type: str - source_image: + sourceImage: description: - The source image used to create this disk. If the source image is deleted, this field will not be set. @@ -158,14 +164,14 @@ items: - A reference to the zone where the disk resides. returned: success type: str - source_image_encryption_key: + sourceImageEncryptionKey: description: - The customer-supplied encryption key of the source image. Required if the source image is protected by a customer-supplied encryption key. returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. @@ -177,7 +183,7 @@ items: that protects this resource. returned: success type: str - source_image_id: + sourceImageId: description: - The ID value of the image used to create this disk. This value identifies the exact image that was used to create this persistent disk. For example, if you created @@ -186,7 +192,7 @@ items: was used. returned: success type: str - disk_encryption_key: + diskEncryptionKey: description: - Encrypts the disk using a customer-supplied encryption key. - After you encrypt a disk with a customer-supplied key, you must provide the same @@ -199,7 +205,7 @@ items: returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. @@ -211,7 +217,7 @@ items: that protects this resource. returned: success type: str - source_snapshot: + sourceSnapshot: description: - 'The source snapshot used to create this disk. You can provide this as a partial or full URL to the resource. For example, the following are valid values: * @@ -219,14 +225,14 @@ items: * `projects/project/global/snapshots/snapshot` * `global/snapshots/snapshot` .' returned: success type: dict - source_snapshot_encryption_key: + sourceSnapshotEncryptionKey: description: - The customer-supplied encryption key of the source snapshot. Required if the source snapshot is protected by a customer-supplied encryption key. returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. @@ -238,7 +244,7 @@ items: that protects this resource. returned: success type: str - source_snapshot_id: + sourceSnapshotId: description: - The unique ID of the snapshot used to create this disk. This value identifies the exact snapshot that was used to create this persistent disk. For example, if you diff --git a/lib/ansible/modules/cloud/google/gcp_compute_firewall.py b/lib/ansible/modules/cloud/google/gcp_compute_firewall.py index 747ea290ae7..09fcadb93fa 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_firewall.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_firewall.py @@ -72,11 +72,55 @@ options: specified, this rule applies to connections through any port. - 'Example inputs include: ["22"], ["80","443"], and ["12345-12349"].' required: false + denied: + description: + - The list of DENY rules specified by this firewall. Each rule specifies a protocol + and port-range tuple that describes a denied connection. + required: false + version_added: 2.8 + suboptions: + ip_protocol: + description: + - The IP protocol to which this rule applies. The protocol type is required when creating + a firewall rule. This value can either be one of the following well known protocol + strings (tcp, udp, icmp, esp, ah, sctp), or the IP protocol number. + required: true + ports: + description: + - An optional list of ports to which this rule applies. This field is only applicable + for UDP or TCP protocol. Each entry must be either an integer or a range. If not + specified, this rule applies to connections through any port. + - 'Example inputs include: ["22"], ["80","443"], and ["12345-12349"].' + required: false description: description: - An optional description of this resource. Provide this property when you create the resource. required: false + destination_ranges: + description: + - If destination ranges are specified, the firewall will apply only to traffic that + has destination IP address in these ranges. These ranges must be expressed in CIDR + format. Only IPv4 is supported. + required: false + version_added: 2.8 + direction: + description: + - 'Direction of traffic to which this firewall applies; default is INGRESS. Note: + For INGRESS traffic, it is NOT supported to specify destinationRanges; For EGRESS + traffic, it is NOT supported to specify sourceRanges OR sourceTags.' + required: false + version_added: 2.8 + choices: ['INGRESS', 'EGRESS'] + disabled: + description: + - Denotes whether the firewall rule is disabled, i.e not applied to the network it + is associated with. When set to true, the firewall rule is not enforced and the + network behaves as if it did not exist. If this is unspecified, the firewall rule + will be enabled. + required: false + type: bool + version_added: 2.8 name: description: - Name of the resource. Provided by the client when the resource is created. The name @@ -85,7 +129,7 @@ options: which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash. - required: false + required: true network: description: - 'URL of the network resource for this firewall rule. If not specified when creating @@ -95,7 +139,22 @@ options: U(https://www.googleapis.com/compute/v1/projects/myproject/global/) networks/my-network projects/myproject/global/networks/my-network global/networks/default .' + - 'This field represents a link to a Network resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_network task + and then set this network field to "{{ name-of-resource }}" Alternatively, you can + set this network to a dictionary with the selfLink key where the value is the selfLink + of your Network.' + required: true + priority: + description: + - Priority for this rule. This is an integer between 0 and 65535, both inclusive. + When not specified, the value assumed is 1000. Relative priorities determine precedence + of conflicting rules. Lower value of priority implies higher precedence (eg, a rule + with priority 0 has higher precedence than a rule with priority 1). DENY rules take + precedence over ALLOW rules having equal priority. required: false + default: 1000 + version_added: 2.8 source_ranges: description: - If source ranges are specified, the firewall will apply only to traffic that has @@ -105,6 +164,19 @@ options: OR the source IP that belongs to a tag listed in the sourceTags property. The connection does not need to match both properties for the firewall to apply. Only IPv4 is supported. required: false + source_service_accounts: + description: + - If source service accounts are specified, the firewall will apply only to traffic + originating from an instance with a service account in this list. Source service + accounts cannot be used to control traffic to an instance's external IP address + because service accounts are associated with an instance, not an IP address. sourceRanges + can be set at the same time as sourceServiceAccounts. If both are set, the firewall + will apply to traffic that has source IP address within sourceRanges OR the source + IP belongs to an instance with service account listed in sourceServiceAccount. The + connection does not need to match both properties for the firewall to apply. sourceServiceAccounts + cannot be used at the same time as sourceTags or targetTags. + required: false + version_added: 2.8 source_tags: description: - If source tags are specified, the firewall will apply only to traffic with source @@ -116,6 +188,15 @@ options: sourceTags property. The connection does not need to match both properties for the firewall to apply. required: false + target_service_accounts: + description: + - A list of service accounts indicating sets of instances located in the network that + may make network connections as specified in allowed[]. + - targetServiceAccounts cannot be used at the same time as targetTags or sourceTags. + If neither targetServiceAccounts nor targetTags are specified, the firewall rule + applies to all instances on the specified network. + required: false + version_added: 2.8 target_tags: description: - A list of instance tags indicating sets of instances located in the network that @@ -124,6 +205,9 @@ options: specified network. required: false extends_documentation_fragment: gcp +notes: + - "API Reference: U(https://cloud.google.com/compute/docs/reference/latest/firewalls)" + - "Official Documentation: U(https://cloud.google.com/vpc/docs/firewalls)" ''' EXAMPLES = ''' @@ -140,7 +224,7 @@ EXAMPLES = ''' source_tags: - test-ssh-clients project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -168,17 +252,61 @@ RETURN = ''' - 'Example inputs include: ["22"], ["80","443"], and ["12345-12349"].' returned: success type: list - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success type: str + denied: + description: + - The list of DENY rules specified by this firewall. Each rule specifies a protocol + and port-range tuple that describes a denied connection. + returned: success + type: complex + contains: + ip_protocol: + description: + - The IP protocol to which this rule applies. The protocol type is required when creating + a firewall rule. This value can either be one of the following well known protocol + strings (tcp, udp, icmp, esp, ah, sctp), or the IP protocol number. + returned: success + type: str + ports: + description: + - An optional list of ports to which this rule applies. This field is only applicable + for UDP or TCP protocol. Each entry must be either an integer or a range. If not + specified, this rule applies to connections through any port. + - 'Example inputs include: ["22"], ["80","443"], and ["12345-12349"].' + returned: success + type: list description: description: - An optional description of this resource. Provide this property when you create the resource. returned: success type: str + destinationRanges: + description: + - If destination ranges are specified, the firewall will apply only to traffic that + has destination IP address in these ranges. These ranges must be expressed in CIDR + format. Only IPv4 is supported. + returned: success + type: list + direction: + description: + - 'Direction of traffic to which this firewall applies; default is INGRESS. Note: + For INGRESS traffic, it is NOT supported to specify destinationRanges; For EGRESS + traffic, it is NOT supported to specify sourceRanges OR sourceTags.' + returned: success + type: str + disabled: + description: + - Denotes whether the firewall rule is disabled, i.e not applied to the network it + is associated with. When set to true, the firewall rule is not enforced and the + network behaves as if it did not exist. If this is unspecified, the firewall rule + will be enabled. + returned: success + type: bool id: description: - The unique identifier for the resource. @@ -204,8 +332,17 @@ RETURN = ''' networks/my-network projects/myproject/global/networks/my-network global/networks/default .' returned: success - type: str - source_ranges: + type: dict + priority: + description: + - Priority for this rule. This is an integer between 0 and 65535, both inclusive. + When not specified, the value assumed is 1000. Relative priorities determine precedence + of conflicting rules. Lower value of priority implies higher precedence (eg, a rule + with priority 0 has higher precedence than a rule with priority 1). DENY rules take + precedence over ALLOW rules having equal priority. + returned: success + type: int + sourceRanges: description: - If source ranges are specified, the firewall will apply only to traffic that has source IP address in these ranges. These ranges must be expressed in CIDR format. @@ -215,7 +352,20 @@ RETURN = ''' does not need to match both properties for the firewall to apply. Only IPv4 is supported. returned: success type: list - source_tags: + sourceServiceAccounts: + description: + - If source service accounts are specified, the firewall will apply only to traffic + originating from an instance with a service account in this list. Source service + accounts cannot be used to control traffic to an instance's external IP address + because service accounts are associated with an instance, not an IP address. sourceRanges + can be set at the same time as sourceServiceAccounts. If both are set, the firewall + will apply to traffic that has source IP address within sourceRanges OR the source + IP belongs to an instance with service account listed in sourceServiceAccount. The + connection does not need to match both properties for the firewall to apply. sourceServiceAccounts + cannot be used at the same time as sourceTags or targetTags. + returned: success + type: list + sourceTags: description: - If source tags are specified, the firewall will apply only to traffic with source IP that belongs to a tag listed in source tags. Source tags cannot be used to control @@ -227,7 +377,16 @@ RETURN = ''' firewall to apply. returned: success type: list - target_tags: + targetServiceAccounts: + description: + - A list of service accounts indicating sets of instances located in the network that + may make network connections as specified in allowed[]. + - targetServiceAccounts cannot be used at the same time as targetTags or sourceTags. + If neither targetServiceAccounts nor targetTags are specified, the firewall rule + applies to all instances on the specified network. + returned: success + type: list + targetTags: description: - A list of instance tags indicating sets of instances located in the network that may make network connections as specified in allowed[]. @@ -260,11 +419,21 @@ def main(): ip_protocol=dict(required=True, type='str'), ports=dict(type='list', elements='str') )), + denied=dict(type='list', elements='dict', options=dict( + ip_protocol=dict(required=True, type='str'), + ports=dict(type='list', elements='str') + )), description=dict(type='str'), - name=dict(type='str'), - network=dict(type='str'), + destination_ranges=dict(type='list', elements='str'), + direction=dict(type='str', choices=['INGRESS', 'EGRESS']), + disabled=dict(type='bool'), + name=dict(required=True, type='str'), + network=dict(required=True, type='dict'), + priority=dict(default=1000, type='int'), source_ranges=dict(type='list', elements='str'), + source_service_accounts=dict(type='list', elements='str'), source_tags=dict(type='list', elements='str'), + target_service_accounts=dict(type='list', elements='str'), target_tags=dict(type='list', elements='str') ) ) @@ -281,7 +450,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -306,7 +476,7 @@ def create(module, link, kind): def update(module, link, kind): auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + return wait_for_operation(module, auth.patch(link, resource_to_request(module))) def delete(module, link, kind): @@ -318,11 +488,18 @@ def resource_to_request(module): request = { u'kind': 'compute#firewall', u'allowed': FirewallAllowedArray(module.params.get('allowed', []), module).to_request(), + u'denied': FirewallDeniedArray(module.params.get('denied', []), module).to_request(), u'description': module.params.get('description'), + u'destinationRanges': module.params.get('destination_ranges'), + u'direction': module.params.get('direction'), + u'disabled': module.params.get('disabled'), u'name': module.params.get('name'), - u'network': module.params.get('network'), + u'network': replace_resource_dict(module.params.get(u'network', {}), 'selfLink'), + u'priority': module.params.get('priority'), u'sourceRanges': module.params.get('source_ranges'), + u'sourceServiceAccounts': module.params.get('source_service_accounts'), u'sourceTags': module.params.get('source_tags'), + u'targetServiceAccounts': module.params.get('target_service_accounts'), u'targetTags': module.params.get('target_tags') } return_vals = {} @@ -333,9 +510,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -346,9 +523,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/firewalls".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -363,8 +540,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result @@ -393,12 +568,19 @@ def response_to_hash(module, response): return { u'allowed': FirewallAllowedArray(response.get(u'allowed', []), module).from_response(), u'creationTimestamp': response.get(u'creationTimestamp'), + u'denied': FirewallDeniedArray(response.get(u'denied', []), module).from_response(), u'description': response.get(u'description'), + u'destinationRanges': response.get(u'destinationRanges'), + u'direction': response.get(u'direction'), + u'disabled': response.get(u'disabled'), u'id': response.get(u'id'), - u'name': response.get(u'name'), + u'name': module.params.get('name'), u'network': response.get(u'network'), + u'priority': response.get(u'priority'), u'sourceRanges': response.get(u'sourceRanges'), + u'sourceServiceAccounts': response.get(u'sourceServiceAccounts'), u'sourceTags': response.get(u'sourceTags'), + u'targetServiceAccounts': response.get(u'targetServiceAccounts'), u'targetTags': response.get(u'targetTags') } @@ -473,5 +655,38 @@ class FirewallAllowedArray(object): }) +class FirewallDeniedArray(object): + def __init__(self, request, module): + self.module = module + if request: + self.request = request + else: + self.request = [] + + def to_request(self): + items = [] + for item in self.request: + items.append(self._request_for_item(item)) + return items + + def from_response(self): + items = [] + for item in self.request: + items.append(self._response_from_item(item)) + return items + + def _request_for_item(self, item): + return remove_nones_from_dict({ + u'IPProtocol': item.get('ip_protocol'), + u'ports': item.get('ports') + }) + + def _response_from_item(self, item): + return remove_nones_from_dict({ + u'IPProtocol': item.get(u'ip_protocol'), + u'ports': item.get(u'ports') + }) + + if __name__ == '__main__': main() diff --git a/lib/ansible/modules/cloud/google/gcp_compute_firewall_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_firewall_facts.py index 1ad88378ff0..5fce2e9fbac 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_firewall_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_firewall_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -88,17 +88,61 @@ items: - 'Example inputs include: ["22"], ["80","443"], and ["12345-12349"].' returned: success type: list - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success type: str + denied: + description: + - The list of DENY rules specified by this firewall. Each rule specifies a protocol + and port-range tuple that describes a denied connection. + returned: success + type: complex + contains: + ip_protocol: + description: + - The IP protocol to which this rule applies. The protocol type is required when creating + a firewall rule. This value can either be one of the following well known protocol + strings (tcp, udp, icmp, esp, ah, sctp), or the IP protocol number. + returned: success + type: str + ports: + description: + - An optional list of ports to which this rule applies. This field is only applicable + for UDP or TCP protocol. Each entry must be either an integer or a range. If not + specified, this rule applies to connections through any port. + - 'Example inputs include: ["22"], ["80","443"], and ["12345-12349"].' + returned: success + type: list description: description: - An optional description of this resource. Provide this property when you create the resource. returned: success type: str + destinationRanges: + description: + - If destination ranges are specified, the firewall will apply only to traffic that + has destination IP address in these ranges. These ranges must be expressed in CIDR + format. Only IPv4 is supported. + returned: success + type: list + direction: + description: + - 'Direction of traffic to which this firewall applies; default is INGRESS. Note: + For INGRESS traffic, it is NOT supported to specify destinationRanges; For EGRESS + traffic, it is NOT supported to specify sourceRanges OR sourceTags.' + returned: success + type: str + disabled: + description: + - Denotes whether the firewall rule is disabled, i.e not applied to the network it + is associated with. When set to true, the firewall rule is not enforced and the + network behaves as if it did not exist. If this is unspecified, the firewall rule + will be enabled. + returned: success + type: bool id: description: - The unique identifier for the resource. @@ -124,8 +168,17 @@ items: networks/my-network projects/myproject/global/networks/my-network global/networks/default .' returned: success - type: str - source_ranges: + type: dict + priority: + description: + - Priority for this rule. This is an integer between 0 and 65535, both inclusive. + When not specified, the value assumed is 1000. Relative priorities determine precedence + of conflicting rules. Lower value of priority implies higher precedence (eg, a rule + with priority 0 has higher precedence than a rule with priority 1). DENY rules take + precedence over ALLOW rules having equal priority. + returned: success + type: int + sourceRanges: description: - If source ranges are specified, the firewall will apply only to traffic that has source IP address in these ranges. These ranges must be expressed in CIDR format. @@ -135,7 +188,20 @@ items: does not need to match both properties for the firewall to apply. Only IPv4 is supported. returned: success type: list - source_tags: + sourceServiceAccounts: + description: + - If source service accounts are specified, the firewall will apply only to traffic + originating from an instance with a service account in this list. Source service + accounts cannot be used to control traffic to an instance's external IP address + because service accounts are associated with an instance, not an IP address. sourceRanges + can be set at the same time as sourceServiceAccounts. If both are set, the firewall + will apply to traffic that has source IP address within sourceRanges OR the source + IP belongs to an instance with service account listed in sourceServiceAccount. The + connection does not need to match both properties for the firewall to apply. sourceServiceAccounts + cannot be used at the same time as sourceTags or targetTags. + returned: success + type: list + sourceTags: description: - If source tags are specified, the firewall will apply only to traffic with source IP that belongs to a tag listed in source tags. Source tags cannot be used to control @@ -147,7 +213,16 @@ items: firewall to apply. returned: success type: list - target_tags: + targetServiceAccounts: + description: + - A list of service accounts indicating sets of instances located in the network that + may make network connections as specified in allowed[]. + - targetServiceAccounts cannot be used at the same time as targetTags or sourceTags. + If neither targetServiceAccounts nor targetTags are specified, the firewall rule + applies to all instances on the specified network. + returned: success + type: list + targetTags: description: - A list of instance tags indicating sets of instances located in the network that may make network connections as specified in allowed[]. @@ -171,7 +246,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_forwarding_rule.py b/lib/ansible/modules/cloud/google/gcp_compute_forwarding_rule.py index 500482d5f3f..a712c5e709f 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_forwarding_rule.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_forwarding_rule.py @@ -85,6 +85,11 @@ options: - A reference to a BackendService to receive the matched traffic. - This is used for internal load balancing. - "(not used for external load balancing) ." + - 'This field represents a link to a BackendService resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_backend_service + task and then set this backend_service field to "{{ name-of-resource }}" Alternatively, + you can set this backend_service to a dictionary with the selfLink key where the + value is the selfLink of your BackendService.' required: false ip_version: description: @@ -116,6 +121,11 @@ options: IP should belong to for this Forwarding Rule. If this field is not specified, the default network will be used. - This field is not used for external load balancing. + - 'This field represents a link to a Network resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_network task + and then set this network field to "{{ name-of-resource }}" Alternatively, you can + set this network to a dictionary with the selfLink key where the value is the selfLink + of your Network.' required: false port_range: description: @@ -147,6 +157,11 @@ options: - If the network specified is in auto subnet mode, this field is optional. However, if the network is in custom subnet mode, a subnetwork must be specified. - This field is not used for external load balancing. + - 'This field represents a link to a Subnetwork resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_subnetwork + task and then set this subnetwork field to "{{ name-of-resource }}" Alternatively, + you can set this subnetwork to a dictionary with the selfLink key where the value + is the selfLink of your Subnetwork.' required: false target: description: @@ -155,8 +170,21 @@ options: rule. For global forwarding rules, this target must be a global load balancing resource. The forwarded traffic must be of a type appropriate to the target object. - This field is not used for internal load balancing. + - 'This field represents a link to a TargetPool resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_target_pool + task and then set this target field to "{{ name-of-resource }}" Alternatively, you + can set this target to a dictionary with the selfLink key where the value is the + selfLink of your TargetPool.' required: false version_added: 2.7 + network_tier: + description: + - 'The networking tier used for configuring this address. This field can take the + following values: PREMIUM or STANDARD. If this field is not specified, it is assumed + to be PREMIUM.' + required: false + version_added: 2.8 + choices: ['PREMIUM', 'STANDARD'] region: description: - A reference to the region where the regional forwarding rule resides. @@ -198,13 +226,13 @@ EXAMPLES = ''' port_range: 80-80 ip_address: "{{ address.address }}" project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -220,7 +248,7 @@ RETURN = ''' - The unique identifier for the resource. returned: success type: int - ip_address: + IPAddress: description: - The IP address that this forwarding rule is serving on behalf of. - Addresses are restricted based on the forwarding rule's load balancing scheme (EXTERNAL @@ -241,27 +269,27 @@ RETURN = ''' * global/addresses/address * address .' returned: success type: str - ip_protocol: + IPProtocol: description: - The IP protocol to which this rule applies. Valid options are TCP, UDP, ESP, AH, SCTP or ICMP. - When the load balancing scheme is INTERNAL, only TCP and UDP are valid. returned: success type: str - backend_service: + backendService: description: - A reference to a BackendService to receive the matched traffic. - This is used for internal load balancing. - "(not used for external load balancing) ." returned: success type: dict - ip_version: + ipVersion: description: - The IP Version that will be used by this forwarding rule. Valid options are IPV4 or IPV6. This can only be specified for a global forwarding rule. returned: success type: str - load_balancing_scheme: + loadBalancingScheme: description: - 'This signifies what the ForwardingRule will be used for and can only take the following values: INTERNAL, EXTERNAL The value of INTERNAL means that this will be used for @@ -288,7 +316,7 @@ RETURN = ''' - This field is not used for external load balancing. returned: success type: dict - port_range: + portRange: description: - This field is used along with the target field for TargetHttpProxy, TargetHttpsProxy, TargetSslProxy, TargetTcpProxy, TargetVpnGateway, TargetPool, TargetInstance. @@ -331,6 +359,19 @@ RETURN = ''' - This field is not used for internal load balancing. returned: success type: dict + labelFingerprint: + description: + - The fingerprint used for optimistic locking of this resource. Used internally during + updates. + returned: success + type: str + networkTier: + description: + - 'The networking tier used for configuring this address. This field can take the + following values: PREMIUM or STANDARD. If this field is not specified, it is assumed + to be PREMIUM.' + returned: success + type: str region: description: - A reference to the region where the regional forwarding rule resides. @@ -370,6 +411,7 @@ def main(): ports=dict(type='list', elements='str'), subnetwork=dict(type='dict'), target=dict(type='dict'), + network_tier=dict(type='str', choices=['PREMIUM', 'STANDARD']), region=dict(required=True, type='str') ) ) @@ -386,7 +428,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind, fetch) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -409,9 +452,41 @@ def create(module, link, kind): return wait_for_operation(module, auth.post(link, resource_to_request(module))) -def update(module, link, kind): +def update(module, link, kind, fetch): + update_fields(module, resource_to_request(module), + response_to_hash(module, fetch)) + return fetch_resource(module, self_link(module), kind) + + +def update_fields(module, request, response): + if response.get('target') != request.get('target'): + target_update(module, request, response) + + +def target_update(module, request, response): auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/regions/{region}/forwardingRules/{name}/setTarget" + ]).format(**module.params), + { + u'target': replace_resource_dict(module.params.get(u'target', {}), 'selfLink') + } + ) + + +def label_fingerprint_update(module, request, response): + auth = GcpSession(module, 'compute') + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/regions/{region}/forwardingRules/{name}/setLabels" + ]).format(**module.params), + { + u'labelFingerprint': response.get('labelFingerprint') + } + ) def delete(module, link, kind): @@ -433,7 +508,8 @@ def resource_to_request(module): u'portRange': module.params.get('port_range'), u'ports': module.params.get('ports'), u'subnetwork': replace_resource_dict(module.params.get(u'subnetwork', {}), 'selfLink'), - u'target': replace_resource_dict(module.params.get(u'target', {}), 'selfLink') + u'target': replace_resource_dict(module.params.get(u'target', {}), 'selfLink'), + u'networkTier': module.params.get('network_tier') } return_vals = {} for k, v in request.items(): @@ -443,9 +519,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -456,9 +532,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/regions/{region}/forwardingRules".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -473,8 +549,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result @@ -514,7 +588,9 @@ def response_to_hash(module, response): u'portRange': response.get(u'portRange'), u'ports': response.get(u'ports'), u'subnetwork': response.get(u'subnetwork'), - u'target': response.get(u'target') + u'target': response.get(u'target'), + u'labelFingerprint': response.get(u'labelFingerprint'), + u'networkTier': module.params.get('network_tier') } diff --git a/lib/ansible/modules/cloud/google/gcp_compute_forwarding_rule_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_forwarding_rule_facts.py index 2be21b9ec54..a56710298af 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_forwarding_rule_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_forwarding_rule_facts.py @@ -62,7 +62,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -72,7 +72,7 @@ items: returned: always type: complex contains: - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -88,7 +88,7 @@ items: - The unique identifier for the resource. returned: success type: int - ip_address: + IPAddress: description: - The IP address that this forwarding rule is serving on behalf of. - Addresses are restricted based on the forwarding rule's load balancing scheme (EXTERNAL @@ -109,27 +109,27 @@ items: * global/addresses/address * address .' returned: success type: str - ip_protocol: + IPProtocol: description: - The IP protocol to which this rule applies. Valid options are TCP, UDP, ESP, AH, SCTP or ICMP. - When the load balancing scheme is INTERNAL, only TCP and UDP are valid. returned: success type: str - backend_service: + backendService: description: - A reference to a BackendService to receive the matched traffic. - This is used for internal load balancing. - "(not used for external load balancing) ." returned: success type: dict - ip_version: + ipVersion: description: - The IP Version that will be used by this forwarding rule. Valid options are IPV4 or IPV6. This can only be specified for a global forwarding rule. returned: success type: str - load_balancing_scheme: + loadBalancingScheme: description: - 'This signifies what the ForwardingRule will be used for and can only take the following values: INTERNAL, EXTERNAL The value of INTERNAL means that this will be used for @@ -156,7 +156,7 @@ items: - This field is not used for external load balancing. returned: success type: dict - port_range: + portRange: description: - This field is used along with the target field for TargetHttpProxy, TargetHttpsProxy, TargetSslProxy, TargetTcpProxy, TargetVpnGateway, TargetPool, TargetInstance. @@ -199,6 +199,19 @@ items: - This field is not used for internal load balancing. returned: success type: dict + labelFingerprint: + description: + - The fingerprint used for optimistic locking of this resource. Used internally during + updates. + returned: success + type: str + networkTier: + description: + - 'The networking tier used for configuring this address. This field can take the + following values: PREMIUM or STANDARD. If this field is not specified, it is assumed + to be PREMIUM.' + returned: success + type: str region: description: - A reference to the region where the regional forwarding rule resides. diff --git a/lib/ansible/modules/cloud/google/gcp_compute_global_address.py b/lib/ansible/modules/cloud/google/gcp_compute_global_address.py index 3402fd96e18..f8cc7eaea96 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_global_address.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_global_address.py @@ -67,6 +67,15 @@ options: The default value is IPV4. required: false choices: ['IPV4', 'IPV6'] + address_type: + description: + - The type of the address to reserve, default is EXTERNAL. + - "* EXTERNAL indicates public/external single IP address." + - "* INTERNAL indicates internal IP ranges belonging to some network." + required: false + default: EXTERNAL + version_added: 2.8 + choices: ['EXTERNAL', 'INTERNAL'] extends_documentation_fragment: gcp notes: - "API Reference: U(https://cloud.google.com/compute/docs/reference/latest/globalAddresses)" @@ -78,7 +87,7 @@ EXAMPLES = ''' gcp_compute_global_address: name: "test_object" project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -89,7 +98,7 @@ RETURN = ''' - The static external IP address represented by this resource. returned: success type: str - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -115,7 +124,13 @@ RETURN = ''' be a dash. returned: success type: str - ip_version: + labelFingerprint: + description: + - The fingerprint used for optimistic locking of this resource. Used internally during + updates. + returned: success + type: str + ipVersion: description: - The IP Version that will be used by this address. Valid options are IPV4 or IPV6. The default value is IPV4. @@ -126,6 +141,13 @@ RETURN = ''' - A reference to the region where the regional address resides. returned: success type: str + addressType: + description: + - The type of the address to reserve, default is EXTERNAL. + - "* EXTERNAL indicates public/external single IP address." + - "* INTERNAL indicates internal IP ranges belonging to some network." + returned: success + type: str ''' ################################################################################ @@ -150,7 +172,8 @@ def main(): state=dict(default='present', choices=['present', 'absent'], type='str'), description=dict(type='str'), name=dict(required=True, type='str'), - ip_version=dict(type='str', choices=['IPV4', 'IPV6']) + ip_version=dict(type='str', choices=['IPV4', 'IPV6']), + address_type=dict(default='EXTERNAL', type='str', choices=['EXTERNAL', 'INTERNAL']) ) ) @@ -166,7 +189,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind, fetch) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -189,9 +213,27 @@ def create(module, link, kind): return wait_for_operation(module, auth.post(link, resource_to_request(module))) -def update(module, link, kind): +def update(module, link, kind, fetch): + update_fields(module, resource_to_request(module), + response_to_hash(module, fetch)) + return fetch_resource(module, self_link(module), kind) + + +def update_fields(module, request, response): + pass + + +def label_fingerprint_update(module, request, response): auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/global/addresses/{name}/setLabels" + ]).format(**module.params), + { + u'labelFingerprint': response.get('labelFingerprint') + } + ) def delete(module, link, kind): @@ -204,7 +246,8 @@ def resource_to_request(module): u'kind': 'compute#address', u'description': module.params.get('description'), u'name': module.params.get('name'), - u'ipVersion': module.params.get('ip_version') + u'ipVersion': module.params.get('ip_version'), + u'addressType': module.params.get('address_type') } return_vals = {} for k, v in request.items(): @@ -214,9 +257,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -227,9 +270,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/addresses".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -244,8 +287,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result @@ -277,8 +318,10 @@ def response_to_hash(module, response): u'description': response.get(u'description'), u'id': response.get(u'id'), u'name': response.get(u'name'), + u'labelFingerprint': response.get(u'labelFingerprint'), u'ipVersion': response.get(u'ipVersion'), - u'region': response.get(u'region') + u'region': response.get(u'region'), + u'addressType': response.get(u'addressType') } diff --git a/lib/ansible/modules/cloud/google/gcp_compute_global_address_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_global_address_facts.py index 69cdab4f813..cd91bc0a272 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_global_address_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_global_address_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -71,7 +71,7 @@ items: - The static external IP address represented by this resource. returned: success type: str - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -97,7 +97,13 @@ items: be a dash. returned: success type: str - ip_version: + labelFingerprint: + description: + - The fingerprint used for optimistic locking of this resource. Used internally during + updates. + returned: success + type: str + ipVersion: description: - The IP Version that will be used by this address. Valid options are IPV4 or IPV6. The default value is IPV4. @@ -108,6 +114,13 @@ items: - A reference to the region where the regional address resides. returned: success type: str + addressType: + description: + - The type of the address to reserve, default is EXTERNAL. + - "* EXTERNAL indicates public/external single IP address." + - "* INTERNAL indicates internal IP ranges belonging to some network." + returned: success + type: str ''' ################################################################################ @@ -124,7 +137,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_global_forwarding_rule.py b/lib/ansible/modules/cloud/google/gcp_compute_global_forwarding_rule.py index 27b65d5c62f..09e563396cc 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_global_forwarding_rule.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_global_forwarding_rule.py @@ -87,6 +87,11 @@ options: - A reference to a BackendService to receive the matched traffic. - This is used for internal load balancing. - "(not used for external load balancing) ." + - 'This field represents a link to a BackendService resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_backend_service + task and then set this backend_service field to "{{ name-of-resource }}" Alternatively, + you can set this backend_service to a dictionary with the selfLink key where the + value is the selfLink of your BackendService.' required: false ip_version: description: @@ -118,6 +123,11 @@ options: IP should belong to for this Forwarding Rule. If this field is not specified, the default network will be used. - This field is not used for external load balancing. + - 'This field represents a link to a Network resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_network task + and then set this network field to "{{ name-of-resource }}" Alternatively, you can + set this network to a dictionary with the selfLink key where the value is the selfLink + of your Network.' required: false port_range: description: @@ -149,6 +159,11 @@ options: - If the network specified is in auto subnet mode, this field is optional. However, if the network is in custom subnet mode, a subnetwork must be specified. - This field is not used for external load balancing. + - 'This field represents a link to a Subnetwork resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_subnetwork + task and then set this subnetwork field to "{{ name-of-resource }}" Alternatively, + you can set this subnetwork to a dictionary with the selfLink key where the value + is the selfLink of your Subnetwork.' required: false target: description: @@ -234,13 +249,13 @@ EXAMPLES = ''' port_range: 80-80 target: "{{ httpproxy.selfLink }}" project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -256,7 +271,7 @@ RETURN = ''' - The unique identifier for the resource. returned: success type: int - ip_address: + IPAddress: description: - The IP address that this forwarding rule is serving on behalf of. - Addresses are restricted based on the forwarding rule's load balancing scheme (EXTERNAL @@ -277,27 +292,27 @@ RETURN = ''' * global/addresses/address * address .' returned: success type: str - ip_protocol: + IPProtocol: description: - The IP protocol to which this rule applies. Valid options are TCP, UDP, ESP, AH, SCTP or ICMP. - When the load balancing scheme is INTERNAL, only TCP and UDP are valid. returned: success type: str - backend_service: + backendService: description: - A reference to a BackendService to receive the matched traffic. - This is used for internal load balancing. - "(not used for external load balancing) ." returned: success type: dict - ip_version: + ipVersion: description: - The IP Version that will be used by this forwarding rule. Valid options are IPV4 or IPV6. This can only be specified for a global forwarding rule. returned: success type: str - load_balancing_scheme: + loadBalancingScheme: description: - 'This signifies what the ForwardingRule will be used for and can only take the following values: INTERNAL, EXTERNAL The value of INTERNAL means that this will be used for @@ -324,7 +339,7 @@ RETURN = ''' - This field is not used for external load balancing. returned: success type: dict - port_range: + portRange: description: - This field is used along with the target field for TargetHttpProxy, TargetHttpsProxy, TargetSslProxy, TargetTcpProxy, TargetVpnGateway, TargetPool, TargetInstance. @@ -419,7 +434,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -476,9 +492,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -489,9 +505,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/forwardingRules".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -506,8 +522,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_global_forwarding_rule_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_global_forwarding_rule_facts.py index e8693d9375a..3c25d67d007 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_global_forwarding_rule_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_global_forwarding_rule_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,7 +66,7 @@ items: returned: always type: complex contains: - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -82,7 +82,7 @@ items: - The unique identifier for the resource. returned: success type: int - ip_address: + IPAddress: description: - The IP address that this forwarding rule is serving on behalf of. - Addresses are restricted based on the forwarding rule's load balancing scheme (EXTERNAL @@ -103,27 +103,27 @@ items: * global/addresses/address * address .' returned: success type: str - ip_protocol: + IPProtocol: description: - The IP protocol to which this rule applies. Valid options are TCP, UDP, ESP, AH, SCTP or ICMP. - When the load balancing scheme is INTERNAL, only TCP and UDP are valid. returned: success type: str - backend_service: + backendService: description: - A reference to a BackendService to receive the matched traffic. - This is used for internal load balancing. - "(not used for external load balancing) ." returned: success type: dict - ip_version: + ipVersion: description: - The IP Version that will be used by this forwarding rule. Valid options are IPV4 or IPV6. This can only be specified for a global forwarding rule. returned: success type: str - load_balancing_scheme: + loadBalancingScheme: description: - 'This signifies what the ForwardingRule will be used for and can only take the following values: INTERNAL, EXTERNAL The value of INTERNAL means that this will be used for @@ -150,7 +150,7 @@ items: - This field is not used for external load balancing. returned: success type: dict - port_range: + portRange: description: - This field is used along with the target field for TargetHttpProxy, TargetHttpsProxy, TargetSslProxy, TargetTcpProxy, TargetVpnGateway, TargetPool, TargetInstance. @@ -213,7 +213,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_health_check.py b/lib/ansible/modules/cloud/google/gcp_compute_health_check.py index a07c5d1030b..a7c2f4a330d 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_health_check.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_health_check.py @@ -32,8 +32,15 @@ DOCUMENTATION = ''' --- module: gcp_compute_health_check description: - - An HealthCheck resource. This resource defines a template for how individual virtual - machines should be checked for health, via one of the supported protocols. + - Health Checks determine whether instances are responsive and able to do work. + - They are an important part of a comprehensive load balancing configuration, as they + enable monitoring instances behind load balancers. + - Health Checks poll instances at a specified interval. Instances that do not respond + successfully to some number of probes in a row are marked as unhealthy. No new connections + are sent to unhealthy instances, though existing connections will continue. The + health check will continue to poll unhealthy instances. If an instance later responds + successfully to some number of consecutive probes, it is marked healthy again and + can receive new connections. short_description: Creates a GCP HealthCheck version_added: 2.6 author: Google Inc. (@googlecloudplatform) @@ -62,6 +69,7 @@ options: - A so-far unhealthy instance will be marked healthy after this many consecutive successes. The default value is 2. required: false + default: 2 name: description: - Name of the resource. Provided by the client when the resource is created. The name @@ -70,7 +78,7 @@ options: which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash. - required: false + required: true timeout_sec: description: - How long (in seconds) to wait before claiming failure. @@ -91,7 +99,7 @@ options: the default is TCP. Exactly one of the protocol-specific health check field must be specified, which must match type field. required: false - choices: ['TCP', 'SSL', 'HTTP'] + choices: ['TCP', 'SSL', 'HTTP', 'HTTPS'] http_health_check: description: - A nested object resource. @@ -108,6 +116,7 @@ options: - The request path of the HTTP health check request. - The default value is /. required: false + default: / port: description: - The TCP port number for the HTTP health check request. @@ -123,6 +132,7 @@ options: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. required: false + default: NONE choices: ['NONE', 'PROXY_V1'] https_health_check: description: @@ -140,6 +150,7 @@ options: - The request path of the HTTPS health check request. - The default value is /. required: false + default: / port: description: - The TCP port number for the HTTPS health check request. @@ -155,6 +166,7 @@ options: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. required: false + default: NONE choices: ['NONE', 'PROXY_V1'] tcp_health_check: description: @@ -188,6 +200,7 @@ options: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. required: false + default: NONE choices: ['NONE', 'PROXY_V1'] ssl_health_check: description: @@ -221,8 +234,12 @@ options: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. required: false + default: NONE choices: ['NONE', 'PROXY_V1'] extends_documentation_fragment: gcp +notes: + - "API Reference: U(https://cloud.google.com/compute/docs/reference/rest/latest/healthChecks)" + - "Official Documentation: U(https://cloud.google.com/load-balancing/docs/health-checks)" ''' EXAMPLES = ''' @@ -238,18 +255,18 @@ EXAMPLES = ''' timeout_sec: 2 unhealthy_threshold: 5 project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - check_interval_sec: + checkIntervalSec: description: - How often (in seconds) to send a health check. The default value is 5 seconds. returned: success type: int - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -260,7 +277,7 @@ RETURN = ''' the resource. returned: success type: str - healthy_threshold: + healthyThreshold: description: - A so-far unhealthy instance will be marked healthy after this many consecutive successes. The default value is 2. @@ -281,14 +298,14 @@ RETURN = ''' be a dash. returned: success type: str - timeout_sec: + timeoutSec: description: - How long (in seconds) to wait before claiming failure. - The default value is 5 seconds. It is invalid for timeoutSec to have greater value than checkIntervalSec. returned: success type: int - unhealthy_threshold: + unhealthyThreshold: description: - A so-far healthy instance will be marked unhealthy after this many consecutive failures. The default value is 2. @@ -301,7 +318,7 @@ RETURN = ''' be specified, which must match type field. returned: success type: str - http_health_check: + httpHealthCheck: description: - A nested object resource. returned: success @@ -314,7 +331,7 @@ RETURN = ''' is performed will be used. returned: success type: str - request_path: + requestPath: description: - The request path of the HTTP health check request. - The default value is /. @@ -326,19 +343,19 @@ RETURN = ''' - The default value is 80. returned: success type: int - port_name: + portName: description: - Port name as defined in InstanceGroup#NamedPort#name. If both port and port_name are defined, port takes precedence. returned: success type: str - proxy_header: + proxyHeader: description: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. returned: success type: str - https_health_check: + httpsHealthCheck: description: - A nested object resource. returned: success @@ -351,7 +368,7 @@ RETURN = ''' is performed will be used. returned: success type: str - request_path: + requestPath: description: - The request path of the HTTPS health check request. - The default value is /. @@ -363,19 +380,19 @@ RETURN = ''' - The default value is 443. returned: success type: int - port_name: + portName: description: - Port name as defined in InstanceGroup#NamedPort#name. If both port and port_name are defined, port takes precedence. returned: success type: str - proxy_header: + proxyHeader: description: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. returned: success type: str - tcp_health_check: + tcpHealthCheck: description: - A nested object resource. returned: success @@ -401,19 +418,19 @@ RETURN = ''' - The default value is 443. returned: success type: int - port_name: + portName: description: - Port name as defined in InstanceGroup#NamedPort#name. If both port and port_name are defined, port takes precedence. returned: success type: str - proxy_header: + proxyHeader: description: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. returned: success type: str - ssl_health_check: + sslHealthCheck: description: - A nested object resource. returned: success @@ -439,13 +456,13 @@ RETURN = ''' - The default value is 443. returned: success type: int - port_name: + portName: description: - Port name as defined in InstanceGroup#NamedPort#name. If both port and port_name are defined, port takes precedence. returned: success type: str - proxy_header: + proxyHeader: description: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. @@ -474,38 +491,38 @@ def main(): state=dict(default='present', choices=['present', 'absent'], type='str'), check_interval_sec=dict(default=5, type='int'), description=dict(type='str'), - healthy_threshold=dict(type='int'), - name=dict(type='str'), + healthy_threshold=dict(default=2, type='int'), + name=dict(required=True, type='str'), timeout_sec=dict(default=5, type='int', aliases=['timeout_seconds']), unhealthy_threshold=dict(default=2, type='int'), - type=dict(type='str', choices=['TCP', 'SSL', 'HTTP']), + type=dict(type='str', choices=['TCP', 'SSL', 'HTTP', 'HTTPS']), http_health_check=dict(type='dict', options=dict( host=dict(type='str'), - request_path=dict(type='str'), + request_path=dict(default='/', type='str'), port=dict(type='int'), port_name=dict(type='str'), - proxy_header=dict(type='str', choices=['NONE', 'PROXY_V1']) + proxy_header=dict(default='NONE', type='str', choices=['NONE', 'PROXY_V1']) )), https_health_check=dict(type='dict', options=dict( host=dict(type='str'), - request_path=dict(type='str'), + request_path=dict(default='/', type='str'), port=dict(type='int'), port_name=dict(type='str'), - proxy_header=dict(type='str', choices=['NONE', 'PROXY_V1']) + proxy_header=dict(default='NONE', type='str', choices=['NONE', 'PROXY_V1']) )), tcp_health_check=dict(type='dict', options=dict( request=dict(type='str'), response=dict(type='str'), port=dict(type='int'), port_name=dict(type='str'), - proxy_header=dict(type='str', choices=['NONE', 'PROXY_V1']) + proxy_header=dict(default='NONE', type='str', choices=['NONE', 'PROXY_V1']) )), ssl_health_check=dict(type='dict', options=dict( request=dict(type='str'), response=dict(type='str'), port=dict(type='int'), port_name=dict(type='str'), - proxy_header=dict(type='str', choices=['NONE', 'PROXY_V1']) + proxy_header=dict(default='NONE', type='str', choices=['NONE', 'PROXY_V1']) )) ) ) @@ -522,7 +539,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -578,9 +596,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -591,9 +609,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/healthChecks".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -608,8 +626,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result @@ -641,7 +657,7 @@ def response_to_hash(module, response): u'description': response.get(u'description'), u'healthyThreshold': response.get(u'healthyThreshold'), u'id': response.get(u'id'), - u'name': response.get(u'name'), + u'name': module.params.get('name'), u'timeoutSec': response.get(u'timeoutSec'), u'unhealthyThreshold': response.get(u'unhealthyThreshold'), u'type': response.get(u'type'), diff --git a/lib/ansible/modules/cloud/google/gcp_compute_health_check_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_health_check_facts.py index e3d4480a064..4fb8fc5e436 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_health_check_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_health_check_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,12 +66,12 @@ items: returned: always type: complex contains: - check_interval_sec: + checkIntervalSec: description: - How often (in seconds) to send a health check. The default value is 5 seconds. returned: success type: int - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -82,7 +82,7 @@ items: the resource. returned: success type: str - healthy_threshold: + healthyThreshold: description: - A so-far unhealthy instance will be marked healthy after this many consecutive successes. The default value is 2. @@ -103,14 +103,14 @@ items: be a dash. returned: success type: str - timeout_sec: + timeoutSec: description: - How long (in seconds) to wait before claiming failure. - The default value is 5 seconds. It is invalid for timeoutSec to have greater value than checkIntervalSec. returned: success type: int - unhealthy_threshold: + unhealthyThreshold: description: - A so-far healthy instance will be marked unhealthy after this many consecutive failures. The default value is 2. @@ -123,7 +123,7 @@ items: be specified, which must match type field. returned: success type: str - http_health_check: + httpHealthCheck: description: - A nested object resource. returned: success @@ -136,7 +136,7 @@ items: is performed will be used. returned: success type: str - request_path: + requestPath: description: - The request path of the HTTP health check request. - The default value is /. @@ -148,19 +148,19 @@ items: - The default value is 80. returned: success type: int - port_name: + portName: description: - Port name as defined in InstanceGroup#NamedPort#name. If both port and port_name are defined, port takes precedence. returned: success type: str - proxy_header: + proxyHeader: description: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. returned: success type: str - https_health_check: + httpsHealthCheck: description: - A nested object resource. returned: success @@ -173,7 +173,7 @@ items: is performed will be used. returned: success type: str - request_path: + requestPath: description: - The request path of the HTTPS health check request. - The default value is /. @@ -185,19 +185,19 @@ items: - The default value is 443. returned: success type: int - port_name: + portName: description: - Port name as defined in InstanceGroup#NamedPort#name. If both port and port_name are defined, port takes precedence. returned: success type: str - proxy_header: + proxyHeader: description: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. returned: success type: str - tcp_health_check: + tcpHealthCheck: description: - A nested object resource. returned: success @@ -223,19 +223,19 @@ items: - The default value is 443. returned: success type: int - port_name: + portName: description: - Port name as defined in InstanceGroup#NamedPort#name. If both port and port_name are defined, port takes precedence. returned: success type: str - proxy_header: + proxyHeader: description: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. returned: success type: str - ssl_health_check: + sslHealthCheck: description: - A nested object resource. returned: success @@ -261,13 +261,13 @@ items: - The default value is 443. returned: success type: int - port_name: + portName: description: - Port name as defined in InstanceGroup#NamedPort#name. If both port and port_name are defined, port takes precedence. returned: success type: str - proxy_header: + proxyHeader: description: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. @@ -289,7 +289,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_http_health_check.py b/lib/ansible/modules/cloud/google/gcp_compute_http_health_check.py index d22962a468d..df8c7f67580 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_http_health_check.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_http_health_check.py @@ -115,18 +115,18 @@ EXAMPLES = ''' timeout_sec: 2 unhealthy_threshold: 5 project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - check_interval_sec: + checkIntervalSec: description: - How often (in seconds) to send a health check. The default value is 5 seconds. returned: success type: int - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -137,7 +137,7 @@ RETURN = ''' the resource. returned: success type: str - healthy_threshold: + healthyThreshold: description: - A so-far unhealthy instance will be marked healthy after this many consecutive successes. The default value is 2. @@ -171,20 +171,20 @@ RETURN = ''' - The default value is 80. returned: success type: int - request_path: + requestPath: description: - The request path of the HTTP health check request. - The default value is /. returned: success type: str - timeout_sec: + timeoutSec: description: - How long (in seconds) to wait before claiming failure. - The default value is 5 seconds. It is invalid for timeoutSec to have greater value than checkIntervalSec. returned: success type: int - unhealthy_threshold: + unhealthyThreshold: description: - A so-far healthy instance will be marked unhealthy after this many consecutive failures. The default value is 2. @@ -235,7 +235,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -289,9 +290,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -302,9 +303,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/httpHealthChecks".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -319,8 +320,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_http_health_check_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_http_health_check_facts.py index e4f9096920c..6fc063d0230 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_http_health_check_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_http_health_check_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,12 +66,12 @@ items: returned: always type: complex contains: - check_interval_sec: + checkIntervalSec: description: - How often (in seconds) to send a health check. The default value is 5 seconds. returned: success type: int - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -82,7 +82,7 @@ items: the resource. returned: success type: str - healthy_threshold: + healthyThreshold: description: - A so-far unhealthy instance will be marked healthy after this many consecutive successes. The default value is 2. @@ -116,20 +116,20 @@ items: - The default value is 80. returned: success type: int - request_path: + requestPath: description: - The request path of the HTTP health check request. - The default value is /. returned: success type: str - timeout_sec: + timeoutSec: description: - How long (in seconds) to wait before claiming failure. - The default value is 5 seconds. It is invalid for timeoutSec to have greater value than checkIntervalSec. returned: success type: int - unhealthy_threshold: + unhealthyThreshold: description: - A so-far healthy instance will be marked unhealthy after this many consecutive failures. The default value is 2. @@ -151,7 +151,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_https_health_check.py b/lib/ansible/modules/cloud/google/gcp_compute_https_health_check.py index 2cf4962cce0..96e0101babd 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_https_health_check.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_https_health_check.py @@ -113,18 +113,18 @@ EXAMPLES = ''' timeout_sec: 2 unhealthy_threshold: 5 project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - check_interval_sec: + checkIntervalSec: description: - How often (in seconds) to send a health check. The default value is 5 seconds. returned: success type: int - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -135,7 +135,7 @@ RETURN = ''' the resource. returned: success type: str - healthy_threshold: + healthyThreshold: description: - A so-far unhealthy instance will be marked healthy after this many consecutive successes. The default value is 2. @@ -169,20 +169,20 @@ RETURN = ''' - The default value is 80. returned: success type: int - request_path: + requestPath: description: - The request path of the HTTPS health check request. - The default value is /. returned: success type: str - timeout_sec: + timeoutSec: description: - How long (in seconds) to wait before claiming failure. - The default value is 5 seconds. It is invalid for timeoutSec to have greater value than checkIntervalSec. returned: success type: int - unhealthy_threshold: + unhealthyThreshold: description: - A so-far healthy instance will be marked unhealthy after this many consecutive failures. The default value is 2. @@ -233,7 +233,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -287,9 +288,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -300,9 +301,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/httpsHealthChecks".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -317,8 +318,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_https_health_check_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_https_health_check_facts.py index 9599e301908..5a599f764de 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_https_health_check_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_https_health_check_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,12 +66,12 @@ items: returned: always type: complex contains: - check_interval_sec: + checkIntervalSec: description: - How often (in seconds) to send a health check. The default value is 5 seconds. returned: success type: int - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -82,7 +82,7 @@ items: the resource. returned: success type: str - healthy_threshold: + healthyThreshold: description: - A so-far unhealthy instance will be marked healthy after this many consecutive successes. The default value is 2. @@ -116,20 +116,20 @@ items: - The default value is 80. returned: success type: int - request_path: + requestPath: description: - The request path of the HTTPS health check request. - The default value is /. returned: success type: str - timeout_sec: + timeoutSec: description: - How long (in seconds) to wait before claiming failure. - The default value is 5 seconds. It is invalid for timeoutSec to have greater value than checkIntervalSec. returned: success type: int - unhealthy_threshold: + unhealthyThreshold: description: - A so-far healthy instance will be marked unhealthy after this many consecutive failures. The default value is 2. @@ -151,7 +151,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_image.py b/lib/ansible/modules/cloud/google/gcp_compute_image.py index be56da41a46..bf3307b9856 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_image.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_image.py @@ -148,6 +148,11 @@ options: description: - Refers to a gcompute_disk object You must provide either this property or the rawDisk.source property but not both to create an image. + - 'This field represents a link to a Disk resource in GCP. It can be specified in + two ways. You can add `register: name-of-resource` to a gcp_compute_disk task and + then set this source_disk field to "{{ name-of-resource }}" Alternatively, you can + set this source_disk to a dictionary with the selfLink key where the value is the + selfLink of your Disk.' required: false source_disk_encryption_key: description: @@ -196,18 +201,18 @@ EXAMPLES = ''' name: "test_object" source_disk: "{{ disk }}" project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - archive_size_bytes: + archiveSizeBytes: description: - Size of the image tar.gz archive stored in Google Cloud Storage (in bytes). returned: success type: int - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -261,7 +266,7 @@ RETURN = ''' the resource. returned: success type: str - disk_size_gb: + diskSizeGb: description: - Size of the image when restored onto a persistent disk (in GB). returned: success @@ -274,7 +279,7 @@ RETURN = ''' comply with RFC1035. returned: success type: str - guest_os_features: + guestOsFeatures: description: - A list of features to enable on the guest OS. Applicable for bootable images only. Currently, only one feature can be enabled, VIRTIO_SCSI_MULTIQUEUE, which allows @@ -300,7 +305,7 @@ RETURN = ''' - The unique identifier for the resource. This identifier is defined by the server. returned: success type: int - image_encryption_key: + imageEncryptionKey: description: - Encrypts the image using a customer-supplied encryption key. - After you encrypt an image with a customer-supplied key, you must provide the same @@ -308,7 +313,7 @@ RETURN = ''' returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. @@ -335,20 +340,20 @@ RETURN = ''' be a dash. returned: success type: str - raw_disk: + rawDisk: description: - The parameters of the raw disk image. returned: success type: complex contains: - container_type: + containerType: description: - The format used to encode and transmit the block device, which should be TAR. This is just a container and transmission format and not a runtime format. Provided by the client when the disk image is created. returned: success type: str - sha1_checksum: + sha1Checksum: description: - An optional SHA1 checksum of the disk image before unpackaging. - This is provided by the client when the disk image is created. @@ -360,20 +365,20 @@ RETURN = ''' either this property or the sourceDisk property but not both. returned: success type: str - source_disk: + sourceDisk: description: - Refers to a gcompute_disk object You must provide either this property or the rawDisk.source property but not both to create an image. returned: success type: dict - source_disk_encryption_key: + sourceDiskEncryptionKey: description: - The customer-supplied encryption key of the source disk. Required if the source disk is protected by a customer-supplied encryption key. returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. @@ -385,14 +390,14 @@ RETURN = ''' that protects this resource. returned: success type: str - source_disk_id: + sourceDiskId: description: - The ID value of the disk used to create this image. This value may be used to determine whether the image was taken from the current or a previous instance of a given disk name. returned: success type: str - source_type: + sourceType: description: - The type of the image used to create this disk. The default and only value is RAW . @@ -458,7 +463,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -515,9 +521,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -528,9 +534,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/images".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -545,8 +551,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_image_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_image_facts.py index 516315890ff..17ecd7a8d08 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_image_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_image_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,12 +66,12 @@ items: returned: always type: complex contains: - archive_size_bytes: + archiveSizeBytes: description: - Size of the image tar.gz archive stored in Google Cloud Storage (in bytes). returned: success type: int - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -125,7 +125,7 @@ items: the resource. returned: success type: str - disk_size_gb: + diskSizeGb: description: - Size of the image when restored onto a persistent disk (in GB). returned: success @@ -138,7 +138,7 @@ items: comply with RFC1035. returned: success type: str - guest_os_features: + guestOsFeatures: description: - A list of features to enable on the guest OS. Applicable for bootable images only. Currently, only one feature can be enabled, VIRTIO_SCSI_MULTIQUEUE, which allows @@ -164,7 +164,7 @@ items: - The unique identifier for the resource. This identifier is defined by the server. returned: success type: int - image_encryption_key: + imageEncryptionKey: description: - Encrypts the image using a customer-supplied encryption key. - After you encrypt an image with a customer-supplied key, you must provide the same @@ -172,7 +172,7 @@ items: returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. @@ -199,20 +199,20 @@ items: be a dash. returned: success type: str - raw_disk: + rawDisk: description: - The parameters of the raw disk image. returned: success type: complex contains: - container_type: + containerType: description: - The format used to encode and transmit the block device, which should be TAR. This is just a container and transmission format and not a runtime format. Provided by the client when the disk image is created. returned: success type: str - sha1_checksum: + sha1Checksum: description: - An optional SHA1 checksum of the disk image before unpackaging. - This is provided by the client when the disk image is created. @@ -224,20 +224,20 @@ items: either this property or the sourceDisk property but not both. returned: success type: str - source_disk: + sourceDisk: description: - Refers to a gcompute_disk object You must provide either this property or the rawDisk.source property but not both to create an image. returned: success type: dict - source_disk_encryption_key: + sourceDiskEncryptionKey: description: - The customer-supplied encryption key of the source disk. Required if the source disk is protected by a customer-supplied encryption key. returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. @@ -249,14 +249,14 @@ items: that protects this resource. returned: success type: str - source_disk_id: + sourceDiskId: description: - The ID value of the disk used to create this image. This value may be used to determine whether the image was taken from the current or a previous instance of a given disk name. returned: success type: str - source_type: + sourceType: description: - The type of the image used to create this disk. The default and only value is RAW . @@ -278,7 +278,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_instance.py b/lib/ansible/modules/cloud/google/gcp_compute_instance.py index 5ac79538a30..4c52273d079 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_instance.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_instance.py @@ -171,6 +171,11 @@ options: or disks.source is required. - If desired, you can also attach existing non-root persistent disks using this property. This field is only applicable for persistent disks. + - 'This field represents a link to a Disk resource in GCP. It can be specified in + two ways. You can add `register: name-of-resource` to a gcp_compute_disk task and + then set this source field to "{{ name-of-resource }}" Alternatively, you can set + this source to a dictionary with the selfLink key where the value is the selfLink + of your Disk.' required: false type: description: @@ -249,6 +254,11 @@ options: field undefined to use an IP from a shared ephemeral IP address pool. If you specify a static external IP address, it must live in the same region as the zone of the instance. + - 'This field represents a link to a Address resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_address task + and then set this nat_ip field to "{{ name-of-resource }}" Alternatively, you can + set this nat_ip to a dictionary with the address key where the value is the address + of your Address.' required: false type: description: @@ -286,6 +296,11 @@ options: if neither the network nor the subnetwork is specified, the default network global/networks/default is used; if the network is not specified but the subnetwork is specified, the network is inferred. + - 'This field represents a link to a Network resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_network task + and then set this network field to "{{ name-of-resource }}" Alternatively, you can + set this network to a dictionary with the selfLink key where the value is the selfLink + of your Network.' required: false network_ip: description: @@ -298,6 +313,11 @@ options: - If the network resource is in legacy mode, do not provide this property. If the network is in auto subnet mode, providing the subnetwork is optional. If the network is in custom subnet mode, then this field should be specified. + - 'This field represents a link to a Subnetwork resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_subnetwork + task and then set this subnetwork field to "{{ name-of-resource }}" Alternatively, + you can set this subnetwork to a dictionary with the selfLink key where the value + is the selfLink of your Subnetwork.' required: false scheduling: description: @@ -417,24 +437,24 @@ EXAMPLES = ''' type: ONE_TO_ONE_NAT zone: us-central1-a project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - can_ip_forward: + canIpForward: description: - Allows this instance to send and receive packets with non-matching destination or source IPs. This is required if you plan to use this instance to forward routes. returned: success type: bool - cpu_platform: + cpuPlatform: description: - The CPU platform used by this instance. returned: success type: str - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -446,7 +466,7 @@ RETURN = ''' returned: success type: complex contains: - auto_delete: + autoDelete: description: - Specifies whether the disk will be auto-deleted when the instance is deleted (but not when the disk is detached from the instance). @@ -460,26 +480,26 @@ RETURN = ''' of the disk for its root filesystem. returned: success type: bool - device_name: + deviceName: description: - Specifies a unique device name of your choice that is reflected into the /dev/disk/by-id/google-* tree of a Linux operating system running within the instance. This name can be used to reference the device for mounting, resizing, and so on, from within the instance. returned: success type: str - disk_encryption_key: + diskEncryptionKey: description: - Encrypts or decrypts a disk using a customer-supplied encryption key. returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. returned: success type: str - rsa_encrypted_key: + rsaEncryptedKey: description: - Specifies an RFC 4648 base64 encoded, RSA-wrapped 2048-bit customer-supplied encryption key to either encrypt or decrypt this resource. @@ -498,7 +518,7 @@ RETURN = ''' a unique index number. If not specified, the server will choose an appropriate value. returned: success type: int - initialize_params: + initializeParams: description: - Specifies the parameters for a new disk that will be created alongside the new instance. Use initialization parameters to create boot disks or local SSDs attached to the @@ -506,32 +526,32 @@ RETURN = ''' returned: success type: complex contains: - disk_name: + diskName: description: - Specifies the disk name. If not specified, the default is to use the name of the instance. returned: success type: str - disk_size_gb: + diskSizeGb: description: - Specifies the size of the disk in base-2 GB. returned: success type: int - disk_type: + diskType: description: - Reference to a gcompute_disk_type resource. - Specifies the disk type to use to create the instance. - If not specified, the default is pd-standard. returned: success type: str - source_image: + sourceImage: description: - The source image to create this disk. When creating a new instance, one of initializeParams.sourceImage or disks.source is required. To create a disk with one of the public operating system images, specify the image by its family name. returned: success type: str - source_image_encryption_key: + sourceImageEncryptionKey: description: - The customer-supplied encryption key of the source image. Required if the source image is protected by a customer-supplied encryption key. @@ -541,7 +561,7 @@ RETURN = ''' returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. @@ -581,18 +601,18 @@ RETURN = ''' the default is PERSISTENT. returned: success type: str - guest_accelerators: + guestAccelerators: description: - List of the type and count of accelerator cards attached to the instance . returned: success type: complex contains: - accelerator_count: + acceleratorCount: description: - The number of the guest accelerator cards exposed to this instance. returned: success type: int - accelerator_type: + acceleratorType: description: - Full or partial URL of the accelerator type resource to expose to this instance. returned: success @@ -602,7 +622,7 @@ RETURN = ''' - The unique identifier for the resource. This identifier is defined by the server. returned: success type: int - label_fingerprint: + labelFingerprint: description: - A fingerprint for this request, which is essentially a hash of the metadata's contents and used for optimistic locking. The fingerprint is initially generated by Compute @@ -616,12 +636,12 @@ RETURN = ''' These pairs can consist of custom metadata or predefined keys. returned: success type: dict - machine_type: + machineType: description: - A reference to a machine type which defines VM kind. returned: success type: str - min_cpu_platform: + minCpuPlatform: description: - Specifies a minimum CPU platform for the VM instance. Applicable values are the friendly names of CPU platforms . @@ -637,7 +657,7 @@ RETURN = ''' be a dash. returned: success type: str - network_interfaces: + networkInterfaces: description: - An array of configurations for this interface. This specifies how this interface is configured to interact with other network services, such as connecting to the @@ -645,7 +665,7 @@ RETURN = ''' returned: success type: complex contains: - access_configs: + accessConfigs: description: - An array of configurations for this interface. Currently, only one access config, ONE_TO_ONE_NAT, is supported. If there are no accessConfigs specified, then this @@ -660,7 +680,7 @@ RETURN = ''' IP or Network Access. returned: success type: str - nat_ip: + natIP: description: - Specifies the title of a gcompute_address. - An external IP address associated with this instance. @@ -675,14 +695,14 @@ RETURN = ''' - The type of configuration. The default and only option is ONE_TO_ONE_NAT. returned: success type: str - alias_ip_ranges: + aliasIpRanges: description: - An array of alias IP ranges for this network interface. Can only be specified for network interfaces on subnet-mode networks. returned: success type: complex contains: - ip_cidr_range: + ipCidrRange: description: - The IP CIDR range represented by this alias IP range. - This IP CIDR range must belong to the specified subnetwork and cannot contain IP @@ -691,7 +711,7 @@ RETURN = ''' (e.g. 10.1.2.0/24). returned: success type: str - subnetwork_range_name: + subnetworkRangeName: description: - Optional subnetwork secondary range name specifying the secondary range from which to allocate the IP CIDR range for this alias IP range. If left unspecified, the @@ -712,7 +732,7 @@ RETURN = ''' is inferred. returned: success type: dict - network_ip: + networkIP: description: - An IPv4 internal network address to assign to the instance for this network interface. If not specified by the user, an unused internal IP is assigned by the system. @@ -732,7 +752,7 @@ RETURN = ''' returned: success type: complex contains: - automatic_restart: + automaticRestart: description: - Specifies whether the instance should be automatically restarted if it is terminated by Compute Engine (not terminated by a user). @@ -740,7 +760,7 @@ RETURN = ''' instances cannot be automatically restarted. returned: success type: bool - on_host_maintenance: + onHostMaintenance: description: - Defines the maintenance behavior for this instance. For standard instances, the default behavior is MIGRATE. For preemptible instances, the default and only possible @@ -754,7 +774,7 @@ RETURN = ''' creation, it cannot be set or changed after the instance has been created. returned: success type: bool - service_accounts: + serviceAccounts: description: - A list of service accounts, with their specified scopes, authorized for this instance. Only one service account per VM instance is supported. @@ -777,7 +797,7 @@ RETURN = ''' RUNNING, STOPPING, SUSPENDING, SUSPENDED, and TERMINATED.' returned: success type: str - status_message: + statusMessage: description: - An optional, human-readable explanation of the status. returned: success @@ -911,7 +931,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind, fetch) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -934,9 +955,28 @@ def create(module, link, kind): return wait_for_operation(module, auth.post(link, resource_to_request(module))) -def update(module, link, kind): +def update(module, link, kind, fetch): + update_fields(module, resource_to_request(module), + response_to_hash(module, fetch)) + return fetch_resource(module, self_link(module), kind) + + +def update_fields(module, request, response): + if response.get('machineType') != request.get('machineType'): + machine_type_update(module, request, response) + + +def machine_type_update(module, request, response): auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projdcts/{project}/zones/{zone}/instances/{name}/setMachineType" + ]).format(**module.params), + { + u'machineType': machine_type_selflink(module.params.get('machine_type'), module.params) + } + ) def delete(module, link, kind): @@ -969,9 +1009,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -982,9 +1022,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/instances".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -1001,8 +1041,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result @@ -1053,7 +1091,7 @@ def response_to_hash(module, response): def disk_type_selflink(name, params): if name is None: return - url = r"https://www.googleapis.com/compute/v1/projects/.*/zones/{zone}/diskTypes/[a-z1-9\-]*" + url = r"https://www.googleapis.com/compute/v1/projects/.*/zones/[a-z1-9\-]*/diskTypes/[a-z1-9\-]*" if not re.match(url, name): name = "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/diskTypes/%s".format(**params) % name return name @@ -1062,7 +1100,7 @@ def disk_type_selflink(name, params): def machine_type_selflink(name, params): if name is None: return - url = r"https://www.googleapis.com/compute/v1/projects/.*/zones/{zone}/machineTypes/[a-z1-9\-]*" + url = r"https://www.googleapis.com/compute/v1/projects/.*/zones/[a-z1-9\-]*/machineTypes/[a-z1-9\-]*" if not re.match(url, name): name = "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/machineTypes/%s".format(**params) % name return name @@ -1112,7 +1150,7 @@ def encode_request(request, module): def decode_response(response, module): - if 'metadata' in response: + if 'metadata' in response and response['metadata'] is not None: response['metadata'] = metadata_decoder(response['metadata']) return response diff --git a/lib/ansible/modules/cloud/google/gcp_compute_instance_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_instance_facts.py index a8fc1d5050d..1d1096b8c3f 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_instance_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_instance_facts.py @@ -61,7 +61,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -71,18 +71,18 @@ items: returned: always type: complex contains: - can_ip_forward: + canIpForward: description: - Allows this instance to send and receive packets with non-matching destination or source IPs. This is required if you plan to use this instance to forward routes. returned: success type: bool - cpu_platform: + cpuPlatform: description: - The CPU platform used by this instance. returned: success type: str - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -94,7 +94,7 @@ items: returned: success type: complex contains: - auto_delete: + autoDelete: description: - Specifies whether the disk will be auto-deleted when the instance is deleted (but not when the disk is detached from the instance). @@ -108,26 +108,26 @@ items: of the disk for its root filesystem. returned: success type: bool - device_name: + deviceName: description: - Specifies a unique device name of your choice that is reflected into the /dev/disk/by-id/google-* tree of a Linux operating system running within the instance. This name can be used to reference the device for mounting, resizing, and so on, from within the instance. returned: success type: str - disk_encryption_key: + diskEncryptionKey: description: - Encrypts or decrypts a disk using a customer-supplied encryption key. returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. returned: success type: str - rsa_encrypted_key: + rsaEncryptedKey: description: - Specifies an RFC 4648 base64 encoded, RSA-wrapped 2048-bit customer-supplied encryption key to either encrypt or decrypt this resource. @@ -146,7 +146,7 @@ items: a unique index number. If not specified, the server will choose an appropriate value. returned: success type: int - initialize_params: + initializeParams: description: - Specifies the parameters for a new disk that will be created alongside the new instance. Use initialization parameters to create boot disks or local SSDs attached to the @@ -154,32 +154,32 @@ items: returned: success type: complex contains: - disk_name: + diskName: description: - Specifies the disk name. If not specified, the default is to use the name of the instance. returned: success type: str - disk_size_gb: + diskSizeGb: description: - Specifies the size of the disk in base-2 GB. returned: success type: int - disk_type: + diskType: description: - Reference to a gcompute_disk_type resource. - Specifies the disk type to use to create the instance. - If not specified, the default is pd-standard. returned: success type: str - source_image: + sourceImage: description: - The source image to create this disk. When creating a new instance, one of initializeParams.sourceImage or disks.source is required. To create a disk with one of the public operating system images, specify the image by its family name. returned: success type: str - source_image_encryption_key: + sourceImageEncryptionKey: description: - The customer-supplied encryption key of the source image. Required if the source image is protected by a customer-supplied encryption key. @@ -189,7 +189,7 @@ items: returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. @@ -229,18 +229,18 @@ items: the default is PERSISTENT. returned: success type: str - guest_accelerators: + guestAccelerators: description: - List of the type and count of accelerator cards attached to the instance . returned: success type: complex contains: - accelerator_count: + acceleratorCount: description: - The number of the guest accelerator cards exposed to this instance. returned: success type: int - accelerator_type: + acceleratorType: description: - Full or partial URL of the accelerator type resource to expose to this instance. returned: success @@ -250,7 +250,7 @@ items: - The unique identifier for the resource. This identifier is defined by the server. returned: success type: int - label_fingerprint: + labelFingerprint: description: - A fingerprint for this request, which is essentially a hash of the metadata's contents and used for optimistic locking. The fingerprint is initially generated by Compute @@ -264,12 +264,12 @@ items: These pairs can consist of custom metadata or predefined keys. returned: success type: dict - machine_type: + machineType: description: - A reference to a machine type which defines VM kind. returned: success type: str - min_cpu_platform: + minCpuPlatform: description: - Specifies a minimum CPU platform for the VM instance. Applicable values are the friendly names of CPU platforms . @@ -285,7 +285,7 @@ items: be a dash. returned: success type: str - network_interfaces: + networkInterfaces: description: - An array of configurations for this interface. This specifies how this interface is configured to interact with other network services, such as connecting to the @@ -293,7 +293,7 @@ items: returned: success type: complex contains: - access_configs: + accessConfigs: description: - An array of configurations for this interface. Currently, only one access config, ONE_TO_ONE_NAT, is supported. If there are no accessConfigs specified, then this @@ -308,7 +308,7 @@ items: IP or Network Access. returned: success type: str - nat_ip: + natIP: description: - Specifies the title of a gcompute_address. - An external IP address associated with this instance. @@ -323,14 +323,14 @@ items: - The type of configuration. The default and only option is ONE_TO_ONE_NAT. returned: success type: str - alias_ip_ranges: + aliasIpRanges: description: - An array of alias IP ranges for this network interface. Can only be specified for network interfaces on subnet-mode networks. returned: success type: complex contains: - ip_cidr_range: + ipCidrRange: description: - The IP CIDR range represented by this alias IP range. - This IP CIDR range must belong to the specified subnetwork and cannot contain IP @@ -339,7 +339,7 @@ items: (e.g. 10.1.2.0/24). returned: success type: str - subnetwork_range_name: + subnetworkRangeName: description: - Optional subnetwork secondary range name specifying the secondary range from which to allocate the IP CIDR range for this alias IP range. If left unspecified, the @@ -360,7 +360,7 @@ items: is inferred. returned: success type: dict - network_ip: + networkIP: description: - An IPv4 internal network address to assign to the instance for this network interface. If not specified by the user, an unused internal IP is assigned by the system. @@ -380,7 +380,7 @@ items: returned: success type: complex contains: - automatic_restart: + automaticRestart: description: - Specifies whether the instance should be automatically restarted if it is terminated by Compute Engine (not terminated by a user). @@ -388,7 +388,7 @@ items: instances cannot be automatically restarted. returned: success type: bool - on_host_maintenance: + onHostMaintenance: description: - Defines the maintenance behavior for this instance. For standard instances, the default behavior is MIGRATE. For preemptible instances, the default and only possible @@ -402,7 +402,7 @@ items: creation, it cannot be set or changed after the instance has been created. returned: success type: bool - service_accounts: + serviceAccounts: description: - A list of service accounts, with their specified scopes, authorized for this instance. Only one service account per VM instance is supported. @@ -425,7 +425,7 @@ items: RUNNING, STOPPING, SUSPENDING, SUSPENDED, and TERMINATED.' returned: success type: str - status_message: + statusMessage: description: - An optional, human-readable explanation of the status. returned: success diff --git a/lib/ansible/modules/cloud/google/gcp_compute_instance_group.py b/lib/ansible/modules/cloud/google/gcp_compute_instance_group.py index 276798dcc82..f1b711e03f9 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_instance_group.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_instance_group.py @@ -81,6 +81,11 @@ options: network: description: - The network to which all instances in the instance group belong. + - 'This field represents a link to a Network resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_network task + and then set this network field to "{{ name-of-resource }}" Alternatively, you can + set this network to a dictionary with the selfLink key where the value is the selfLink + of your Network.' required: false region: description: @@ -89,11 +94,25 @@ options: subnetwork: description: - The subnetwork to which all instances in the instance group belong. + - 'This field represents a link to a Subnetwork resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_subnetwork + task and then set this subnetwork field to "{{ name-of-resource }}" Alternatively, + you can set this subnetwork to a dictionary with the selfLink key where the value + is the selfLink of your Subnetwork.' required: false zone: description: - A reference to the zone where the instance group resides. required: true + instances: + description: + - The list of instances associated with this InstanceGroup. + - All instances must be created before being added to an InstanceGroup. + - All instances not in this list will be removed from the InstanceGroup and will not + be deleted. + - Only the full identifier of the instance will be returned. + required: false + version_added: 2.8 extends_documentation_fragment: gcp ''' @@ -116,13 +135,13 @@ EXAMPLES = ''' network: "{{ network }}" zone: us-central1-a project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -144,7 +163,7 @@ RETURN = ''' - The name must be 1-63 characters long, and comply with RFC1035. returned: success type: str - named_ports: + namedPorts: description: - Assigns a name to a port number. - 'For example: {name: "http", port: 80}.' @@ -186,6 +205,15 @@ RETURN = ''' - A reference to the zone where the instance group resides. returned: success type: str + instances: + description: + - The list of instances associated with this InstanceGroup. + - All instances must be created before being added to an InstanceGroup. + - All instances not in this list will be removed from the InstanceGroup and will not + be deleted. + - Only the full identifier of the instance will be returned. + returned: success + type: list ''' ################################################################################ @@ -217,7 +245,8 @@ def main(): network=dict(type='dict'), region=dict(type='str'), subnetwork=dict(type='dict'), - zone=dict(required=True, type='str') + zone=dict(required=True, type='str'), + instances=dict(type='list', elements='dict') ) ) @@ -233,7 +262,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -246,6 +276,10 @@ def main(): else: fetch = {} + if fetch: + instance = InstanceLogic(module) + instance.run() + fetch.update({'instances': instance.list_instances()}) fetch.update({'changed': changed}) module.exit_json(**fetch) @@ -257,7 +291,8 @@ def create(module, link, kind): def update(module, link, kind): - module.fail_json(msg="InstanceGroup cannot be edited") + instance = InstanceLogic(module) + instance.run() def delete(module, link, kind): @@ -283,9 +318,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -296,9 +331,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/instanceGroups".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -313,8 +348,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result @@ -398,6 +431,66 @@ def raise_if_errors(response, err_path, module): module.fail_json(msg=errors) +class InstanceLogic(object): + def __init__(self, module): + self.module = module + self.current_instances = self.list_instances() + self.module_instances = [] + + # Transform module list of instances (dicts of instance responses) into a list of selfLinks. + instances = self.module.params.get('instances') + if instances: + for instance in instances: + self.module_instances.append(replace_resource_dict(instance, 'selfLink')) + + def run(self): + # Find all instances to add and add them + instances_to_add = list(set(self.module_instances) - set(self.current_instances)) + if instances_to_add: + self.add_instances(instances_to_add) + + # Find all instances to remove and remove them + instances_to_remove = list(set(self.current_instances) - set(self.module_instances)) + if instances_to_remove: + self.remove_instances(instances_to_remove) + + def list_instances(self): + auth = GcpSession(self.module, 'compute') + response = return_if_object(self.module, auth.post(self._list_instances_url(), {'instanceState': 'ALL'}), + 'compute#instanceGroupsListInstances') + + # Transform instance list into a list of selfLinks for diffing with module parameters + instances = [] + for instance in response.get('items', []): + instances.append(instance['instance']) + return instances + + def add_instances(self, instances): + auth = GcpSession(self.module, 'compute') + wait_for_operation(self.module, auth.post(self._add_instances_url(), self._build_request(instances))) + + def remove_instances(self, instances): + auth = GcpSession(self.module, 'compute') + wait_for_operation(self.module, auth.post(self._remove_instances_url(), self._build_request(instances))) + + def _list_instances_url(self): + return "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/instanceGroups/{name}/listInstances".format(**self.module.params) + + def _remove_instances_url(self): + return "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/instanceGroups/{name}/removeInstances".format(**self.module.params) + + def _add_instances_url(self): + return "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/instanceGroups/{name}/addInstances".format(**self.module.params) + + def _build_request(self, instances): + request = { + 'instances': [] + } + for instance in instances: + request['instances'].append({'instance': instance}) + return request + + class InstanceGroupNamedPortsArray(object): def __init__(self, request, module): self.module = module diff --git a/lib/ansible/modules/cloud/google/gcp_compute_instance_group_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_instance_group_facts.py index 9ffccbf34c6..952363ec5ab 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_instance_group_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_instance_group_facts.py @@ -61,7 +61,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -71,7 +71,7 @@ items: returned: always type: complex contains: - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -93,7 +93,7 @@ items: - The name must be 1-63 characters long, and comply with RFC1035. returned: success type: str - named_ports: + namedPorts: description: - Assigns a name to a port number. - 'For example: {name: "http", port: 80}.' @@ -135,6 +135,15 @@ items: - A reference to the zone where the instance group resides. returned: success type: str + instances: + description: + - The list of instances associated with this InstanceGroup. + - All instances must be created before being added to an InstanceGroup. + - All instances not in this list will be removed from the InstanceGroup and will not + be deleted. + - Only the full identifier of the instance will be returned. + returned: success + type: list ''' ################################################################################ diff --git a/lib/ansible/modules/cloud/google/gcp_compute_instance_group_manager.py b/lib/ansible/modules/cloud/google/gcp_compute_instance_group_manager.py index c3e39d3d53c..f48f8c2d697 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_instance_group_manager.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_instance_group_manager.py @@ -67,6 +67,11 @@ options: description: - The instance template that is specified for this managed instance group. The group uses this template to create all new instances in the managed instance group. + - 'This field represents a link to a InstanceTemplate resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_instance_template + task and then set this instance_template field to "{{ name-of-resource }}" Alternatively, + you can set this instance_template to a dictionary with the selfLink key where the + value is the selfLink of your InstanceTemplate.' required: true name: description: @@ -156,13 +161,13 @@ EXAMPLES = ''' target_size: 3 zone: us-west1-a project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - base_instance_name: + baseInstanceName: description: - The base instance name to use for instances in this group. The value must be 1-58 characters long. Instances are named by appending a hyphen and a random four-character @@ -170,12 +175,12 @@ RETURN = ''' - The base instance name must comply with RFC1035. returned: success type: str - creation_timestamp: + creationTimestamp: description: - The creation timestamp for this managed instance group in RFC3339 text format. returned: success type: str - current_actions: + currentActions: description: - The list of instance actions and the number of instances in this managed instance group that are scheduled for each of those actions. @@ -198,7 +203,7 @@ RETURN = ''' the creatingWithoutRetries field will be populated. returned: success type: int - creating_without_retries: + creatingWithoutRetries: description: - The number of instances that the managed instance group will attempt to create. The group attempts to create each instance only once. If the group fails to create @@ -249,12 +254,12 @@ RETURN = ''' - A unique identifier for this resource. returned: success type: int - instance_group: + instanceGroup: description: - The instance group being managed. returned: success type: dict - instance_template: + instanceTemplate: description: - The instance template that is specified for this managed instance group. The group uses this template to create all new instances in the managed instance group. @@ -266,7 +271,7 @@ RETURN = ''' comply with RFC1035. returned: success type: str - named_ports: + namedPorts: description: - Named ports configured for the Instance Groups complementary to this Instance Group Manager. @@ -289,14 +294,14 @@ RETURN = ''' - The region this managed instance group resides (for regional resources). returned: success type: str - target_pools: + targetPools: description: - TargetPool resources to which instances in the instanceGroup field are added. The target pools automatically apply to all of the instances in the managed instance group. returned: success type: list - target_size: + targetSize: description: - The target number of running instances for this managed instance group. Deleting or abandoning instances reduces this number. Resizing the group changes this number. @@ -355,7 +360,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -407,9 +413,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -420,9 +426,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/instanceGroupManagers".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -437,8 +443,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_instance_group_manager_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_instance_group_manager_facts.py index 2d8928b6e90..5db47bbdb19 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_instance_group_manager_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_instance_group_manager_facts.py @@ -61,7 +61,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -71,7 +71,7 @@ items: returned: always type: complex contains: - base_instance_name: + baseInstanceName: description: - The base instance name to use for instances in this group. The value must be 1-58 characters long. Instances are named by appending a hyphen and a random four-character @@ -79,12 +79,12 @@ items: - The base instance name must comply with RFC1035. returned: success type: str - creation_timestamp: + creationTimestamp: description: - The creation timestamp for this managed instance group in RFC3339 text format. returned: success type: str - current_actions: + currentActions: description: - The list of instance actions and the number of instances in this managed instance group that are scheduled for each of those actions. @@ -107,7 +107,7 @@ items: the creatingWithoutRetries field will be populated. returned: success type: int - creating_without_retries: + creatingWithoutRetries: description: - The number of instances that the managed instance group will attempt to create. The group attempts to create each instance only once. If the group fails to create @@ -158,12 +158,12 @@ items: - A unique identifier for this resource. returned: success type: int - instance_group: + instanceGroup: description: - The instance group being managed. returned: success type: dict - instance_template: + instanceTemplate: description: - The instance template that is specified for this managed instance group. The group uses this template to create all new instances in the managed instance group. @@ -175,7 +175,7 @@ items: comply with RFC1035. returned: success type: str - named_ports: + namedPorts: description: - Named ports configured for the Instance Groups complementary to this Instance Group Manager. @@ -198,14 +198,14 @@ items: - The region this managed instance group resides (for regional resources). returned: success type: str - target_pools: + targetPools: description: - TargetPool resources to which instances in the instanceGroup field are added. The target pools automatically apply to all of the instances in the managed instance group. returned: success type: list - target_size: + targetSize: description: - The target number of running instances for this managed instance group. Deleting or abandoning instances reduces this number. Resizing the group changes this number. diff --git a/lib/ansible/modules/cloud/google/gcp_compute_instance_template.py b/lib/ansible/modules/cloud/google/gcp_compute_instance_template.py index a3b4919e7f0..519f965f156 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_instance_template.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_instance_template.py @@ -200,6 +200,11 @@ options: - If desired, you can also attach existing non-root persistent disks using this property. This field is only applicable for persistent disks. - Note that for InstanceTemplate, specify the disk name, not the URL for the disk. + - 'This field represents a link to a Disk resource in GCP. It can be specified in + two ways. You can add `register: name-of-resource` to a gcp_compute_disk task and + then set this source field to "{{ name-of-resource }}" Alternatively, you can set + this source to a dictionary with the name key where the value is the name of your + Disk.' required: false type: description: @@ -257,6 +262,11 @@ options: field undefined to use an IP from a shared ephemeral IP address pool. If you specify a static external IP address, it must live in the same region as the zone of the instance. + - 'This field represents a link to a Address resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_address task + and then set this nat_ip field to "{{ name-of-resource }}" Alternatively, you can + set this nat_ip to a dictionary with the address key where the value is the address + of your Address.' required: false type: description: @@ -294,6 +304,11 @@ options: if neither the network nor the subnetwork is specified, the default network global/networks/default is used; if the network is not specified but the subnetwork is specified, the network is inferred. + - 'This field represents a link to a Network resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_network task + and then set this network field to "{{ name-of-resource }}" Alternatively, you can + set this network to a dictionary with the selfLink key where the value is the selfLink + of your Network.' required: false network_ip: description: @@ -306,6 +321,11 @@ options: - If the network resource is in legacy mode, do not provide this property. If the network is in auto subnet mode, providing the subnetwork is optional. If the network is in custom subnet mode, then this field should be specified. + - 'This field represents a link to a Subnetwork resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_subnetwork + task and then set this subnetwork field to "{{ name-of-resource }}" Alternatively, + you can set this subnetwork to a dictionary with the selfLink key where the value + is the selfLink of your Subnetwork.' required: false scheduling: description: @@ -407,13 +427,13 @@ EXAMPLES = ''' type: ONE_TO_ONE_NAT nat_ip: "{{ address }}" project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -440,7 +460,7 @@ RETURN = ''' returned: success type: complex contains: - can_ip_forward: + canIpForward: description: - Enables instances created based on this template to send packets with source IP addresses other than their own and receive packets with destination IP addresses @@ -462,7 +482,7 @@ RETURN = ''' returned: success type: complex contains: - auto_delete: + autoDelete: description: - Specifies whether the disk will be auto-deleted when the instance is deleted (but not when the disk is detached from the instance). @@ -476,26 +496,26 @@ RETURN = ''' of the disk for its root filesystem. returned: success type: bool - device_name: + deviceName: description: - Specifies a unique device name of your choice that is reflected into the /dev/disk/by-id/google-* tree of a Linux operating system running within the instance. This name can be used to reference the device for mounting, resizing, and so on, from within the instance. returned: success type: str - disk_encryption_key: + diskEncryptionKey: description: - Encrypts or decrypts a disk using a customer-supplied encryption key. returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. returned: success type: str - rsa_encrypted_key: + rsaEncryptedKey: description: - Specifies an RFC 4648 base64 encoded, RSA-wrapped 2048-bit customer-supplied encryption key to either encrypt or decrypt this resource. @@ -514,7 +534,7 @@ RETURN = ''' a unique index number. If not specified, the server will choose an appropriate value. returned: success type: int - initialize_params: + initializeParams: description: - Specifies the parameters for a new disk that will be created alongside the new instance. Use initialization parameters to create boot disks or local SSDs attached to the @@ -522,32 +542,32 @@ RETURN = ''' returned: success type: complex contains: - disk_name: + diskName: description: - Specifies the disk name. If not specified, the default is to use the name of the instance. returned: success type: str - disk_size_gb: + diskSizeGb: description: - Specifies the size of the disk in base-2 GB. returned: success type: int - disk_type: + diskType: description: - Reference to a gcompute_disk_type resource. - Specifies the disk type to use to create the instance. - If not specified, the default is pd-standard. returned: success type: str - source_image: + sourceImage: description: - The source image to create this disk. When creating a new instance, one of initializeParams.sourceImage or disks.source is required. To create a disk with one of the public operating system images, specify the image by its family name. returned: success type: str - source_image_encryption_key: + sourceImageEncryptionKey: description: - The customer-supplied encryption key of the source image. Required if the source image is protected by a customer-supplied encryption key. @@ -557,7 +577,7 @@ RETURN = ''' returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. @@ -598,7 +618,7 @@ RETURN = ''' the default is PERSISTENT. returned: success type: str - machine_type: + machineType: description: - Reference to a gcompute_machine_type resource. returned: success @@ -609,23 +629,23 @@ RETURN = ''' These pairs can consist of custom metadata or predefined keys. returned: success type: dict - guest_accelerators: + guestAccelerators: description: - List of the type and count of accelerator cards attached to the instance . returned: success type: complex contains: - accelerator_count: + acceleratorCount: description: - The number of the guest accelerator cards exposed to this instance. returned: success type: int - accelerator_type: + acceleratorType: description: - Full or partial URL of the accelerator type resource to expose to this instance. returned: success type: str - network_interfaces: + networkInterfaces: description: - An array of configurations for this interface. This specifies how this interface is configured to interact with other network services, such as connecting to the @@ -633,7 +653,7 @@ RETURN = ''' returned: success type: complex contains: - access_configs: + accessConfigs: description: - An array of configurations for this interface. Currently, only one access config, ONE_TO_ONE_NAT, is supported. If there are no accessConfigs specified, then this @@ -648,7 +668,7 @@ RETURN = ''' IP or Network Access. returned: success type: str - nat_ip: + natIP: description: - Specifies the title of a gcompute_address. - An external IP address associated with this instance. @@ -663,14 +683,14 @@ RETURN = ''' - The type of configuration. The default and only option is ONE_TO_ONE_NAT. returned: success type: str - alias_ip_ranges: + aliasIpRanges: description: - An array of alias IP ranges for this network interface. Can only be specified for network interfaces on subnet-mode networks. returned: success type: complex contains: - ip_cidr_range: + ipCidrRange: description: - The IP CIDR range represented by this alias IP range. - This IP CIDR range must belong to the specified subnetwork and cannot contain IP @@ -679,7 +699,7 @@ RETURN = ''' (e.g. 10.1.2.0/24). returned: success type: str - subnetwork_range_name: + subnetworkRangeName: description: - Optional subnetwork secondary range name specifying the secondary range from which to allocate the IP CIDR range for this alias IP range. If left unspecified, the @@ -700,7 +720,7 @@ RETURN = ''' is inferred. returned: success type: dict - network_ip: + networkIP: description: - An IPv4 internal network address to assign to the instance for this network interface. If not specified by the user, an unused internal IP is assigned by the system. @@ -720,7 +740,7 @@ RETURN = ''' returned: success type: complex contains: - automatic_restart: + automaticRestart: description: - Specifies whether the instance should be automatically restarted if it is terminated by Compute Engine (not terminated by a user). @@ -728,7 +748,7 @@ RETURN = ''' instances cannot be automatically restarted. returned: success type: bool - on_host_maintenance: + onHostMaintenance: description: - Defines the maintenance behavior for this instance. For standard instances, the default behavior is MIGRATE. For preemptible instances, the default and only possible @@ -742,7 +762,7 @@ RETURN = ''' creation, it cannot be set or changed after the instance has been created. returned: success type: bool - service_accounts: + serviceAccounts: description: - A list of service accounts, with their specified scopes, authorized for this instance. Only one service account per VM instance is supported. @@ -884,7 +904,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -908,8 +929,7 @@ def create(module, link, kind): def update(module, link, kind): - auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + module.fail_json(msg="InstanceTemplate cannot be edited") def delete(module, link, kind): @@ -933,9 +953,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -946,9 +966,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/instanceTemplates".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -965,8 +985,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result @@ -1005,7 +1023,7 @@ def response_to_hash(module, response): def disk_type_selflink(name, params): if name is None: return - url = r"https://www.googleapis.com/compute/v1/projects/.*/zones/{zone}/diskTypes/[a-z1-9\-]*" + url = r"https://www.googleapis.com/compute/v1/projects/.*/zones/[a-z1-9\-]*/diskTypes/[a-z1-9\-]*" if not re.match(url, name): name = "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/diskTypes/%s".format(**params) % name return name @@ -1049,13 +1067,13 @@ def raise_if_errors(response, err_path, module): def encode_request(request, module): - if 'metadata' in request: + if 'metadata' in request and request['metadata'] is not None: request['metadata'] = metadata_encoder(request['metadata']) return request def decode_response(response, module): - if 'metadata' in response: + if 'metadata' in response and response['metadata'] is not None: response['metadata'] = metadata_decoder(response['metadata']) return response diff --git a/lib/ansible/modules/cloud/google/gcp_compute_instance_template_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_instance_template_facts.py index aca57a632ca..5d687b9d928 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_instance_template_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_instance_template_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,7 +66,7 @@ items: returned: always type: complex contains: - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -93,7 +93,7 @@ items: returned: success type: complex contains: - can_ip_forward: + canIpForward: description: - Enables instances created based on this template to send packets with source IP addresses other than their own and receive packets with destination IP addresses @@ -115,7 +115,7 @@ items: returned: success type: complex contains: - auto_delete: + autoDelete: description: - Specifies whether the disk will be auto-deleted when the instance is deleted (but not when the disk is detached from the instance). @@ -129,26 +129,26 @@ items: of the disk for its root filesystem. returned: success type: bool - device_name: + deviceName: description: - Specifies a unique device name of your choice that is reflected into the /dev/disk/by-id/google-* tree of a Linux operating system running within the instance. This name can be used to reference the device for mounting, resizing, and so on, from within the instance. returned: success type: str - disk_encryption_key: + diskEncryptionKey: description: - Encrypts or decrypts a disk using a customer-supplied encryption key. returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. returned: success type: str - rsa_encrypted_key: + rsaEncryptedKey: description: - Specifies an RFC 4648 base64 encoded, RSA-wrapped 2048-bit customer-supplied encryption key to either encrypt or decrypt this resource. @@ -167,7 +167,7 @@ items: a unique index number. If not specified, the server will choose an appropriate value. returned: success type: int - initialize_params: + initializeParams: description: - Specifies the parameters for a new disk that will be created alongside the new instance. Use initialization parameters to create boot disks or local SSDs attached to the @@ -175,32 +175,32 @@ items: returned: success type: complex contains: - disk_name: + diskName: description: - Specifies the disk name. If not specified, the default is to use the name of the instance. returned: success type: str - disk_size_gb: + diskSizeGb: description: - Specifies the size of the disk in base-2 GB. returned: success type: int - disk_type: + diskType: description: - Reference to a gcompute_disk_type resource. - Specifies the disk type to use to create the instance. - If not specified, the default is pd-standard. returned: success type: str - source_image: + sourceImage: description: - The source image to create this disk. When creating a new instance, one of initializeParams.sourceImage or disks.source is required. To create a disk with one of the public operating system images, specify the image by its family name. returned: success type: str - source_image_encryption_key: + sourceImageEncryptionKey: description: - The customer-supplied encryption key of the source image. Required if the source image is protected by a customer-supplied encryption key. @@ -210,7 +210,7 @@ items: returned: success type: complex contains: - raw_key: + rawKey: description: - Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. @@ -251,7 +251,7 @@ items: the default is PERSISTENT. returned: success type: str - machine_type: + machineType: description: - Reference to a gcompute_machine_type resource. returned: success @@ -262,23 +262,23 @@ items: These pairs can consist of custom metadata or predefined keys. returned: success type: dict - guest_accelerators: + guestAccelerators: description: - List of the type and count of accelerator cards attached to the instance . returned: success type: complex contains: - accelerator_count: + acceleratorCount: description: - The number of the guest accelerator cards exposed to this instance. returned: success type: int - accelerator_type: + acceleratorType: description: - Full or partial URL of the accelerator type resource to expose to this instance. returned: success type: str - network_interfaces: + networkInterfaces: description: - An array of configurations for this interface. This specifies how this interface is configured to interact with other network services, such as connecting to the @@ -286,7 +286,7 @@ items: returned: success type: complex contains: - access_configs: + accessConfigs: description: - An array of configurations for this interface. Currently, only one access config, ONE_TO_ONE_NAT, is supported. If there are no accessConfigs specified, then this @@ -301,7 +301,7 @@ items: IP or Network Access. returned: success type: str - nat_ip: + natIP: description: - Specifies the title of a gcompute_address. - An external IP address associated with this instance. @@ -316,14 +316,14 @@ items: - The type of configuration. The default and only option is ONE_TO_ONE_NAT. returned: success type: str - alias_ip_ranges: + aliasIpRanges: description: - An array of alias IP ranges for this network interface. Can only be specified for network interfaces on subnet-mode networks. returned: success type: complex contains: - ip_cidr_range: + ipCidrRange: description: - The IP CIDR range represented by this alias IP range. - This IP CIDR range must belong to the specified subnetwork and cannot contain IP @@ -332,7 +332,7 @@ items: (e.g. 10.1.2.0/24). returned: success type: str - subnetwork_range_name: + subnetworkRangeName: description: - Optional subnetwork secondary range name specifying the secondary range from which to allocate the IP CIDR range for this alias IP range. If left unspecified, the @@ -353,7 +353,7 @@ items: is inferred. returned: success type: dict - network_ip: + networkIP: description: - An IPv4 internal network address to assign to the instance for this network interface. If not specified by the user, an unused internal IP is assigned by the system. @@ -373,7 +373,7 @@ items: returned: success type: complex contains: - automatic_restart: + automaticRestart: description: - Specifies whether the instance should be automatically restarted if it is terminated by Compute Engine (not terminated by a user). @@ -381,7 +381,7 @@ items: instances cannot be automatically restarted. returned: success type: bool - on_host_maintenance: + onHostMaintenance: description: - Defines the maintenance behavior for this instance. For standard instances, the default behavior is MIGRATE. For preemptible instances, the default and only possible @@ -395,7 +395,7 @@ items: creation, it cannot be set or changed after the instance has been created. returned: success type: bool - service_accounts: + serviceAccounts: description: - A list of service accounts, with their specified scopes, authorized for this instance. Only one service account per VM instance is supported. @@ -451,7 +451,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_network.py b/lib/ansible/modules/cloud/google/gcp_compute_network.py index f5cda926068..4eccbc5fc1f 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_network.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_network.py @@ -62,12 +62,6 @@ options: - An optional description of this resource. Provide this property when you create the resource. required: false - gateway_ipv4: - description: - - A gateway address for default routing to other networks. This value is read only - and is selected by the Google Compute Engine, typically as the first usable address - in the IPv4Range. - required: false ipv4_range: description: - 'The range of internal addresses that are legal on this network. This range is a @@ -82,7 +76,7 @@ options: which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash. - required: false + required: true auto_create_subnetworks: description: - When set to true, the network is created in "auto subnet mode". When set to false, @@ -91,7 +85,25 @@ options: and it automatically creates one subnetwork per region. required: false type: bool + routing_config: + description: + - The network-level routing configuration for this network. Used by Cloud Router to + determine what type of network-wide routing behavior to enforce. + required: false + version_added: 2.8 + suboptions: + routing_mode: + description: + - The network-wide routing mode to use. If set to REGIONAL, this network's cloud routers + will only advertise routes with subnetworks of this network in the same region as + the router. If set to GLOBAL, this network's cloud routers will advertise routes + with all subnetworks of this network, across regions. + required: true + choices: ['REGIONAL', 'GLOBAL'] extends_documentation_fragment: gcp +notes: + - "API Reference: U(https://cloud.google.com/compute/docs/reference/rest/v1/networks)" + - "Official Documentation: U(https://cloud.google.com/vpc/docs/vpc)" ''' EXAMPLES = ''' @@ -100,7 +112,7 @@ EXAMPLES = ''' name: "test_object" auto_create_subnetworks: true project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -146,7 +158,7 @@ RETURN = ''' - Server-defined fully-qualified URLs for all subnetworks in this network. returned: success type: list - auto_create_subnetworks: + autoCreateSubnetworks: description: - When set to true, the network is created in "auto subnet mode". When set to false, the network is in "custom subnet mode". @@ -154,18 +166,33 @@ RETURN = ''' and it automatically creates one subnetwork per region. returned: success type: bool - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success type: str + routingConfig: + description: + - The network-level routing configuration for this network. Used by Cloud Router to + determine what type of network-wide routing behavior to enforce. + returned: success + type: complex + contains: + routingMode: + description: + - The network-wide routing mode to use. If set to REGIONAL, this network's cloud routers + will only advertise routes with subnetworks of this network in the same region as + the router. If set to GLOBAL, this network's cloud routers will advertise routes + with all subnetworks of this network, across regions. + returned: success + type: str ''' ################################################################################ # Imports ################################################################################ -from ansible.module_utils.gcp_utils import navigate_hash, GcpSession, GcpModule, GcpRequest, replace_resource_dict +from ansible.module_utils.gcp_utils import navigate_hash, GcpSession, GcpModule, GcpRequest, remove_nones_from_dict, replace_resource_dict import json import time @@ -181,10 +208,12 @@ def main(): argument_spec=dict( state=dict(default='present', choices=['present', 'absent'], type='str'), description=dict(type='str'), - gateway_ipv4=dict(type='str'), ipv4_range=dict(type='str'), - name=dict(type='str'), - auto_create_subnetworks=dict(type='bool') + name=dict(required=True, type='str'), + auto_create_subnetworks=dict(type='bool'), + routing_config=dict(type='list', elements='dict', options=dict( + routing_mode=dict(required=True, type='str', choices=['REGIONAL', 'GLOBAL']) + )) ) ) @@ -200,7 +229,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind, fetch) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -223,9 +253,29 @@ def create(module, link, kind): return wait_for_operation(module, auth.post(link, resource_to_request(module))) -def update(module, link, kind): +def update(module, link, kind, fetch): + update_fields(module, resource_to_request(module), + response_to_hash(module, fetch)) auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + return wait_for_operation(module, auth.patch(link, resource_to_request(module))) + + +def update_fields(module, request, response): + if response.get('routingConfig') != request.get('routingConfig'): + routing_config_update(module, request, response) + + +def routing_config_update(module, request, response): + auth = GcpSession(module, 'compute') + auth.patch( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/regions/{region}/subnetworks/{name}" + ]).format(**module.params), + { + u'routingConfig': NetworkRoutingConfigArray(module.params.get('routing_config', []), module).to_request() + } + ) def delete(module, link, kind): @@ -237,10 +287,10 @@ def resource_to_request(module): request = { u'kind': 'compute#network', u'description': module.params.get('description'), - u'gatewayIPv4': module.params.get('gateway_ipv4'), u'IPv4Range': module.params.get('ipv4_range'), u'name': module.params.get('name'), - u'autoCreateSubnetworks': module.params.get('auto_create_subnetworks') + u'autoCreateSubnetworks': module.params.get('auto_create_subnetworks'), + u'routingConfig': NetworkRoutingConfigArray(module.params.get('routing_config', []), module).to_request() } return_vals = {} for k, v in request.items(): @@ -250,9 +300,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -263,9 +313,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/networks".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -280,8 +330,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result @@ -308,14 +356,15 @@ def is_different(module, response): # This is for doing comparisons with Ansible's current parameters. def response_to_hash(module, response): return { - u'description': response.get(u'description'), + u'description': module.params.get('description'), u'gatewayIPv4': response.get(u'gateway_ipv4'), u'id': response.get(u'id'), - u'IPv4Range': response.get(u'ipv4_range'), - u'name': response.get(u'name'), + u'IPv4Range': module.params.get('ipv4_range'), + u'name': module.params.get('name'), u'subnetworks': response.get(u'subnetworks'), - u'autoCreateSubnetworks': response.get(u'autoCreateSubnetworks'), - u'creationTimestamp': response.get(u'creationTimestamp') + u'autoCreateSubnetworks': module.params.get('auto_create_subnetworks'), + u'creationTimestamp': response.get(u'creationTimestamp'), + u'routingConfig': NetworkRoutingConfigArray(response.get(u'routingConfig', []), module).from_response() } @@ -356,5 +405,36 @@ def raise_if_errors(response, err_path, module): module.fail_json(msg=errors) +class NetworkRoutingConfigArray(object): + def __init__(self, request, module): + self.module = module + if request: + self.request = request + else: + self.request = [] + + def to_request(self): + items = [] + for item in self.request: + items.append(self._request_for_item(item)) + return items + + def from_response(self): + items = [] + for item in self.request: + items.append(self._response_from_item(item)) + return items + + def _request_for_item(self, item): + return remove_nones_from_dict({ + u'routingMode': item.get('routing_mode') + }) + + def _response_from_item(self, item): + return remove_nones_from_dict({ + u'routingMode': item.get(u'routingMode') + }) + + if __name__ == '__main__': main() diff --git a/lib/ansible/modules/cloud/google/gcp_compute_network_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_network_facts.py index 22a32ffeb01..f7c87a316c6 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_network_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_network_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -106,7 +106,7 @@ items: - Server-defined fully-qualified URLs for all subnetworks in this network. returned: success type: list - auto_create_subnetworks: + autoCreateSubnetworks: description: - When set to true, the network is created in "auto subnet mode". When set to false, the network is in "custom subnet mode". @@ -114,11 +114,26 @@ items: and it automatically creates one subnetwork per region. returned: success type: bool - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success type: str + routingConfig: + description: + - The network-level routing configuration for this network. Used by Cloud Router to + determine what type of network-wide routing behavior to enforce. + returned: success + type: complex + contains: + routingMode: + description: + - The network-wide routing mode to use. If set to REGIONAL, this network's cloud routers + will only advertise routes with subnetworks of this network in the same region as + the router. If set to GLOBAL, this network's cloud routers will advertise routes + with all subnetworks of this network, across regions. + returned: success + type: str ''' ################################################################################ @@ -135,7 +150,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_route.py b/lib/ansible/modules/cloud/google/gcp_compute_route.py index 1a61e098b6b..bbda85c765e 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_route.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_route.py @@ -84,6 +84,11 @@ options: network: description: - The network that this route applies to. + - 'This field represents a link to a Network resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_network task + and then set this network field to "{{ name-of-resource }}" Alternatively, you can + set this network to a dictionary with the selfLink key where the value is the selfLink + of your Network.' required: true priority: description: @@ -146,13 +151,13 @@ EXAMPLES = ''' - backends - databases project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - dest_range: + destRange: description: - The destination range of outgoing packets that this route applies to. - Only IPv4 is supported. @@ -193,7 +198,7 @@ RETURN = ''' - A list of instance tags to which this route applies. returned: success type: list - next_hop_gateway: + nextHopGateway: description: - URL to a gateway that should handle matching packets. - 'Currently, you can only specify the internet gateway, using a full or partial valid @@ -202,7 +207,7 @@ RETURN = ''' .' returned: success type: str - next_hop_instance: + nextHopInstance: description: - URL to an instance that should handle matching packets. - 'You can specify this as a full or partial URL. For example: * U(https://www.googleapis.com/compute/v1/projects/project/zones/zone/) @@ -210,17 +215,17 @@ RETURN = ''' .' returned: success type: str - next_hop_ip: + nextHopIp: description: - Network IP address of an instance that should handle matching packets. returned: success type: str - next_hop_vpn_tunnel: + nextHopVpnTunnel: description: - URL to a VpnTunnel that should handle matching packets. returned: success type: str - next_hop_network: + nextHopNetwork: description: - URL to a Network that should handle matching packets. returned: success @@ -271,7 +276,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -295,8 +301,7 @@ def create(module, link, kind): def update(module, link, kind): - auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + module.fail_json(msg="Route cannot be edited") def delete(module, link, kind): @@ -326,9 +331,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -339,9 +344,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/routes".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -356,8 +361,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_route_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_route_facts.py index e9b1a43856b..312d9af00cf 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_route_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_route_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,7 +66,7 @@ items: returned: always type: complex contains: - dest_range: + destRange: description: - The destination range of outgoing packets that this route applies to. - Only IPv4 is supported. @@ -107,7 +107,7 @@ items: - A list of instance tags to which this route applies. returned: success type: list - next_hop_gateway: + nextHopGateway: description: - URL to a gateway that should handle matching packets. - 'Currently, you can only specify the internet gateway, using a full or partial valid @@ -116,7 +116,7 @@ items: .' returned: success type: str - next_hop_instance: + nextHopInstance: description: - URL to an instance that should handle matching packets. - 'You can specify this as a full or partial URL. For example: * U(https://www.googleapis.com/compute/v1/projects/project/zones/zone/) @@ -124,17 +124,17 @@ items: .' returned: success type: str - next_hop_ip: + nextHopIp: description: - Network IP address of an instance that should handle matching packets. returned: success type: str - next_hop_vpn_tunnel: + nextHopVpnTunnel: description: - URL to a VpnTunnel that should handle matching packets. returned: success type: str - next_hop_network: + nextHopNetwork: description: - URL to a Network that should handle matching packets. returned: success @@ -155,7 +155,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_router.py b/lib/ansible/modules/cloud/google/gcp_compute_router.py index e35f67f5cfb..40d88a7d9e9 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_router.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_router.py @@ -61,6 +61,11 @@ options: network: description: - A reference to the network to which this router belongs. + - 'This field represents a link to a Network resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_network task + and then set this network field to "{{ name-of-resource }}" Alternatively, you can + set this network to a dictionary with the selfLink key where the value is the selfLink + of your Network.' required: true bgp: description: @@ -138,7 +143,7 @@ EXAMPLES = ''' - range: 6.7.0.0/16 region: us-central1 project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -149,7 +154,7 @@ RETURN = ''' - The unique identifier for the resource. returned: success type: int - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -186,13 +191,13 @@ RETURN = ''' that link to this router will have the same local ASN. returned: success type: int - advertise_mode: + advertiseMode: description: - User-specified flag to indicate which mode to use for advertisement. - 'Valid values of this enum field are: DEFAULT, CUSTOM .' returned: success type: str - advertised_groups: + advertisedGroups: description: - User-specified list of prefix groups to advertise in custom mode. - This field can only be populated if advertiseMode is CUSTOM and is advertised to @@ -201,7 +206,7 @@ RETURN = ''' - 'This enum field has the one valid value: ALL_SUBNETS .' returned: success type: list - advertised_ip_ranges: + advertisedIpRanges: description: - User-specified list of individual IP ranges to advertise in custom mode. This field can only be populated if advertiseMode is CUSTOM and is advertised to all peers @@ -274,7 +279,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -324,9 +330,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -337,9 +343,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/regions/{region}/routers".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -354,8 +360,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_router_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_router_facts.py index 81d7f1fb230..ae9057d727f 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_router_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_router_facts.py @@ -61,7 +61,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -76,7 +76,7 @@ items: - The unique identifier for the resource. returned: success type: int - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -113,13 +113,13 @@ items: that link to this router will have the same local ASN. returned: success type: int - advertise_mode: + advertiseMode: description: - User-specified flag to indicate which mode to use for advertisement. - 'Valid values of this enum field are: DEFAULT, CUSTOM .' returned: success type: str - advertised_groups: + advertisedGroups: description: - User-specified list of prefix groups to advertise in custom mode. - This field can only be populated if advertiseMode is CUSTOM and is advertised to @@ -128,7 +128,7 @@ items: - 'This enum field has the one valid value: ALL_SUBNETS .' returned: success type: list - advertised_ip_ranges: + advertisedIpRanges: description: - User-specified list of individual IP ranges to advertise in custom mode. This field can only be populated if advertiseMode is CUSTOM and is advertised to all peers diff --git a/lib/ansible/modules/cloud/google/gcp_compute_ssl_certificate.py b/lib/ansible/modules/cloud/google/gcp_compute_ssl_certificate.py index 2f64ca61670..5fdd494b04b 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_ssl_certificate.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_ssl_certificate.py @@ -32,8 +32,9 @@ DOCUMENTATION = ''' --- module: gcp_compute_ssl_certificate description: - - An SslCertificate resource. This resource provides a mechanism to upload an SSL - key and certificate to the load balancer to serve secure connections from the user. + - An SslCertificate resource, used for HTTPS load balancing. This resource provides + a mechanism to upload an SSL key and certificate to the load balancer to serve secure + connections from the user. short_description: Creates a GCP SslCertificate version_added: 2.6 author: Google Inc. (@googlecloudplatform) @@ -52,7 +53,7 @@ options: - The certificate in PEM format. - The certificate chain must be no greater than 5 certs long. - The chain must include at least one intermediate cert. - required: false + required: true description: description: - An optional description of this resource. @@ -68,9 +69,12 @@ options: required: false private_key: description: - - The private key in PEM format. - required: false + - The write-only private key in PEM format. + required: true extends_documentation_fragment: gcp +notes: + - "API Reference: U(https://cloud.google.com/compute/docs/reference/rest/v1/sslCertificates)" + - "Official Documentation: U(https://cloud.google.com/load-balancing/docs/ssl-certificates)" ''' EXAMPLES = ''' @@ -103,7 +107,7 @@ EXAMPLES = ''' OGN02HtkpBOZzzvUARTR10JQoSe2/5PIwQ== -----END EC PRIVATE KEY----- project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -116,7 +120,7 @@ RETURN = ''' - The chain must include at least one intermediate cert. returned: success type: str - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -141,9 +145,9 @@ RETURN = ''' be a dash. returned: success type: str - private_key: + privateKey: description: - - The private key in PEM format. + - The write-only private key in PEM format. returned: success type: str ''' @@ -167,10 +171,10 @@ def main(): module = GcpModule( argument_spec=dict( state=dict(default='present', choices=['present', 'absent'], type='str'), - certificate=dict(type='str'), + certificate=dict(required=True, type='str'), description=dict(type='str'), name=dict(type='str'), - private_key=dict(type='str') + private_key=dict(required=True, type='str') ) ) @@ -186,7 +190,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -210,8 +215,7 @@ def create(module, link, kind): def update(module, link, kind): - auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + module.fail_json(msg="SslCertificate cannot be edited") def delete(module, link, kind): @@ -235,9 +239,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -248,9 +252,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/sslCertificates".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -265,8 +269,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_ssl_certificate_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_ssl_certificate_facts.py index 10dcc20f090..4fc37c9483f 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_ssl_certificate_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_ssl_certificate_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -73,7 +73,7 @@ items: - The chain must include at least one intermediate cert. returned: success type: str - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -98,9 +98,9 @@ items: be a dash. returned: success type: str - private_key: + privateKey: description: - - The private key in PEM format. + - The write-only private key in PEM format. returned: success type: str ''' @@ -119,7 +119,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_ssl_policy.py b/lib/ansible/modules/cloud/google/gcp_compute_ssl_policy.py index bb7ab24d734..003eaf34eb0 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_ssl_policy.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_ssl_policy.py @@ -96,13 +96,13 @@ EXAMPLES = ''' - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -135,18 +135,18 @@ RETURN = ''' in the `customFeatures` field. returned: success type: str - min_tls_version: + minTlsVersion: description: - The minimum version of SSL protocol that can be used by the clients to establish a connection with the load balancer. This can be one of `TLS_1_0`, `TLS_1_1`, `TLS_1_2`. returned: success type: str - enabled_features: + enabledFeatures: description: - The list of features enabled in the SSL policy. returned: success type: list - custom_features: + customFeatures: description: - A list of features enabled when the selected profile is CUSTOM. The method returns the set of features that can be specified in this list. This field must be empty @@ -217,7 +217,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -267,9 +268,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -280,9 +281,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/sslPolicies".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -297,8 +298,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_ssl_policy_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_ssl_policy_facts.py index 3b638b9c667..80de3606b99 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_ssl_policy_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_ssl_policy_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,7 +66,7 @@ items: returned: always type: complex contains: - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -99,18 +99,18 @@ items: in the `customFeatures` field. returned: success type: str - min_tls_version: + minTlsVersion: description: - The minimum version of SSL protocol that can be used by the clients to establish a connection with the load balancer. This can be one of `TLS_1_0`, `TLS_1_1`, `TLS_1_2`. returned: success type: str - enabled_features: + enabledFeatures: description: - The list of features enabled in the SSL policy. returned: success type: list - custom_features: + customFeatures: description: - A list of features enabled when the selected profile is CUSTOM. The method returns the set of features that can be specified in this list. This field must be empty @@ -156,7 +156,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_subnetwork.py b/lib/ansible/modules/cloud/google/gcp_compute_subnetwork.py index e2cca992391..9348fb62938 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_subnetwork.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_subnetwork.py @@ -87,7 +87,39 @@ options: description: - The network this subnet belongs to. - Only networks that are in the distributed mode can have subnetworks. + - 'This field represents a link to a Network resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_network task + and then set this network field to "{{ name-of-resource }}" Alternatively, you can + set this network to a dictionary with the selfLink key where the value is the selfLink + of your Network.' required: true + enable_flow_logs: + description: + - Whether to enable flow logging for this subnetwork. + required: false + type: bool + version_added: 2.8 + secondary_ip_ranges: + description: + - An array of configurations for secondary IP ranges for VM instances contained in + this subnetwork. The primary IP of such VM must belong to the primary ipCidrRange + of the subnetwork. The alias IPs may belong to either primary or secondary ranges. + required: false + version_added: 2.8 + suboptions: + range_name: + description: + - The name associated with this subnetwork secondary range, used when adding an alias + IP range to a VM instance. The name must be 1-63 characters long, and comply with + RFC1035. The name must be unique within the subnetwork. + required: true + ip_cidr_range: + description: + - The range of IP addresses belonging to this subnetwork secondary range. Provide + this property when you create the subnetwork. + - Ranges must be unique and non-overlapping with all primary and secondary IP ranges + within a network. Only IPv4 is supported. + required: true private_ip_google_access: description: - Whether the VMs in this subnet can access Google services without assigned external @@ -123,13 +155,13 @@ EXAMPLES = ''' network: "{{ network }}" ip_cidr_range: 172.16.0.0/16 project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -140,7 +172,7 @@ RETURN = ''' the resource. This field can be set only at resource creation time. returned: success type: str - gateway_address: + gatewayAddress: description: - The gateway address for default routes to reach destination addresses outside this subnetwork. @@ -151,7 +183,7 @@ RETURN = ''' - The unique identifier for the resource. returned: success type: int - ip_cidr_range: + ipCidrRange: description: - The range of internal addresses that are owned by this subnetwork. - Provide this property when you create the subnetwork. For example, 10.0.0.0/8 or @@ -175,7 +207,41 @@ RETURN = ''' - Only networks that are in the distributed mode can have subnetworks. returned: success type: dict - private_ip_google_access: + enableFlowLogs: + description: + - Whether to enable flow logging for this subnetwork. + returned: success + type: bool + fingerprint: + description: + - Fingerprint of this resource. This field is used internally during updates of this + resource. + returned: success + type: str + secondaryIpRanges: + description: + - An array of configurations for secondary IP ranges for VM instances contained in + this subnetwork. The primary IP of such VM must belong to the primary ipCidrRange + of the subnetwork. The alias IPs may belong to either primary or secondary ranges. + returned: success + type: complex + contains: + rangeName: + description: + - The name associated with this subnetwork secondary range, used when adding an alias + IP range to a VM instance. The name must be 1-63 characters long, and comply with + RFC1035. The name must be unique within the subnetwork. + returned: success + type: str + ipCidrRange: + description: + - The range of IP addresses belonging to this subnetwork secondary range. Provide + this property when you create the subnetwork. + - Ranges must be unique and non-overlapping with all primary and secondary IP ranges + within a network. Only IPv4 is supported. + returned: success + type: str + privateIpGoogleAccess: description: - Whether the VMs in this subnet can access Google services without assigned external IP addresses. @@ -192,7 +258,7 @@ RETURN = ''' # Imports ################################################################################ -from ansible.module_utils.gcp_utils import navigate_hash, GcpSession, GcpModule, GcpRequest, replace_resource_dict +from ansible.module_utils.gcp_utils import navigate_hash, GcpSession, GcpModule, GcpRequest, remove_nones_from_dict, replace_resource_dict import json import time @@ -211,6 +277,11 @@ def main(): ip_cidr_range=dict(required=True, type='str'), name=dict(required=True, type='str'), network=dict(required=True, type='dict'), + enable_flow_logs=dict(type='bool'), + secondary_ip_ranges=dict(type='list', elements='dict', options=dict( + range_name=dict(required=True, type='str'), + ip_cidr_range=dict(required=True, type='str') + )), private_ip_google_access=dict(type='bool'), region=dict(required=True, type='str') ) @@ -228,7 +299,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind, fetch) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -251,9 +323,60 @@ def create(module, link, kind): return wait_for_operation(module, auth.post(link, resource_to_request(module))) -def update(module, link, kind): +def update(module, link, kind, fetch): + update_fields(module, resource_to_request(module), + response_to_hash(module, fetch)) + return fetch_resource(module, self_link(module), kind) + + +def update_fields(module, request, response): + if response.get('ipCidrRange') != request.get('ipCidrRange'): + ip_cidr_range_update(module, request, response) + if response.get('enableFlowLogs') != request.get('enableFlowLogs') or response.get('secondaryIpRanges') != request.get('secondaryIpRanges'): + enable_flow_logs_update(module, request, response) + if response.get('privateIpGoogleAccess') != request.get('privateIpGoogleAccess'): + private_ip_google_access_update(module, request, response) + + +def ip_cidr_range_update(module, request, response): + auth = GcpSession(module, 'compute') + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/regions/{region}/subnetworks/{name}/expandIpCidrRange" + ]).format(**module.params), + { + u'ipCidrRange': module.params.get('ip_cidr_range') + } + ) + + +def enable_flow_logs_update(module, request, response): auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + auth.patch( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/regions/{region}/subnetworks/{name}" + ]).format(**module.params), + { + u'enableFlowLogs': module.params.get('enable_flow_logs'), + u'fingerprint': response.get('fingerprint'), + u'secondaryIpRanges': SubnetworkSecondaryIpRangesArray(module.params.get('secondary_ip_ranges', []), module).to_request() + } + ) + + +def private_ip_google_access_update(module, request, response): + auth = GcpSession(module, 'compute') + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/regions/{region}/subnetworks/{name}/setPrivateIpGoogleAccess" + ]).format(**module.params), + { + u'privateIpGoogleAccess': module.params.get('private_ip_google_access') + } + ) def delete(module, link, kind): @@ -268,6 +391,8 @@ def resource_to_request(module): u'ipCidrRange': module.params.get('ip_cidr_range'), u'name': module.params.get('name'), u'network': replace_resource_dict(module.params.get(u'network', {}), 'selfLink'), + u'enableFlowLogs': module.params.get('enable_flow_logs'), + u'secondaryIpRanges': SubnetworkSecondaryIpRangesArray(module.params.get('secondary_ip_ranges', []), module).to_request(), u'privateIpGoogleAccess': module.params.get('private_ip_google_access'), u'region': module.params.get('region') } @@ -279,9 +404,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -292,9 +417,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/regions/{region}/subnetworks".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -309,8 +434,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result @@ -344,6 +467,9 @@ def response_to_hash(module, response): u'ipCidrRange': response.get(u'ipCidrRange'), u'name': response.get(u'name'), u'network': replace_resource_dict(module.params.get(u'network', {}), 'selfLink'), + u'enableFlowLogs': response.get(u'enableFlowLogs'), + u'fingerprint': response.get(u'fingerprint'), + u'secondaryIpRanges': SubnetworkSecondaryIpRangesArray(response.get(u'secondaryIpRanges', []), module).from_response(), u'privateIpGoogleAccess': response.get(u'privateIpGoogleAccess'), u'region': module.params.get('region') } @@ -386,5 +512,38 @@ def raise_if_errors(response, err_path, module): module.fail_json(msg=errors) +class SubnetworkSecondaryIpRangesArray(object): + def __init__(self, request, module): + self.module = module + if request: + self.request = request + else: + self.request = [] + + def to_request(self): + items = [] + for item in self.request: + items.append(self._request_for_item(item)) + return items + + def from_response(self): + items = [] + for item in self.request: + items.append(self._response_from_item(item)) + return items + + def _request_for_item(self, item): + return remove_nones_from_dict({ + u'rangeName': item.get('range_name'), + u'ipCidrRange': item.get('ip_cidr_range') + }) + + def _response_from_item(self, item): + return remove_nones_from_dict({ + u'rangeName': item.get(u'rangeName'), + u'ipCidrRange': item.get(u'ipCidrRange') + }) + + if __name__ == '__main__': main() diff --git a/lib/ansible/modules/cloud/google/gcp_compute_subnetwork_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_subnetwork_facts.py index aa05e1d4d12..2952d3a1514 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_subnetwork_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_subnetwork_facts.py @@ -61,7 +61,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -71,7 +71,7 @@ items: returned: always type: complex contains: - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -82,7 +82,7 @@ items: the resource. This field can be set only at resource creation time. returned: success type: str - gateway_address: + gatewayAddress: description: - The gateway address for default routes to reach destination addresses outside this subnetwork. @@ -93,7 +93,7 @@ items: - The unique identifier for the resource. returned: success type: int - ip_cidr_range: + ipCidrRange: description: - The range of internal addresses that are owned by this subnetwork. - Provide this property when you create the subnetwork. For example, 10.0.0.0/8 or @@ -117,7 +117,41 @@ items: - Only networks that are in the distributed mode can have subnetworks. returned: success type: dict - private_ip_google_access: + enableFlowLogs: + description: + - Whether to enable flow logging for this subnetwork. + returned: success + type: bool + fingerprint: + description: + - Fingerprint of this resource. This field is used internally during updates of this + resource. + returned: success + type: str + secondaryIpRanges: + description: + - An array of configurations for secondary IP ranges for VM instances contained in + this subnetwork. The primary IP of such VM must belong to the primary ipCidrRange + of the subnetwork. The alias IPs may belong to either primary or secondary ranges. + returned: success + type: complex + contains: + rangeName: + description: + - The name associated with this subnetwork secondary range, used when adding an alias + IP range to a VM instance. The name must be 1-63 characters long, and comply with + RFC1035. The name must be unique within the subnetwork. + returned: success + type: str + ipCidrRange: + description: + - The range of IP addresses belonging to this subnetwork secondary range. Provide + this property when you create the subnetwork. + - Ranges must be unique and non-overlapping with all primary and secondary IP ranges + within a network. Only IPv4 is supported. + returned: success + type: str + privateIpGoogleAccess: description: - Whether the VMs in this subnet can access Google services without assigned external IP addresses. diff --git a/lib/ansible/modules/cloud/google/gcp_compute_target_http_proxy.py b/lib/ansible/modules/cloud/google/gcp_compute_target_http_proxy.py index ae62cd29d38..808d4d3c28b 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_target_http_proxy.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_target_http_proxy.py @@ -63,6 +63,11 @@ options: url_map: description: - A reference to the UrlMap resource that defines the mapping from URL to the BackendService. + - 'This field represents a link to a UrlMap resource in GCP. It can be specified in + two ways. You can add `register: name-of-resource` to a gcp_compute_url_map task + and then set this url_map field to "{{ name-of-resource }}" Alternatively, you can + set this url_map to a dictionary with the selfLink key where the value is the selfLink + of your UrlMap.' required: true extends_documentation_fragment: gcp notes: @@ -123,13 +128,13 @@ EXAMPLES = ''' name: "test_object" url_map: "{{ urlmap }}" project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -154,7 +159,7 @@ RETURN = ''' be a dash. returned: success type: str - url_map: + urlMap: description: - A reference to the UrlMap resource that defines the mapping from URL to the BackendService. returned: success @@ -198,7 +203,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind, fetch) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -221,9 +227,28 @@ def create(module, link, kind): return wait_for_operation(module, auth.post(link, resource_to_request(module))) -def update(module, link, kind): +def update(module, link, kind, fetch): + update_fields(module, resource_to_request(module), + response_to_hash(module, fetch)) + return fetch_resource(module, self_link(module), kind) + + +def update_fields(module, request, response): + if response.get('urlMap') != request.get('urlMap'): + url_map_update(module, request, response) + + +def url_map_update(module, request, response): auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/targetHttpProxies/{name}/setUrlMap" + ]).format(**module.params), + { + u'urlMap': replace_resource_dict(module.params.get(u'url_map', {}), 'selfLink') + } + ) def delete(module, link, kind): @@ -246,9 +271,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -259,9 +284,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/targetHttpProxies".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -276,8 +301,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_target_http_proxy_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_target_http_proxy_facts.py index f5f276e083c..0506a929cac 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_target_http_proxy_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_target_http_proxy_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,7 +66,7 @@ items: returned: always type: complex contains: - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -91,7 +91,7 @@ items: be a dash. returned: success type: str - url_map: + urlMap: description: - A reference to the UrlMap resource that defines the mapping from URL to the BackendService. returned: success @@ -112,7 +112,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_target_https_proxy.py b/lib/ansible/modules/cloud/google/gcp_compute_target_https_proxy.py index c3e4cb23434..fc298bdc89b 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_target_https_proxy.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_target_https_proxy.py @@ -78,6 +78,11 @@ options: url_map: description: - A reference to the UrlMap resource that defines the mapping from URL to the BackendService. + - 'This field represents a link to a UrlMap resource in GCP. It can be specified in + two ways. You can add `register: name-of-resource` to a gcp_compute_url_map task + and then set this url_map field to "{{ name-of-resource }}" Alternatively, you can + set this url_map to a dictionary with the selfLink key where the value is the selfLink + of your UrlMap.' required: true extends_documentation_fragment: gcp notes: @@ -174,13 +179,13 @@ EXAMPLES = ''' - "{{ sslcert }}" url_map: "{{ urlmap }}" project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -205,7 +210,7 @@ RETURN = ''' be a dash. returned: success type: str - quic_override: + quicOverride: description: - Specifies the QUIC override policy for this resource. This determines whether the load balancer will attempt to negotiate QUIC with clients or not. Can specify one @@ -214,13 +219,13 @@ RETURN = ''' to specifying NONE. returned: success type: str - ssl_certificates: + sslCertificates: description: - A list of SslCertificate resources that are used to authenticate connections between users and the load balancer. Currently, exactly one SSL certificate must be specified. returned: success type: list - url_map: + urlMap: description: - A reference to the UrlMap resource that defines the mapping from URL to the BackendService. returned: success @@ -266,7 +271,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind, fetch) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -289,9 +295,58 @@ def create(module, link, kind): return wait_for_operation(module, auth.post(link, resource_to_request(module))) -def update(module, link, kind): +def update(module, link, kind, fetch): + update_fields(module, resource_to_request(module), + response_to_hash(module, fetch)) + return fetch_resource(module, self_link(module), kind) + + +def update_fields(module, request, response): + if response.get('quicOverride') != request.get('quicOverride'): + quic_override_update(module, request, response) + if response.get('sslCertificates') != request.get('sslCertificates'): + ssl_certificates_update(module, request, response) + if response.get('urlMap') != request.get('urlMap'): + url_map_update(module, request, response) + + +def quic_override_update(module, request, response): + auth = GcpSession(module, 'compute') + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/global/targetHttpsProxies/{name}/setQuicOverride" + ]).format(**module.params), + { + u'quicOverride': module.params.get('quic_override') + } + ) + + +def ssl_certificates_update(module, request, response): + auth = GcpSession(module, 'compute') + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/targetHttpsProxies/{name}/setSslCertificates" + ]).format(**module.params), + { + u'sslCertificates': replace_resource_dict(module.params.get('ssl_certificates', []), 'selfLink') + } + ) + + +def url_map_update(module, request, response): auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/targetHttpsProxies/{name}/setUrlMap" + ]).format(**module.params), + { + u'urlMap': replace_resource_dict(module.params.get(u'url_map', {}), 'selfLink') + } + ) def delete(module, link, kind): @@ -316,9 +371,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -329,9 +384,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/targetHttpsProxies".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -346,8 +401,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_target_https_proxy_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_target_https_proxy_facts.py index 2b33efa3d87..1b536b643bb 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_target_https_proxy_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_target_https_proxy_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,7 +66,7 @@ items: returned: always type: complex contains: - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -91,7 +91,7 @@ items: be a dash. returned: success type: str - quic_override: + quicOverride: description: - Specifies the QUIC override policy for this resource. This determines whether the load balancer will attempt to negotiate QUIC with clients or not. Can specify one @@ -100,13 +100,13 @@ items: to specifying NONE. returned: success type: str - ssl_certificates: + sslCertificates: description: - A list of SslCertificate resources that are used to authenticate connections between users and the load balancer. Currently, exactly one SSL certificate must be specified. returned: success type: list - url_map: + urlMap: description: - A reference to the UrlMap resource that defines the mapping from URL to the BackendService. returned: success @@ -127,7 +127,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_target_pool.py b/lib/ansible/modules/cloud/google/gcp_compute_target_pool.py index dac771de36d..b6fab8bb0f2 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_target_pool.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_target_pool.py @@ -59,6 +59,11 @@ options: the backup pool are unhealthy, the traffic will be directed back to the primary pool in the "force" mode, where traffic will be spread to the healthy instances with the best effort, or to all instances when no instance is healthy. + - 'This field represents a link to a TargetPool resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_target_pool + task and then set this backup_pool field to "{{ name-of-resource }}" Alternatively, + you can set this backup_pool to a dictionary with the selfLink key where the value + is the selfLink of your TargetPool.' required: false description: description: @@ -84,6 +89,11 @@ options: - A member instance in this pool is considered healthy if and only if the health checks pass. If not specified it means all member instances will be considered healthy at all times. + - 'This field represents a link to a HttpHealthCheck resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_http_health_check + task and then set this health_check field to "{{ name-of-resource }}" Alternatively, + you can set this health_check to a dictionary with the selfLink key where the value + is the selfLink of your HttpHealthCheck.' required: false instances: description: @@ -125,13 +135,13 @@ EXAMPLES = ''' name: "test_object" region: us-west1 project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - backup_pool: + backupPool: description: - This field is applicable only when the containing target pool is serving a forwarding rule as the primary pool, and its failoverRatio field is properly set to a value @@ -146,7 +156,7 @@ RETURN = ''' with the best effort, or to all instances when no instance is healthy. returned: success type: dict - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -156,7 +166,7 @@ RETURN = ''' - An optional description of this resource. returned: success type: str - failover_ratio: + failoverRatio: description: - This field is applicable only when the containing target pool is serving a forwarding rule as the primary pool (i.e., not as a backup pool to some other target pool). @@ -171,7 +181,7 @@ RETURN = ''' or to all instances when no instance is healthy. returned: success type: str - health_check: + healthCheck: description: - A reference to a HttpHealthCheck resource. - A member instance in this pool is considered healthy if and only if the health checks @@ -200,7 +210,7 @@ RETURN = ''' be a dash. returned: success type: str - session_affinity: + sessionAffinity: description: - 'Session affinity option. Must be one of these values: - NONE: Connections from the same client IP may go to any instance in the pool.' @@ -259,7 +269,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -312,9 +323,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -325,9 +336,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/regions/{region}/targetPools".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -344,8 +355,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_target_pool_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_target_pool_facts.py index b8c65f740d0..a7383290418 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_target_pool_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_target_pool_facts.py @@ -61,7 +61,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -71,7 +71,7 @@ items: returned: always type: complex contains: - backup_pool: + backupPool: description: - This field is applicable only when the containing target pool is serving a forwarding rule as the primary pool, and its failoverRatio field is properly set to a value @@ -86,7 +86,7 @@ items: with the best effort, or to all instances when no instance is healthy. returned: success type: dict - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -96,7 +96,7 @@ items: - An optional description of this resource. returned: success type: str - failover_ratio: + failoverRatio: description: - This field is applicable only when the containing target pool is serving a forwarding rule as the primary pool (i.e., not as a backup pool to some other target pool). @@ -111,7 +111,7 @@ items: or to all instances when no instance is healthy. returned: success type: str - health_check: + healthCheck: description: - A reference to a HttpHealthCheck resource. - A member instance in this pool is considered healthy if and only if the health checks @@ -140,7 +140,7 @@ items: be a dash. returned: success type: str - session_affinity: + sessionAffinity: description: - 'Session affinity option. Must be one of these values: - NONE: Connections from the same client IP may go to any instance in the pool.' diff --git a/lib/ansible/modules/cloud/google/gcp_compute_target_ssl_proxy_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_target_ssl_proxy_facts.py index 8a41381db94..47c4a707ff4 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_target_ssl_proxy_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_target_ssl_proxy_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,7 +66,7 @@ items: returned: always type: complex contains: - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -91,7 +91,7 @@ items: be a dash. returned: success type: str - proxy_header: + proxyHeader: description: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. @@ -102,7 +102,7 @@ items: - A reference to the BackendService resource. returned: success type: dict - ssl_certificates: + sslCertificates: description: - A list of SslCertificate resources that are used to authenticate connections between users and the load balancer. Currently, exactly one SSL certificate must be specified. @@ -124,7 +124,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_target_tcp_proxy.py b/lib/ansible/modules/cloud/google/gcp_compute_target_tcp_proxy.py index f8fea560407..fe805985a9a 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_target_tcp_proxy.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_target_tcp_proxy.py @@ -69,6 +69,11 @@ options: service: description: - A reference to the BackendService resource. + - 'This field represents a link to a BackendService resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_backend_service + task and then set this service field to "{{ name-of-resource }}" Alternatively, + you can set this service to a dictionary with the selfLink key where the value is + the selfLink of your BackendService.' required: true extends_documentation_fragment: gcp notes: @@ -124,13 +129,13 @@ EXAMPLES = ''' proxy_header: PROXY_V1 service: "{{ backendservice }}" project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -155,7 +160,7 @@ RETURN = ''' be a dash. returned: success type: str - proxy_header: + proxyHeader: description: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. @@ -206,7 +211,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind, fetch) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -229,9 +235,43 @@ def create(module, link, kind): return wait_for_operation(module, auth.post(link, resource_to_request(module))) -def update(module, link, kind): +def update(module, link, kind, fetch): + update_fields(module, resource_to_request(module), + response_to_hash(module, fetch)) + return fetch_resource(module, self_link(module), kind) + + +def update_fields(module, request, response): + if response.get('proxyHeader') != request.get('proxyHeader'): + proxy_header_update(module, request, response) + if response.get('service') != request.get('service'): + service_update(module, request, response) + + +def proxy_header_update(module, request, response): auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/global/targetTcpProxies/{name}/setProxyHeader" + ]).format(**module.params), + { + u'proxyHeader': module.params.get('proxy_header') + } + ) + + +def service_update(module, request, response): + auth = GcpSession(module, 'compute') + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/global/targetTcpProxies/{name}/setBackendService" + ]).format(**module.params), + { + u'service': replace_resource_dict(module.params.get(u'service', {}), 'selfLink') + } + ) def delete(module, link, kind): @@ -255,9 +295,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -268,9 +308,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/targetTcpProxies".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -285,8 +325,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_target_tcp_proxy_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_target_tcp_proxy_facts.py index 34e885d4a7e..158198ced71 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_target_tcp_proxy_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_target_tcp_proxy_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,7 +66,7 @@ items: returned: always type: complex contains: - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -91,7 +91,7 @@ items: be a dash. returned: success type: str - proxy_header: + proxyHeader: description: - Specifies the type of proxy header to append before sending data to the backend, either NONE or PROXY_V1. The default is NONE. @@ -118,7 +118,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_target_vpn_gateway.py b/lib/ansible/modules/cloud/google/gcp_compute_target_vpn_gateway.py index a68d32f4462..480c2f02ed4 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_target_vpn_gateway.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_target_vpn_gateway.py @@ -63,6 +63,11 @@ options: network: description: - The network this VPN gateway is accepting traffic for. + - 'This field represents a link to a Network resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_network task + and then set this network field to "{{ name-of-resource }}" Alternatively, you can + set this network to a dictionary with the selfLink key where the value is the selfLink + of your Network.' required: true region: description: @@ -99,13 +104,13 @@ EXAMPLES = ''' region: us-west1 network: "{{ network }}" project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -140,7 +145,7 @@ RETURN = ''' - A list of references to VpnTunnel resources associated to this VPN gateway. returned: success type: list - forwarding_rules: + forwardingRules: description: - A list of references to the ForwardingRule resources associated to this VPN gateway. returned: success @@ -190,7 +195,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -214,8 +220,7 @@ def create(module, link, kind): def update(module, link, kind): - auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + module.fail_json(msg="TargetVpnGateway cannot be edited") def delete(module, link, kind): @@ -238,9 +243,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -251,9 +256,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/regions/{region}/targetVpnGateways".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -268,8 +273,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_compute_target_vpn_gateway_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_target_vpn_gateway_facts.py index 23d1a4b8928..f2d65dca94f 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_target_vpn_gateway_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_target_vpn_gateway_facts.py @@ -61,7 +61,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -71,7 +71,7 @@ items: returned: always type: complex contains: - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -106,7 +106,7 @@ items: - A list of references to VpnTunnel resources associated to this VPN gateway. returned: success type: list - forwarding_rules: + forwardingRules: description: - A list of references to the ForwardingRule resources associated to this VPN gateway. returned: success diff --git a/lib/ansible/modules/cloud/google/gcp_compute_url_map.py b/lib/ansible/modules/cloud/google/gcp_compute_url_map.py index f39d002aee1..60753392d9d 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_url_map.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_url_map.py @@ -50,6 +50,11 @@ options: default_service: description: - A reference to BackendService resource if none of the hostRules match. + - 'This field represents a link to a BackendService resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_backend_service + task and then set this default_service field to "{{ name-of-resource }}" Alternatively, + you can set this default_service to a dictionary with the selfLink key where the + value is the selfLink of your BackendService.' required: true description: description: @@ -71,12 +76,12 @@ options: - The list of host patterns to match. They must be valid hostnames, except * will match any string of ([a-z0-9-.]*). In that case, * must be the first character and must be followed in the pattern by either - or . - required: false + required: true path_matcher: description: - The name of the PathMatcher to use to match the path portion of the URL if the hostRule matches the URL's host portion. - required: false + required: true name: description: - Name of the resource. Provided by the client when the resource is created. The name @@ -85,7 +90,7 @@ options: which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash. - required: false + required: true path_matchers: description: - The list of named PathMatchers to use against the URL. @@ -95,7 +100,12 @@ options: description: - A reference to a BackendService resource. This will be used if none of the pathRules defined by this PathMatcher is matched by the URL's path portion. - required: false + - 'This field represents a link to a BackendService resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_backend_service + task and then set this default_service field to "{{ name-of-resource }}" Alternatively, + you can set this default_service to a dictionary with the selfLink key where the + value is the selfLink of your BackendService.' + required: true description: description: - An optional description of this resource. @@ -103,7 +113,7 @@ options: name: description: - The name to which this PathMatcher is referred by the HostRule. - required: false + required: true path_rules: description: - The list of path rules. @@ -118,7 +128,12 @@ options: service: description: - A reference to the BackendService resource if this rule is matched. - required: false + - 'This field represents a link to a BackendService resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_backend_service + task and then set this service field to "{{ name-of-resource }}" Alternatively, + you can set this service to a dictionary with the selfLink key where the value is + the selfLink of your BackendService.' + required: true tests: description: - The list of expected URL mappings. Request to update this UrlMap will succeed only @@ -132,15 +147,20 @@ options: host: description: - Host portion of the URL. - required: false + required: true path: description: - Path portion of the URL. - required: false + required: true service: description: - A reference to expected BackendService resource the given URL should be mapped to. - required: false + - 'This field represents a link to a BackendService resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_backend_service + task and then set this service field to "{{ name-of-resource }}" Alternatively, + you can set this service to a dictionary with the selfLink key where the value is + the selfLink of your BackendService.' + required: true extends_documentation_fragment: gcp ''' @@ -187,18 +207,18 @@ EXAMPLES = ''' name: "test_object" default_service: "{{ backendservice }}" project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success type: str - default_service: + defaultService: description: - A reference to BackendService resource if none of the hostRules match. returned: success @@ -209,7 +229,7 @@ RETURN = ''' the resource. returned: success type: str - host_rules: + hostRules: description: - The list of HostRules to use against the URL. returned: success @@ -228,7 +248,7 @@ RETURN = ''' must be followed in the pattern by either - or . returned: success type: list - path_matcher: + pathMatcher: description: - The name of the PathMatcher to use to match the path portion of the URL if the hostRule matches the URL's host portion. @@ -239,6 +259,12 @@ RETURN = ''' - The unique identifier for the resource. returned: success type: int + fingerprint: + description: + - Fingerprint of this resource. This field is used internally during updates of this + resource. + returned: success + type: str name: description: - Name of the resource. Provided by the client when the resource is created. The name @@ -249,13 +275,13 @@ RETURN = ''' be a dash. returned: success type: str - path_matchers: + pathMatchers: description: - The list of named PathMatchers to use against the URL. returned: success type: complex contains: - default_service: + defaultService: description: - A reference to a BackendService resource. This will be used if none of the pathRules defined by this PathMatcher is matched by the URL's path portion. @@ -271,7 +297,7 @@ RETURN = ''' - The name to which this PathMatcher is referred by the HostRule. returned: success type: str - path_rules: + pathRules: description: - The list of path rules. returned: success @@ -341,24 +367,24 @@ def main(): description=dict(type='str'), host_rules=dict(type='list', elements='dict', options=dict( description=dict(type='str'), - hosts=dict(type='list', elements='str'), - path_matcher=dict(type='str') + hosts=dict(required=True, type='list', elements='str'), + path_matcher=dict(required=True, type='str') )), - name=dict(type='str'), + name=dict(required=True, type='str'), path_matchers=dict(type='list', elements='dict', options=dict( - default_service=dict(type='dict'), + default_service=dict(required=True, type='dict'), description=dict(type='str'), - name=dict(type='str'), + name=dict(required=True, type='str'), path_rules=dict(type='list', elements='dict', options=dict( paths=dict(type='list', elements='str'), - service=dict(type='dict') + service=dict(required=True, type='dict') )) )), tests=dict(type='list', elements='dict', options=dict( description=dict(type='str'), - host=dict(type='str'), - path=dict(type='str'), - service=dict(type='dict') + host=dict(required=True, type='str'), + path=dict(required=True, type='str'), + service=dict(required=True, type='dict') )) ) ) @@ -375,7 +401,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -426,9 +453,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -439,9 +466,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/global/urlMaps".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -456,8 +483,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result @@ -489,7 +514,8 @@ def response_to_hash(module, response): u'description': response.get(u'description'), u'hostRules': UrlMapHostRulesArray(response.get(u'hostRules', []), module).from_response(), u'id': response.get(u'id'), - u'name': response.get(u'name'), + u'fingerprint': response.get(u'fingerprint'), + u'name': module.params.get('name'), u'pathMatchers': UrlMapPathMatchersArray(response.get(u'pathMatchers', []), module).from_response(), u'tests': UrlMapTestsArray(response.get(u'tests', []), module).from_response() } diff --git a/lib/ansible/modules/cloud/google/gcp_compute_url_map_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_url_map_facts.py index 515e4031c80..55d114e58cc 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_url_map_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_url_map_facts.py @@ -56,7 +56,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -66,12 +66,12 @@ items: returned: always type: complex contains: - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success type: str - default_service: + defaultService: description: - A reference to BackendService resource if none of the hostRules match. returned: success @@ -82,7 +82,7 @@ items: the resource. returned: success type: str - host_rules: + hostRules: description: - The list of HostRules to use against the URL. returned: success @@ -101,7 +101,7 @@ items: must be followed in the pattern by either - or . returned: success type: list - path_matcher: + pathMatcher: description: - The name of the PathMatcher to use to match the path portion of the URL if the hostRule matches the URL's host portion. @@ -112,6 +112,12 @@ items: - The unique identifier for the resource. returned: success type: int + fingerprint: + description: + - Fingerprint of this resource. This field is used internally during updates of this + resource. + returned: success + type: str name: description: - Name of the resource. Provided by the client when the resource is created. The name @@ -122,13 +128,13 @@ items: be a dash. returned: success type: str - path_matchers: + pathMatchers: description: - The list of named PathMatchers to use against the URL. returned: success type: complex contains: - default_service: + defaultService: description: - A reference to a BackendService resource. This will be used if none of the pathRules defined by this PathMatcher is matched by the URL's path portion. @@ -144,7 +150,7 @@ items: - The name to which this PathMatcher is referred by the HostRule. returned: success type: str - path_rules: + pathRules: description: - The list of path rules. returned: success @@ -205,7 +211,7 @@ import json def main(): module = GcpModule( argument_spec=dict( - filters=dict(type='list', elements='str'), + filters=dict(type='list', elements='str') ) ) diff --git a/lib/ansible/modules/cloud/google/gcp_compute_vpn_tunnel.py b/lib/ansible/modules/cloud/google/gcp_compute_vpn_tunnel.py index 0697ab90023..1a5ea665d0e 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_vpn_tunnel.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_vpn_tunnel.py @@ -61,10 +61,20 @@ options: target_vpn_gateway: description: - URL of the Target VPN gateway with which this VPN tunnel is associated. + - 'This field represents a link to a TargetVpnGateway resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_compute_target_vpn_gateway + task and then set this target_vpn_gateway field to "{{ name-of-resource }}" Alternatively, + you can set this target_vpn_gateway to a dictionary with the selfLink key where + the value is the selfLink of your TargetVpnGateway.' required: true router: description: - URL of router resource to be used for dynamic routing. + - 'This field represents a link to a Router resource in GCP. It can be specified in + two ways. You can add `register: name-of-resource` to a gcp_compute_router task + and then set this router field to "{{ name-of-resource }}" Alternatively, you can + set this router to a dictionary with the selfLink key where the value is the selfLink + of your Router.' required: false peer_ip: description: @@ -158,13 +168,13 @@ EXAMPLES = ''' router: "{{ router }}" shared_secret: super secret project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -183,7 +193,7 @@ RETURN = ''' - An optional description of this resource. returned: success type: str - target_vpn_gateway: + targetVpnGateway: description: - URL of the Target VPN gateway with which this VPN tunnel is associated. returned: success @@ -192,30 +202,30 @@ RETURN = ''' description: - URL of router resource to be used for dynamic routing. returned: success - type: str - peer_ip: + type: dict + peerIp: description: - IP address of the peer VPN gateway. Only IPv4 is supported. returned: success type: str - shared_secret: + sharedSecret: description: - Shared secret used to set the secure session between the Cloud VPN gateway and the peer VPN gateway. returned: success type: str - shared_secret_hash: + sharedSecretHash: description: - Hash of the shared secret. returned: success type: str - ike_version: + ikeVersion: description: - IKE protocol version to use when establishing the VPN tunnel with peer VPN gateway. - Acceptable IKE versions are 1 or 2. Default version is 2. returned: success type: int - local_traffic_selector: + localTrafficSelector: description: - Local traffic selector to use when establishing the VPN tunnel with peer VPN gateway. The value should be a CIDR formatted string, for example `192.168.0.0/16`. The ranges @@ -223,7 +233,7 @@ RETURN = ''' - Only IPv4 is supported. returned: success type: list - remote_traffic_selector: + remoteTrafficSelector: description: - Remote traffic selector to use when establishing the VPN tunnel with peer VPN gateway. The value should be a CIDR formatted string, for example `192.168.0.0/16`. The ranges @@ -236,6 +246,12 @@ RETURN = ''' - Labels to apply to this VpnTunnel. returned: success type: dict + labelFingerprint: + description: + - The fingerprint used for optimistic locking of this resource. Used internally during + updates. + returned: success + type: str region: description: - The region where the tunnel is located. @@ -265,7 +281,7 @@ def main(): name=dict(required=True, type='str'), description=dict(type='str'), target_vpn_gateway=dict(required=True, type='dict'), - router=dict(type='str'), + router=dict(type='dict'), peer_ip=dict(required=True, type='str'), shared_secret=dict(required=True, type='str'), ike_version=dict(default=2, type='int'), @@ -288,7 +304,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind, fetch) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -297,6 +314,7 @@ def main(): else: if state == 'present': fetch = create(module, collection(module), kind) + labels_update(module, module.params, fetch) changed = True else: fetch = {} @@ -311,9 +329,29 @@ def create(module, link, kind): return wait_for_operation(module, auth.post(link, resource_to_request(module))) -def update(module, link, kind): +def update(module, link, kind, fetch): + update_fields(module, resource_to_request(module), + response_to_hash(module, fetch)) + return fetch_resource(module, self_link(module), kind) + + +def update_fields(module, request, response): + if response.get('labels') != request.get('labels'): + labels_update(module, request, response) + + +def labels_update(module, request, response): auth = GcpSession(module, 'compute') - return wait_for_operation(module, auth.put(link, resource_to_request(module))) + auth.post( + ''.join([ + "https://www.googleapis.com/compute/v1/", + "projects/{project}/regions/{region}/vpnTunnels/{name}/setLabels" + ]).format(**module.params), + { + u'labels': module.params.get('labels'), + u'labelFingerprint': response.get('labelFingerprint') + } + ) def delete(module, link, kind): @@ -327,7 +365,7 @@ def resource_to_request(module): u'name': module.params.get('name'), u'description': module.params.get('description'), u'targetVpnGateway': replace_resource_dict(module.params.get(u'target_vpn_gateway', {}), 'selfLink'), - u'router': module.params.get('router'), + u'router': replace_resource_dict(module.params.get(u'router', {}), 'selfLink'), u'peerIp': module.params.get('peer_ip'), u'sharedSecret': module.params.get('shared_secret'), u'ikeVersion': module.params.get('ike_version'), @@ -343,9 +381,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'compute') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -356,9 +394,9 @@ def collection(module): return "https://www.googleapis.com/compute/v1/projects/{project}/regions/{region}/vpnTunnels".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -373,8 +411,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result @@ -405,14 +441,15 @@ def response_to_hash(module, response): u'name': response.get(u'name'), u'description': module.params.get('description'), u'targetVpnGateway': replace_resource_dict(module.params.get(u'target_vpn_gateway', {}), 'selfLink'), - u'router': module.params.get('router'), + u'router': replace_resource_dict(module.params.get(u'router', {}), 'selfLink'), u'peerIp': response.get(u'peerIp'), u'sharedSecret': response.get(u'sharedSecret'), u'sharedSecretHash': response.get(u'sharedSecretHash'), u'ikeVersion': response.get(u'ikeVersion'), u'localTrafficSelector': response.get(u'localTrafficSelector'), u'remoteTrafficSelector': response.get(u'remoteTrafficSelector'), - u'labels': response.get(u'labels') + u'labels': response.get(u'labels'), + u'labelFingerprint': response.get(u'labelFingerprint') } diff --git a/lib/ansible/modules/cloud/google/gcp_compute_vpn_tunnel_facts.py b/lib/ansible/modules/cloud/google/gcp_compute_vpn_tunnel_facts.py index 24a0142a24e..f1f479205da 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_vpn_tunnel_facts.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_vpn_tunnel_facts.py @@ -61,7 +61,7 @@ EXAMPLES = ''' filters: - name = test_object project: test_project - auth_kind: service_account + auth_kind: serviceaccount service_account_file: "/tmp/auth.pem" ''' @@ -71,7 +71,7 @@ items: returned: always type: complex contains: - creation_timestamp: + creationTimestamp: description: - Creation timestamp in RFC3339 text format. returned: success @@ -90,7 +90,7 @@ items: - An optional description of this resource. returned: success type: str - target_vpn_gateway: + targetVpnGateway: description: - URL of the Target VPN gateway with which this VPN tunnel is associated. returned: success @@ -99,30 +99,30 @@ items: description: - URL of router resource to be used for dynamic routing. returned: success - type: str - peer_ip: + type: dict + peerIp: description: - IP address of the peer VPN gateway. Only IPv4 is supported. returned: success type: str - shared_secret: + sharedSecret: description: - Shared secret used to set the secure session between the Cloud VPN gateway and the peer VPN gateway. returned: success type: str - shared_secret_hash: + sharedSecretHash: description: - Hash of the shared secret. returned: success type: str - ike_version: + ikeVersion: description: - IKE protocol version to use when establishing the VPN tunnel with peer VPN gateway. - Acceptable IKE versions are 1 or 2. Default version is 2. returned: success type: int - local_traffic_selector: + localTrafficSelector: description: - Local traffic selector to use when establishing the VPN tunnel with peer VPN gateway. The value should be a CIDR formatted string, for example `192.168.0.0/16`. The ranges @@ -130,7 +130,7 @@ items: - Only IPv4 is supported. returned: success type: list - remote_traffic_selector: + remoteTrafficSelector: description: - Remote traffic selector to use when establishing the VPN tunnel with peer VPN gateway. The value should be a CIDR formatted string, for example `192.168.0.0/16`. The ranges @@ -143,6 +143,12 @@ items: - Labels to apply to this VpnTunnel. returned: success type: dict + labelFingerprint: + description: + - The fingerprint used for optimistic locking of this resource. Used internally during + updates. + returned: success + type: str region: description: - The region where the tunnel is located. diff --git a/lib/ansible/modules/cloud/google/gcp_container_cluster.py b/lib/ansible/modules/cloud/google/gcp_container_cluster.py index 1998155c60a..d0677315eb2 100644 --- a/lib/ansible/modules/cloud/google/gcp_container_cluster.py +++ b/lib/ansible/modules/cloud/google/gcp_container_cluster.py @@ -268,7 +268,7 @@ EXAMPLES = ''' disk_size_gb: 500 zone: us-central1-a project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -286,7 +286,7 @@ RETURN = ''' - An optional description of this cluster. returned: success type: str - initial_node_count: + initialNodeCount: description: - The number of nodes to create in this cluster. You must ensure that your Compute Engine resource quota is sufficient for this number of instances. You must also @@ -296,7 +296,7 @@ RETURN = ''' this and a nodePool at the same time. returned: success type: int - node_config: + nodeConfig: description: - Parameters used in creating the cluster's nodes. - For requests, this field should only be used in lieu of a "nodePool" object, since @@ -307,19 +307,19 @@ RETURN = ''' returned: success type: complex contains: - machine_type: + machineType: description: - The name of a Google Compute Engine machine type (e.g. - n1-standard-1). If unspecified, the default machine type is n1-standard-1. returned: success type: str - disk_size_gb: + diskSizeGb: description: - Size of the disk attached to each node, specified in GB. The smallest allowed disk size is 10GB. If unspecified, the default disk size is 100GB. returned: success type: int - oauth_scopes: + oauthScopes: description: - The set of Google API scopes to be made available on all of the node VMs under the "default" service account. @@ -332,7 +332,7 @@ RETURN = ''' enabled, in which case their required scopes will be added. returned: success type: list - service_account: + serviceAccount: description: - The Google Cloud Platform Service Account to be used by the node VMs. If no Service Account is specified, the "default" service account is used. @@ -353,7 +353,7 @@ RETURN = ''' - 'Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }.' returned: success type: dict - image_type: + imageType: description: - The image type to use for this node. Note that for a given image type, the latest version of it will be used. @@ -371,7 +371,7 @@ RETURN = ''' - 'Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }.' returned: success type: dict - local_ssd_count: + localSsdCount: description: - The number of local SSD disks to be attached to the node. - 'The limit for this value is dependant upon the maximum number of disks available @@ -392,7 +392,7 @@ RETURN = ''' for more inforamtion about preemptible VM instances.' returned: success type: bool - master_auth: + masterAuth: description: - The authentication information for accessing the master endpoint. returned: success @@ -409,23 +409,23 @@ RETURN = ''' the master endpoint is open to the Internet, you should create a strong password. returned: success type: str - cluster_ca_certificate: + clusterCaCertificate: description: - Base64-encoded public certificate that is the root of trust for the cluster. returned: success type: str - client_certificate: + clientCertificate: description: - Base64-encoded public certificate used by clients to authenticate to the cluster endpoint. returned: success type: str - client_key: + clientKey: description: - Base64-encoded private key used by clients to authenticate to the cluster endpoint. returned: success type: str - logging_service: + loggingService: description: - 'The logging service the cluster should use to write logs. Currently available options: logging.googleapis.com - the Google Cloud Logging service.' @@ -433,7 +433,7 @@ RETURN = ''' - if left as an empty string,logging.googleapis.com will be used. returned: success type: str - monitoring_service: + monitoringService: description: - The monitoring service the cluster should use to write metrics. - 'Currently available options: monitoring.googleapis.com - the Google Cloud Monitoring @@ -450,20 +450,20 @@ RETURN = ''' resource. returned: success type: str - cluster_ipv4_cidr: + clusterIpv4Cidr: description: - The IP address range of the container pods in this cluster, in CIDR notation (e.g. 10.96.0.0/14). Leave blank to have one automatically chosen or specify a /14 block in 10.0.0.0/8. returned: success type: str - addons_config: + addonsConfig: description: - Configurations for the various addons available to run in the cluster. returned: success type: complex contains: - http_load_balancing: + httpLoadBalancing: description: - Configuration for the HTTP (L7) load balancing controller addon, which makes it easy to set up HTTP load balancers for services in a cluster. @@ -476,7 +476,7 @@ RETURN = ''' it runs a small pod in the cluster that manages the load balancers. returned: success type: bool - horizontal_pod_autoscaling: + horizontalPodAutoscaling: description: - Configuration for the horizontal pod autoscaling feature, which increases or decreases the number of replica pods a replication controller has based on the resource usage @@ -509,48 +509,48 @@ RETURN = ''' the masterAuth property of this resource for username and password information. returned: success type: str - initial_cluster_version: + initialClusterVersion: description: - The software version of the master endpoint and kubelets used in the cluster when it was first created. The version can be upgraded over time. returned: success type: str - current_master_version: + currentMasterVersion: description: - The current software version of the master endpoint. returned: success type: str - current_node_version: + currentNodeVersion: description: - The current version of the node software components. If they are currently at multiple versions because they're in the process of being upgraded, this reflects the minimum version of all nodes. returned: success type: str - create_time: + createTime: description: - The time the cluster was created, in RFC3339 text format. returned: success type: str - node_ipv4_cidr_size: + nodeIpv4CidrSize: description: - The size of the address space on each node for hosting containers. - This is provisioned from within the container_ipv4_cidr range. returned: success type: int - services_ipv4_cidr: + servicesIpv4Cidr: description: - The IP address range of the Kubernetes services in this cluster, in CIDR notation (e.g. 1.2.3.4/29). Service addresses are typically put in the last /16 from the container CIDR. returned: success type: str - current_node_count: + currentNodeCount: description: - The number of nodes currently in the cluster. returned: success type: int - expire_time: + expireTime: description: - The time the cluster will be automatically deleted in RFC3339 text format. returned: success @@ -632,7 +632,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module)) + update(module, self_link(module)) + fetch = fetch_resource(module, self_link(module)) changed = True else: delete(module, self_link(module)) @@ -689,9 +690,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link): +def fetch_resource(module, link, allow_not_found=True): auth = GcpSession(module, 'container') - return return_if_object(module, auth.get(link)) + return return_if_object(module, auth.get(link), allow_not_found) def self_link(module): @@ -702,9 +703,9 @@ def collection(module): return "https://container.googleapis.com/v1/projects/{project}/zones/{zone}/clusters".format(**module.params) -def return_if_object(module, response): +def return_if_object(module, response, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. diff --git a/lib/ansible/modules/cloud/google/gcp_container_node_pool.py b/lib/ansible/modules/cloud/google/gcp_container_node_pool.py index dffe9b32291..68b267a1330 100644 --- a/lib/ansible/modules/cloud/google/gcp_container_node_pool.py +++ b/lib/ansible/modules/cloud/google/gcp_container_node_pool.py @@ -198,6 +198,11 @@ options: cluster: description: - The cluster this node pool belongs to. + - 'This field represents a link to a Cluster resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_container_cluster + task and then set this cluster field to "{{ name-of-resource }}" Alternatively, + you can set this cluster to a dictionary with the name key where the value is the + name of your Cluster.' required: true zone: description: @@ -225,7 +230,7 @@ EXAMPLES = ''' cluster: "{{ cluster }}" zone: us-central1-a project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -242,19 +247,19 @@ RETURN = ''' returned: success type: complex contains: - machine_type: + machineType: description: - The name of a Google Compute Engine machine type (e.g. - n1-standard-1). If unspecified, the default machine type is n1-standard-1. returned: success type: str - disk_size_gb: + diskSizeGb: description: - Size of the disk attached to each node, specified in GB. The smallest allowed disk size is 10GB. If unspecified, the default disk size is 100GB. returned: success type: int - oauth_scopes: + oauthScopes: description: - The set of Google API scopes to be made available on all of the node VMs under the "default" service account. @@ -267,7 +272,7 @@ RETURN = ''' enabled, in which case their required scopes will be added. returned: success type: list - service_account: + serviceAccount: description: - The Google Cloud Platform Service Account to be used by the node VMs. If no Service Account is specified, the "default" service account is used. @@ -288,7 +293,7 @@ RETURN = ''' - 'Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }.' returned: success type: dict - image_type: + imageType: description: - The image type to use for this node. Note that for a given image type, the latest version of it will be used. @@ -306,7 +311,7 @@ RETURN = ''' - 'Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }.' returned: success type: dict - local_ssd_count: + localSsdCount: description: - The number of local SSD disks to be attached to the node. - 'The limit for this value is dependant upon the maximum number of disks available @@ -327,7 +332,7 @@ RETURN = ''' for more inforamtion about preemptible VM instances.' returned: success type: bool - initial_node_count: + initialNodeCount: description: - The initial node count for the pool. You must ensure that your Compute Engine resource quota is sufficient for this number of instances. You must also have available firewall @@ -351,12 +356,12 @@ RETURN = ''' - Is autoscaling enabled for this node pool. returned: success type: bool - min_node_count: + minNodeCount: description: - Minimum number of nodes in the NodePool. Must be >= 1 and <= maxNodeCount. returned: success type: int - max_node_count: + maxNodeCount: description: - Maximum number of nodes in the NodePool. Must be >= minNodeCount. - There has to enough quota to scale up the cluster. @@ -368,27 +373,27 @@ RETURN = ''' returned: success type: complex contains: - auto_upgrade: + autoUpgrade: description: - A flag that specifies whether node auto-upgrade is enabled for the node pool. If enabled, node auto-upgrade helps keep the nodes in your node pool up to date with the latest release version of Kubernetes. returned: success type: bool - auto_repair: + autoRepair: description: - A flag that specifies whether the node auto-repair is enabled for the node pool. If enabled, the nodes in this node pool will be monitored and, if they fail health checks too many times, an automatic repair action will be triggered. returned: success type: bool - upgrade_options: + upgradeOptions: description: - Specifies the Auto Upgrade knobs for the node pool. returned: success type: complex contains: - auto_upgrade_start_time: + autoUpgradeStartTime: description: - This field is set when upgrades are about to commence with the approximate start time for the upgrades, in RFC3339 text format. @@ -474,7 +479,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module)) + update(module, self_link(module)) + fetch = fetch_resource(module, self_link(module)) changed = True else: delete(module, self_link(module)) @@ -524,9 +530,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link): +def fetch_resource(module, link, allow_not_found=True): auth = GcpSession(module, 'container') - return return_if_object(module, auth.get(link)) + return return_if_object(module, auth.get(link), allow_not_found) def self_link(module): @@ -548,9 +554,9 @@ def collection(module): return "https://container.googleapis.com/v1/projects/{project}/zones/{zone}/clusters/{cluster}/nodePools".format(**res) -def return_if_object(module, response): +def return_if_object(module, response, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. diff --git a/lib/ansible/modules/cloud/google/gcp_dns_managed_zone.py b/lib/ansible/modules/cloud/google/gcp_dns_managed_zone.py index 9965a315068..08fdc31eada 100644 --- a/lib/ansible/modules/cloud/google/gcp_dns_managed_zone.py +++ b/lib/ansible/modules/cloud/google/gcp_dns_managed_zone.py @@ -77,7 +77,7 @@ EXAMPLES = ''' dns_name: test.somewild2.example.com. description: test zone project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -89,7 +89,7 @@ RETURN = ''' user's convenience. Has no effect on the managed zone's function. returned: success type: str - dns_name: + dnsName: description: - The DNS name of this managed zone, for instance "example.com.". returned: success @@ -105,20 +105,20 @@ RETURN = ''' - Must be unique within the project. returned: success type: str - name_servers: + nameServers: description: - Delegate your managed_zone to these virtual name servers; defined by the server . returned: success type: list - name_server_set: + nameServerSet: description: - Optionally specifies the NameServerSet for this ManagedZone. A NameServerSet is a set of DNS name servers that all host the same ManagedZones. Most users will leave this field unset. returned: success type: list - creation_time: + creationTime: description: - The time that this resource was created on the server. - This is in RFC3339 text format. @@ -163,7 +163,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -211,9 +212,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'dns') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -224,9 +225,9 @@ def collection(module): return "https://www.googleapis.com/dns/v1/projects/{project}/managedZones".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -241,8 +242,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_dns_resource_record_set.py b/lib/ansible/modules/cloud/google/gcp_dns_resource_record_set.py index 1e4050108c3..2b0491534b5 100644 --- a/lib/ansible/modules/cloud/google/gcp_dns_resource_record_set.py +++ b/lib/ansible/modules/cloud/google/gcp_dns_resource_record_set.py @@ -71,6 +71,11 @@ options: description: - Identifies the managed zone addressed by this request. - Can be the managed zone name or id. + - 'This field represents a link to a ManagedZone resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_dns_managed_zone + task and then set this managed_zone field to "{{ name-of-resource }}" Alternatively, + you can set this managed_zone to a dictionary with the name key where the value + is the name of your ManagedZone.' required: true extends_documentation_fragment: gcp ''' @@ -97,7 +102,7 @@ EXAMPLES = ''' - 10.1.2.3 - 40.5.6.7 project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -174,7 +179,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind, fetch) + update(module, self_link(module), kind, fetch) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind, fetch) @@ -238,9 +244,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'dns') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def fetch_wrapped_resource(module, kind, wrap_kind, wrap_path): @@ -269,17 +275,17 @@ def self_link(module): return "https://www.googleapis.com/dns/v1/projects/{project}/managedZones/{managed_zone}/rrsets?name={name}&type={type}".format(**res) -def collection(module, extra_url=''): +def collection(module): res = { 'project': module.params['project'], 'managed_zone': replace_resource_dict(module.params['managed_zone'], 'name') } - return "https://www.googleapis.com/dns/v1/projects/{project}/managedZones/{managed_zone}/changes".format(**res) + extra_url + return "https://www.googleapis.com/dns/v1/projects/{project}/managedZones/{managed_zone}/changes".format(**res) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -294,8 +300,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result @@ -436,7 +440,7 @@ def wait_for_change_to_complete(change_id, module): def get_change_status(change_id, module): auth = GcpSession(module, 'dns') - link = collection(module, "/%s" % change_id) + link = collection(module) + "/%s" % change_id return return_if_change_object(module, auth.get(link))['status'] diff --git a/lib/ansible/modules/cloud/google/gcp_pubsub_subscription.py b/lib/ansible/modules/cloud/google/gcp_pubsub_subscription.py index 5f7c2807c56..d5eeeee2535 100644 --- a/lib/ansible/modules/cloud/google/gcp_pubsub_subscription.py +++ b/lib/ansible/modules/cloud/google/gcp_pubsub_subscription.py @@ -54,6 +54,11 @@ options: topic: description: - A reference to a Topic resource. + - 'This field represents a link to a Topic resource in GCP. It can be specified in + two ways. You can add `register: name-of-resource` to a gcp_pubsub_topic task and + then set this topic field to "{{ name-of-resource }}" Alternatively, you can set + this topic to a dictionary with the name key where the value is the name of your + Topic.' required: false push_config: description: @@ -105,7 +110,7 @@ EXAMPLES = ''' push_endpoint: https://myapp.graphite.cloudnativeapp.com/webhook/sub1 ack_deadline_seconds: 300 project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -121,7 +126,7 @@ RETURN = ''' - A reference to a Topic resource. returned: success type: dict - push_config: + pushConfig: description: - If push delivery is used with this subscription, this field is used to configure it. An empty pushConfig signifies that the subscriber will pull and ack messages @@ -129,13 +134,13 @@ RETURN = ''' returned: success type: complex contains: - push_endpoint: + pushEndpoint: description: - A URL locating the endpoint to which messages should be pushed. - For example, a Webhook endpoint might use "U(https://example.com/push".) returned: success type: str - ack_deadline_seconds: + ackDeadlineSeconds: description: - This value is the maximum time after a subscriber receives a message before the subscriber should acknowledge the message. After message delivery but before the @@ -193,7 +198,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module)) + update(module, self_link(module)) + fetch = fetch_resource(module, self_link(module)) changed = True else: delete(module, self_link(module)) @@ -241,9 +247,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link): +def fetch_resource(module, link, allow_not_found=True): auth = GcpSession(module, 'pubsub') - return return_if_object(module, auth.get(link)) + return return_if_object(module, auth.get(link), allow_not_found) def self_link(module): @@ -254,9 +260,9 @@ def collection(module): return "https://pubsub.googleapis.com/v1/projects/{project}/subscriptions".format(**module.params) -def return_if_object(module, response): +def return_if_object(module, response, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -319,7 +325,7 @@ def decode_request(response, module): def encode_request(request, module): request['topic'] = '/'.join(['projects', module.params['project'], - 'topics', module.params['topic']]) + 'topics', module.params['topic']['name']]) request['name'] = '/'.join(['projects', module.params['project'], 'subscriptions', module.params['name']]) diff --git a/lib/ansible/modules/cloud/google/gcp_pubsub_topic.py b/lib/ansible/modules/cloud/google/gcp_pubsub_topic.py index 863c9f39f67..1a266d46ebc 100644 --- a/lib/ansible/modules/cloud/google/gcp_pubsub_topic.py +++ b/lib/ansible/modules/cloud/google/gcp_pubsub_topic.py @@ -58,7 +58,7 @@ EXAMPLES = ''' gcp_pubsub_topic: name: test-topic1 project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -104,7 +104,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module)) + update(module, self_link(module)) + fetch = fetch_resource(module, self_link(module)) changed = True else: delete(module, self_link(module)) @@ -150,9 +151,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link): +def fetch_resource(module, link, allow_not_found=True): auth = GcpSession(module, 'pubsub') - return return_if_object(module, auth.get(link)) + return return_if_object(module, auth.get(link), allow_not_found) def self_link(module): @@ -163,9 +164,9 @@ def collection(module): return "https://pubsub.googleapis.com/v1/projects/{project}/topics".format(**module.params) -def return_if_object(module, response): +def return_if_object(module, response, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. diff --git a/lib/ansible/modules/cloud/google/gcp_spanner_database.py b/lib/ansible/modules/cloud/google/gcp_spanner_database.py index 702dddd3558..3b23cef1dc9 100644 --- a/lib/ansible/modules/cloud/google/gcp_spanner_database.py +++ b/lib/ansible/modules/cloud/google/gcp_spanner_database.py @@ -61,6 +61,11 @@ options: instance: description: - The instance to create the database on. + - 'This field represents a link to a Instance resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_spanner_instance + task and then set this instance field to "{{ name-of-resource }}" Alternatively, + you can set this instance to a dictionary with the name key where the value is the + name of your Instance.' required: true extends_documentation_fragment: gcp ''' @@ -85,7 +90,7 @@ EXAMPLES = ''' name: webstore instance: "{{ instance }}" project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -98,7 +103,7 @@ RETURN = ''' The final segment of the name must be between 6 and 30 characters in length. returned: success type: str - extra_statements: + extraStatements: description: - 'An optional list of DDL statements to run inside the newly created database. Statements can create tables, indexes, etc. These statements execute atomically with the creation @@ -147,7 +152,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module)) + update(module, self_link(module)) + fetch = fetch_resource(module, self_link(module)) changed = True else: delete(module, self_link(module)) @@ -194,9 +200,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link): +def fetch_resource(module, link, allow_not_found=True): auth = GcpSession(module, 'spanner') - return return_if_object(module, auth.get(link)) + return return_if_object(module, auth.get(link), allow_not_found) def self_link(module): @@ -216,9 +222,9 @@ def collection(module): return "https://spanner.googleapis.com/v1/projects/{project}/instances/{instance}/databases".format(**res) -def return_if_object(module, response): +def return_if_object(module, response, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. diff --git a/lib/ansible/modules/cloud/google/gcp_spanner_instance.py b/lib/ansible/modules/cloud/google/gcp_spanner_instance.py index bd5dbb3e6d1..dcac37f969c 100644 --- a/lib/ansible/modules/cloud/google/gcp_spanner_instance.py +++ b/lib/ansible/modules/cloud/google/gcp_spanner_instance.py @@ -99,7 +99,7 @@ EXAMPLES = ''' cost_center: ti-1700004 config: regional-us-central1 project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -117,13 +117,13 @@ RETURN = ''' - A reference to the instance configuration. returned: success type: str - display_name: + displayName: description: - The descriptive name for this instance as it appears in UIs. Must be unique per project and between 4 and 30 characters in length. returned: success type: str - node_count: + nodeCount: description: - The number of nodes allocated to this instance. returned: success @@ -189,7 +189,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module)) + update(module, self_link(module)) + fetch = fetch_resource(module, self_link(module)) changed = True else: delete(module, self_link(module)) @@ -238,9 +239,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link): +def fetch_resource(module, link, allow_not_found=True): auth = GcpSession(module, 'spanner') - return return_if_object(module, auth.get(link)) + return return_if_object(module, auth.get(link), allow_not_found) def self_link(module): @@ -251,9 +252,9 @@ def collection(module): return "https://spanner.googleapis.com/v1/projects/{project}/instances".format(**module.params) -def return_if_object(module, response): +def return_if_object(module, response, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. diff --git a/lib/ansible/modules/cloud/google/gcp_sql_database.py b/lib/ansible/modules/cloud/google/gcp_sql_database.py index 574c00112ba..2069aeded45 100644 --- a/lib/ansible/modules/cloud/google/gcp_sql_database.py +++ b/lib/ansible/modules/cloud/google/gcp_sql_database.py @@ -62,6 +62,11 @@ options: instance: description: - The name of the Cloud SQL instance. This does not include the project ID. + - 'This field represents a link to a Instance resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_sql_instance task + and then set this instance field to "{{ name-of-resource }}" Alternatively, you + can set this instance to a dictionary with the name key where the value is the name + of your Instance.' required: true extends_documentation_fragment: gcp ''' @@ -89,7 +94,7 @@ EXAMPLES = ''' charset: utf8 instance: "{{ instance }}" project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -156,7 +161,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -204,9 +210,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'sql') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -226,7 +232,7 @@ def collection(module): return "https://www.googleapis.com/sql/v1beta4/projects/{project}/instances/{instance}/databases".format(**res) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. if response.status_code == 404: return None diff --git a/lib/ansible/modules/cloud/google/gcp_sql_instance.py b/lib/ansible/modules/cloud/google/gcp_sql_instance.py index 373e1bf0edf..e298130935a 100644 --- a/lib/ansible/modules/cloud/google/gcp_sql_instance.py +++ b/lib/ansible/modules/cloud/google/gcp_sql_instance.py @@ -246,6 +246,12 @@ options: instances, this field determines whether the instance is Second Generation (recommended) or First Generation. required: false + settings_version: + description: + - The version of instance settings. This is a required field for update method to + make sure concurrent updates are handled properly. During update, use the most + recent settingsVersion value for this instance and do not try to update this value. + required: false extends_documentation_fragment: gcp ''' @@ -261,25 +267,25 @@ EXAMPLES = ''' tier: db-n1-standard-1 region: us-central1 project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' - backend_type: + backendType: description: - "* FIRST_GEN: First Generation instance. MySQL only." - "* SECOND_GEN: Second Generation instance or PostgreSQL instance." - "* EXTERNAL: A database server that is not managed by Google." returned: success type: str - connection_name: + connectionName: description: - Connection name of the Cloud SQL instance used in connection strings. returned: success type: str - database_version: + databaseVersion: description: - The database engine type and version. For First Generation instances, can be MYSQL_5_5, or MYSQL_5_6. For Second Generation instances, can be MYSQL_5_6 or MYSQL_5_7. Defaults @@ -288,7 +294,7 @@ RETURN = ''' after instance creation.' returned: success type: str - failover_replica: + failoverReplica: description: - The name and status of the failover replica. This property is applicable only to Second Generation instances. @@ -309,7 +315,7 @@ RETURN = ''' property is applicable only to Second Generation instances. returned: success type: str - instance_type: + instanceType: description: - The instance type. This can be one of the following. - "* CLOUD_SQL_INSTANCE: A Cloud SQL instance that is not replicating from a master." @@ -317,18 +323,18 @@ RETURN = ''' - "* READ_REPLICA_INSTANCE: A Cloud SQL instance configured as a read-replica." returned: success type: str - ip_addresses: + ipAddresses: description: - The assigned IP addresses for the instance. returned: success type: complex contains: - ip_address: + ipAddress: description: - The IP address assigned. returned: success type: str - time_to_retire: + timeToRetire: description: - The due time for this IP to be retired in RFC 3339 format, for example 2012-11-15T16:19:00.094Z. This field is only available when the IP is scheduled to be retired. @@ -341,18 +347,18 @@ RETURN = ''' from the instance, if supported. returned: success type: str - ipv6_address: + ipv6Address: description: - The IPv6 address assigned to the instance. This property is applicable only to First Generation instances. returned: success type: str - master_instance_name: + masterInstanceName: description: - The name of the instance which will act as master in the replication setup. returned: success type: str - max_disk_size: + maxDiskSize: description: - The maximum disk size of the instance in bytes. returned: success @@ -368,13 +374,13 @@ RETURN = ''' instance type (First Generation or Second Generation/PostgreSQL). returned: success type: str - replica_configuration: + replicaConfiguration: description: - Configuration specific to failover replicas and read replicas. returned: success type: complex contains: - failover_target: + failoverTarget: description: - Specifies if the replica is the failover target. If the field is set to true the replica will be designated as a failover replica. @@ -384,7 +390,7 @@ RETURN = ''' in different zone with the master instance. returned: success type: bool - mysql_replica_configuration: + mysqlReplicaConfiguration: description: - MySQL specific configuration when replicating from a MySQL on-premises master. Replication configuration information such as the username, password, certificates, and keys @@ -394,28 +400,28 @@ RETURN = ''' returned: success type: complex contains: - ca_certificate: + caCertificate: description: - PEM representation of the trusted CA's x509 certificate. returned: success type: str - client_certificate: + clientCertificate: description: - PEM representation of the slave's x509 certificate . returned: success type: str - client_key: + clientKey: description: - PEM representation of the slave's private key. The corresponsing public key is encoded in the client's asf asd certificate. returned: success type: str - connect_retry_interval: + connectRetryInterval: description: - Seconds to wait between connect retries. MySQL's default is 60 seconds. returned: success type: int - dump_file_path: + dumpFilePath: description: - Path to a SQL dump file in Google Cloud Storage from which the slave instance is to be created. The URI is in the form gs://bucketName/fileName. Compressed gzip @@ -424,7 +430,7 @@ RETURN = ''' when using mysqldump. returned: success type: str - master_heartbeat_period: + masterHeartbeatPeriod: description: - Interval in milliseconds between replication heartbeats. returned: success @@ -434,7 +440,7 @@ RETURN = ''' - The password for the replication connection. returned: success type: str - ssl_cipher: + sslCipher: description: - A list of permissible ciphers to use for SSL encryption. returned: success @@ -444,18 +450,18 @@ RETURN = ''' - The username for the replication connection. returned: success type: str - verify_server_certificate: + verifyServerCertificate: description: - Whether or not to check the master's Common Name value in the certificate that it sends during the SSL handshake. returned: success type: bool - replica_names: + replicaNames: description: - The replicas of the instance. returned: success type: list - service_account_email_address: + serviceAccountEmailAddress: description: - The service account email address assigned to the instance. This property is applicable only to Second Generation instances. @@ -467,7 +473,7 @@ RETURN = ''' returned: success type: complex contains: - ip_configuration: + ipConfiguration: description: - The settings for IP Management. This allows to enable or disable the instance IP and manage which external networks can connect to the instance. The IPv4 address @@ -475,19 +481,19 @@ RETURN = ''' returned: success type: complex contains: - ipv4_enabled: + ipv4Enabled: description: - Whether the instance should be assigned an IP address or not. returned: success type: bool - authorized_networks: + authorizedNetworks: description: - The list of external networks that are allowed to connect to the instance using the IP. In CIDR notation, also known as 'slash' notation (e.g. 192.168.100.0/24). returned: success type: complex contains: - expiration_time: + expirationTime: description: - The time when this access control entry expires in RFC 3339 format, for example 2012-11-15T16:19:00.094Z. @@ -505,7 +511,7 @@ RETURN = ''' or subnet here. returned: success type: str - require_ssl: + requireSsl: description: - Whether the mysqld should default to 'REQUIRE X509' for users connecting over IP. returned: success @@ -517,6 +523,13 @@ RETURN = ''' or First Generation. returned: success type: str + settingsVersion: + description: + - The version of instance settings. This is a required field for update method to + make sure concurrent updates are handled properly. During update, use the most + recent settingsVersion value for this instance and do not try to update this value. + returned: success + type: int ''' ################################################################################ @@ -578,7 +591,8 @@ def main(): )), require_ssl=dict(type='bool') )), - tier=dict(type='str') + tier=dict(type='str'), + settings_version=dict(type='int') )) ) ) @@ -595,7 +609,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind, fetch) + update(module, self_link(module), kind, fetch) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind, fetch) @@ -652,9 +667,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'sql') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -665,7 +680,7 @@ def collection(module): return "https://www.googleapis.com/sql/v1beta4/projects/{project}/instances".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. if response.status_code == 404: return None @@ -895,13 +910,15 @@ class InstanceSettings(object): def to_request(self): return remove_nones_from_dict({ u'ipConfiguration': InstanceIpConfiguration(self.request.get('ip_configuration', {}), self.module).to_request(), - u'tier': self.request.get('tier') + u'tier': self.request.get('tier'), + u'settingsVersion': self.request.get('settings_version') }) def from_response(self): return remove_nones_from_dict({ u'ipConfiguration': InstanceIpConfiguration(self.request.get(u'ipConfiguration', {}), self.module).from_response(), - u'tier': self.request.get(u'tier') + u'tier': self.request.get(u'tier'), + u'settingsVersion': self.request.get(u'settingsVersion') }) diff --git a/lib/ansible/modules/cloud/google/gcp_sql_user.py b/lib/ansible/modules/cloud/google/gcp_sql_user.py index b9137222105..d0ab9e7ac10 100644 --- a/lib/ansible/modules/cloud/google/gcp_sql_user.py +++ b/lib/ansible/modules/cloud/google/gcp_sql_user.py @@ -59,6 +59,11 @@ options: instance: description: - The name of the Cloud SQL instance. This does not include the project ID. + - 'This field represents a link to a Instance resource in GCP. It can be specified + in two ways. You can add `register: name-of-resource` to a gcp_sql_instance task + and then set this instance field to "{{ name-of-resource }}" Alternatively, you + can set this instance to a dictionary with the name key where the value is the name + of your Instance.' required: true password: description: @@ -91,7 +96,7 @@ EXAMPLES = ''' password: secret-password instance: "{{ instance }}" project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -161,7 +166,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -231,9 +237,9 @@ def unwrap_resource(result, module): return None -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'sql') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def fetch_wrapped_resource(module, kind, wrap_kind, wrap_path): @@ -270,7 +276,7 @@ def collection(module): return "https://www.googleapis.com/sql/v1beta4/projects/{project}/instances/{instance}/users".format(**res) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. if response.status_code == 404: return None diff --git a/lib/ansible/modules/cloud/google/gcp_storage_bucket.py b/lib/ansible/modules/cloud/google/gcp_storage_bucket.py index 0f47994284c..644f6a8d023 100644 --- a/lib/ansible/modules/cloud/google/gcp_storage_bucket.py +++ b/lib/ansible/modules/cloud/google/gcp_storage_bucket.py @@ -59,6 +59,11 @@ options: bucket: description: - The name of the bucket. + - 'This field represents a link to a Bucket resource in GCP. It can be specified in + two ways. You can add `register: name-of-resource` to a gcp_storage_bucket task + and then set this bucket field to "{{ name-of-resource }}" Alternatively, you can + set this bucket to a dictionary with the name key where the value is the name of + your Bucket.' required: true domain: description: @@ -139,6 +144,11 @@ options: bucket: description: - The name of the bucket. + - 'This field represents a link to a Bucket resource in GCP. It can be specified in + two ways. You can add `register: name-of-resource` to a gcp_storage_bucket task + and then set this bucket field to "{{ name-of-resource }}" Alternatively, you can + set this bucket to a dictionary with the name key where the value is the name of + your Bucket.' required: true domain: description: @@ -358,7 +368,7 @@ EXAMPLES = ''' gcp_storage_bucket: name: ansible-storage-module project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -396,7 +406,7 @@ RETURN = ''' entity would be domain-example.com. returned: success type: str - entity_id: + entityId: description: - The ID for the entity. returned: success @@ -406,13 +416,13 @@ RETURN = ''' - The ID of the access-control entry. returned: success type: str - project_team: + projectTeam: description: - The project team associated with the entity. returned: success type: complex contains: - project_number: + projectNumber: description: - The project team associated with the entity. returned: success @@ -433,7 +443,7 @@ RETURN = ''' returned: success type: complex contains: - max_age_seconds: + maxAgeSeconds: description: - The value, in seconds, to return in the Access-Control-Max-Age header used in preflight responses. @@ -451,13 +461,13 @@ RETURN = ''' - 'Note: "*" is permitted in the list of origins, and means "any Origin".' returned: success type: list - response_header: + responseHeader: description: - The list of HTTP headers other than the simple response headers to give permission for the user-agent to share across domains. returned: success type: list - default_object_acl: + defaultObjectAcl: description: - Default access controls to apply to new objects when no ACL is provided. returned: success @@ -489,7 +499,7 @@ RETURN = ''' entity would be domain-example.com. returned: success type: str - entity_id: + entityId: description: - The ID for the entity. returned: success @@ -509,13 +519,13 @@ RETURN = ''' - The name of the object, if applied to an object. returned: success type: str - project_team: + projectTeam: description: - The project team associated with the entity. returned: success type: complex contains: - project_number: + projectNumber: description: - The project team associated with the entity. returned: success @@ -555,7 +565,7 @@ RETURN = ''' returned: success type: complex contains: - storage_class: + storageClass: description: - Target storage class. Required iff the type of the action is SetStorageClass. returned: success @@ -571,32 +581,32 @@ RETURN = ''' returned: success type: complex contains: - age_days: + ageDays: description: - Age of an object (in days). This condition is satisfied when an object reaches the specified age. returned: success type: int - created_before: + createdBefore: description: - A date in RFC 3339 format with only the date part (for instance, "2013-01-15"). This condition is satisfied when an object is created before midnight of the specified date in UTC. returned: success type: str - is_live: + isLive: description: - Relevant only for versioned objects. If the value is true, this condition matches live objects; if the value is false, it matches archived objects. returned: success type: bool - matches_storage_class: + matchesStorageClass: description: - Objects having any of the storage classes specified by this condition will be matched. Values include MULTI_REGIONAL, REGIONAL, NEARLINE, COLDLINE, STANDARD, and DURABLE_REDUCED_AVAILABILITY. returned: success type: list - num_newer_versions: + numNewerVersions: description: - Relevant only for versioned objects. If the value is N, this condition is satisfied when there are at least N versions (including the live version) newer than this @@ -617,12 +627,12 @@ RETURN = ''' returned: success type: complex contains: - log_bucket: + logBucket: description: - The destination bucket where the current bucket's logs should be placed. returned: success type: str - log_object_prefix: + logObjectPrefix: description: - A prefix for log object names. returned: success @@ -648,17 +658,17 @@ RETURN = ''' - The entity, in the form project-owner-projectId. returned: success type: str - entity_id: + entityId: description: - The ID for the entity. returned: success type: str - project_number: + projectNumber: description: - The project number of the project the bucket belongs to. returned: success type: int - storage_class: + storageClass: description: - The bucket's default storage class, used whenever no storageClass is specified for a newly-created object. This defines how objects in the bucket are stored and determines @@ -668,7 +678,7 @@ RETURN = ''' For more information, see storage classes. returned: success type: str - time_created: + timeCreated: description: - The creation time of the bucket in RFC 3339 format. returned: success @@ -696,14 +706,14 @@ RETURN = ''' returned: success type: complex contains: - main_page_suffix: + mainPageSuffix: description: - If the requested object path is missing, the service will ensure the path has a trailing '/', append this suffix, and attempt to retrieve the resulting object. This allows the creation of index.html objects to represent directory pages. returned: success type: str - not_found_page: + notFoundPage: description: - If the requested object path is missing, and any mainPageSuffix object is missing, if applicable, the service will return the named object from this bucket as the @@ -715,7 +725,7 @@ RETURN = ''' - A valid API project identifier. returned: success type: str - predefined_default_object_acl: + predefinedDefaultObjectAcl: description: - Apply a predefined set of default object access controls to this bucket. - 'Acceptable values are: - "authenticatedRead": Object owner gets OWNER access, @@ -840,7 +850,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -899,9 +910,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'storage') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -912,9 +923,9 @@ def collection(module): return "https://www.googleapis.com/storage/v1/b?project={project}".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -929,8 +940,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/lib/ansible/modules/cloud/google/gcp_storage_bucket_access_control.py b/lib/ansible/modules/cloud/google/gcp_storage_bucket_access_control.py index 586ba93202f..0f48f4aaf32 100644 --- a/lib/ansible/modules/cloud/google/gcp_storage_bucket_access_control.py +++ b/lib/ansible/modules/cloud/google/gcp_storage_bucket_access_control.py @@ -58,6 +58,11 @@ options: bucket: description: - The name of the bucket. + - 'This field represents a link to a Bucket resource in GCP. It can be specified in + two ways. You can add `register: name-of-resource` to a gcp_storage_bucket task + and then set this bucket field to "{{ name-of-resource }}" Alternatively, you can + set this bucket to a dictionary with the name key where the value is the name of + your Bucket.' required: true entity: description: @@ -111,7 +116,7 @@ EXAMPLES = ''' entity: user-alexstephen@google.com role: WRITER project: "test_project" - auth_kind: "service_account" + auth_kind: "serviceaccount" service_account_file: "/tmp/auth.pem" state: present ''' @@ -143,7 +148,7 @@ RETURN = ''' entity would be domain-example.com. returned: success type: str - entity_id: + entityId: description: - The ID for the entity. returned: success @@ -153,13 +158,13 @@ RETURN = ''' - The ID of the access-control entry. returned: success type: str - project_team: + projectTeam: description: - The project team associated with the entity. returned: success type: complex contains: - project_number: + projectNumber: description: - The project team associated with the entity. returned: success @@ -217,7 +222,8 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind) + update(module, self_link(module), kind) + fetch = fetch_resource(module, self_link(module), kind) changed = True else: delete(module, self_link(module), kind) @@ -267,9 +273,9 @@ def resource_to_request(module): return return_vals -def fetch_resource(module, link, kind): +def fetch_resource(module, link, kind, allow_not_found=True): auth = GcpSession(module, 'storage') - return return_if_object(module, auth.get(link), kind) + return return_if_object(module, auth.get(link), kind, allow_not_found) def self_link(module): @@ -280,9 +286,9 @@ def collection(module): return "https://www.googleapis.com/storage/v1/b/{bucket}/acl".format(**module.params) -def return_if_object(module, response, kind): +def return_if_object(module, response, kind, allow_not_found=False): # If not found, return nothing. - if response.status_code == 404: + if allow_not_found and response.status_code == 404: return None # If no content, return nothing. @@ -297,8 +303,6 @@ def return_if_object(module, response, kind): if navigate_hash(result, ['error', 'errors']): module.fail_json(msg=navigate_hash(result, ['error', 'errors'])) - if result['kind'] != kind: - module.fail_json(msg="Incorrect result: {kind}".format(**result)) return result diff --git a/test/integration/targets/gcp_container_node_pool/tasks/main.yml b/test/integration/targets/gcp_container_node_pool/tasks/main.yml index 0d120990e43..c3dfcbceff1 100644 --- a/test/integration/targets/gcp_container_node_pool/tasks/main.yml +++ b/test/integration/targets/gcp_container_node_pool/tasks/main.yml @@ -53,7 +53,7 @@ gcp_container_node_pool_facts: filters: - name = {{ resource_name }} - cluster: {{ cluster }} + cluster: "{{ cluster }}" zone: us-central1-a project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" @@ -101,7 +101,7 @@ gcp_container_node_pool_facts: filters: - name = {{ resource_name }} - cluster: {{ cluster }} + cluster: "{{ cluster }}" zone: us-central1-a project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" diff --git a/test/integration/targets/gcp_dns_managed_zone/tasks/main.yml b/test/integration/targets/gcp_dns_managed_zone/tasks/main.yml index e4ce07a36c1..ec4e4c250ab 100644 --- a/test/integration/targets/gcp_dns_managed_zone/tasks/main.yml +++ b/test/integration/targets/gcp_dns_managed_zone/tasks/main.yml @@ -39,13 +39,18 @@ - result.changed == true - "result.kind == 'dns#managedZone'" - name: verify that managed_zone was created - shell: | - gcloud dns managed-zones describe --project="{{ gcp_project }}" "{{ resource_name }}" + gcp_dns_managed_zone_facts: + dns_name: test.somewild2.example.com. + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + scopes: + - https://www.googleapis.com/auth/ndev.clouddns.readwrite register: results - name: verify that command succeeded assert: that: - - results.rc == 0 + - results['items'] | length == 1 # ---------------------------------------------------------------------------- - name: create a managed zone that already exists gcp_dns_managed_zone: @@ -79,15 +84,18 @@ - result.changed == true - result.has_key('kind') == False - name: verify that managed_zone was deleted - shell: | - gcloud dns managed-zones describe --project="{{ gcp_project }}" "{{ resource_name }}" + gcp_dns_managed_zone_facts: + dns_name: test.somewild2.example.com. + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + scopes: + - https://www.googleapis.com/auth/ndev.clouddns.readwrite register: results - failed_when: results.rc == 0 - name: verify that command succeeded assert: that: - - results.rc == 1 - - "\"{{ resource_name }} was not found.\" in results.stderr" + - results['items'] | length == 0 # ---------------------------------------------------------------------------- - name: delete a managed zone that does not exist gcp_dns_managed_zone: diff --git a/test/integration/targets/gcp_dns_resource_record_set/tasks/main.yml b/test/integration/targets/gcp_dns_resource_record_set/tasks/main.yml index b60b513c63d..d9866d4f151 100644 --- a/test/integration/targets/gcp_dns_resource_record_set/tasks/main.yml +++ b/test/integration/targets/gcp_dns_resource_record_set/tasks/main.yml @@ -56,6 +56,19 @@ that: - result.changed == true - "result.kind == 'dns#resourceRecordSet'" +- name: verify that resource_record_set was created + gcp_dns_resource_record_set_facts: + managed_zone: "{{ managed_zone }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + scopes: + - https://www.googleapis.com/auth/ndev.clouddns.readwrite + register: results +- name: verify that command succeeded + assert: + that: + - results['items'] | length >= 2 # ---------------------------------------------------------------------------- - name: create a resource record set that already exists gcp_dns_resource_record_set: @@ -96,6 +109,19 @@ that: - result.changed == true - result.has_key('kind') == False +- name: verify that resource_record_set was deleted + gcp_dns_resource_record_set_facts: + managed_zone: "{{ managed_zone }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + scopes: + - https://www.googleapis.com/auth/ndev.clouddns.readwrite + register: results +- name: verify that command succeeded + assert: + that: + - results['items'] | length >= 2 # ---------------------------------------------------------------------------- - name: delete a resource record set that does not exist gcp_dns_resource_record_set: diff --git a/test/integration/targets/gcp_pubsub_subscription/tasks/main.yml b/test/integration/targets/gcp_pubsub_subscription/tasks/main.yml index 548bf63c556..6afc20203e7 100644 --- a/test/integration/targets/gcp_pubsub_subscription/tasks/main.yml +++ b/test/integration/targets/gcp_pubsub_subscription/tasks/main.yml @@ -50,13 +50,17 @@ that: - result.changed == true - name: verify that subscription was created - shell: | - gcloud beta pubsub subscriptions list --project="{{ gcp_project}}" | grep "{{ resource_name }}" | grep 'test-topic1' + gcp_pubsub_subscription_facts: + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + scopes: + - https://www.googleapis.com/auth/pubsub register: results - name: verify that command succeeded assert: that: - - results.rc == 0 + - results['items'] | length == 1 # ---------------------------------------------------------------------------- - name: create a subscription that already exists gcp_pubsub_subscription: @@ -92,15 +96,17 @@ that: - result.changed == true - name: verify that subscription was deleted - shell: | - gcloud beta pubsub subscriptions list --project="{{ gcp_project}}" | grep "{{ resource_name }}" | grep 'test-topic1' + gcp_pubsub_subscription_facts: + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + scopes: + - https://www.googleapis.com/auth/pubsub register: results - failed_when: results.rc == 0 - name: verify that command succeeded assert: that: - - results.rc == 1 - - "\"{{ resource_name }} was not found.\" in results.stderr" + - results['items'] | length == 0 # ---------------------------------------------------------------------------- - name: delete a subscription that does not exist gcp_pubsub_subscription: diff --git a/test/integration/targets/gcp_pubsub_topic/tasks/main.yml b/test/integration/targets/gcp_pubsub_topic/tasks/main.yml index b0c2903a51a..8d1a0635c58 100644 --- a/test/integration/targets/gcp_pubsub_topic/tasks/main.yml +++ b/test/integration/targets/gcp_pubsub_topic/tasks/main.yml @@ -34,13 +34,17 @@ that: - result.changed == true - name: verify that topic was created - shell: | - gcloud beta pubsub topics list --project="{{ gcp_project}}"| grep 'topic: projects/.*/test-topic1' + gcp_pubsub_topic_facts: + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + scopes: + - https://www.googleapis.com/auth/pubsub register: results - name: verify that command succeeded assert: that: - - results.rc == 0 + - results['items'] | length == 1 # ---------------------------------------------------------------------------- - name: create a topic that already exists gcp_pubsub_topic: @@ -68,15 +72,17 @@ that: - result.changed == true - name: verify that topic was deleted - shell: | - gcloud beta pubsub topics list --project="{{ gcp_project}}"| grep 'topic: projects/.*/test-topic1' + gcp_pubsub_topic_facts: + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + scopes: + - https://www.googleapis.com/auth/pubsub register: results - failed_when: results.rc == 0 - name: verify that command succeeded assert: that: - - results.rc == 1 - - "\"{{ resource_name }} was not found.\" in results.stderr" + - results['items'] | length == 0 # ---------------------------------------------------------------------------- - name: delete a topic that does not exist gcp_pubsub_topic: diff --git a/test/integration/targets/gcp_sql_user/tasks/main.yml b/test/integration/targets/gcp_sql_user/tasks/main.yml index 4cc772c7710..a481b394b7a 100644 --- a/test/integration/targets/gcp_sql_user/tasks/main.yml +++ b/test/integration/targets/gcp_sql_user/tasks/main.yml @@ -59,7 +59,7 @@ gcp_sql_user_facts: filters: - name = test-user - instance: {{ instance }} + instance: "{{ instance }}" project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" @@ -108,7 +108,7 @@ gcp_sql_user_facts: filters: - name = test-user - instance: {{ instance }} + instance: "{{ instance }}" project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}"