From 4ecdf11aaaff7309c14248892fd8fd0841c5b58a Mon Sep 17 00:00:00 2001 From: Alex Stephen Date: Mon, 13 Aug 2018 07:50:04 -0700 Subject: [PATCH] Bug fixes for gcp_compute_address (#42704) --- lib/ansible/module_utils/gcp_utils.py | 10 +- .../cloud/google/gcp_compute_address.py | 127 +++++++++++------- .../utils/module_docs_fragments/gcp.py | 8 +- .../gcp_compute_address/tasks/main.yml | 78 +++++------ 4 files changed, 130 insertions(+), 93 deletions(-) diff --git a/lib/ansible/module_utils/gcp_utils.py b/lib/ansible/module_utils/gcp_utils.py index 8bc6d92d52..584a6d14f6 100644 --- a/lib/ansible/module_utils/gcp_utils.py +++ b/lib/ansible/module_utils/gcp_utils.py @@ -95,6 +95,13 @@ class GcpSession(object): except getattr(requests.exceptions, 'RequestException') as inst: self.module.fail_json(msg=inst.message) + def patch(self, url, body=None, **kwargs): + kwargs.update({'json': body, 'headers': self._headers()}) + try: + return self.session().patch(url, **kwargs) + except getattr(requests.exceptions, 'RequestException') as inst: + self.module.fail_json(msg=inst.message) + def session(self): return AuthorizedSession( self._credentials().with_scopes(self.module.params['scopes'])) @@ -106,9 +113,6 @@ class GcpSession(object): if not HAS_GOOGLE_LIBRARIES: self.module.fail_json(msg="Please install the google-auth library") - if 'auth_kind' not in self.module.params: - self.module.fail_json(msg="Auth kind parameter is missing") - if self.module.params.get('service_account_email') is not None and self.module.params['auth_kind'] != 'machineaccount': self.module.fail_json( msg="Service Acccount Email only works with Machine Account-based authentication" diff --git a/lib/ansible/modules/cloud/google/gcp_compute_address.py b/lib/ansible/modules/cloud/google/gcp_compute_address.py index e03e46158d..c7d40c9838 100644 --- a/lib/ansible/modules/cloud/google/gcp_compute_address.py +++ b/lib/ansible/modules/cloud/google/gcp_compute_address.py @@ -33,15 +33,15 @@ DOCUMENTATION = ''' module: gcp_compute_address description: - Represents an Address resource. - - Each virtual machine instance has an ephemeral internal IP address and, - optionally, an external IP address. To communicate between instances on - the same network, you can use an instance's internal IP address. To - communicate with the Internet and instances outside of the same network, - you must specify the instance's external IP address. - - Internal IP addresses are ephemeral and only belong to an instance for the - lifetime of the instance; if the instance is deleted and recreated, the - instance is assigned a new internal IP address, either by Compute Engine - or by you. External IP addresses can be either ephemeral or static. + - Each virtual machine instance has an ephemeral internal IP address and, optionally, + an external IP address. To communicate between instances on the same network, you + can use an instance's internal IP address. To communicate with the Internet and + instances outside of the same network, you must specify the instance's external + IP address. + - Internal IP addresses are ephemeral and only belong to an instance for the lifetime + of the instance; if the instance is deleted and recreated, the instance is assigned + a new internal IP address, either by Compute Engine or by you. External IP addresses + can be either ephemeral or static. short_description: Creates a GCP Address version_added: 2.6 author: Google Inc. (@googlecloudplatform) @@ -53,53 +53,76 @@ options: state: description: - Whether the given object should exist in GCP - required: true choices: ['present', 'absent'] default: 'present' address: description: - - The static external IP address represented by this resource. Only - IPv4 is supported. + - The static external IP address represented by this resource. Only IPv4 is supported. + An address may only be specified for INTERNAL address types. The IP address must + be inside the specified subnetwork, if any. required: false + address_type: + description: + - The type of address to reserve, either INTERNAL or EXTERNAL. + - If unspecified, defaults to EXTERNAL. + required: false + default: EXTERNAL + version_added: 2.7 + choices: ['INTERNAL', 'EXTERNAL'] description: description: - An optional description of this resource. required: false name: description: - - Name of the resource. The name must be 1-63 characters long, and - comply with RFC1035. Specifically, the name must be 1-63 - characters long and match the regular expression - [a-z]([-a-z0-9]*[a-z0-9])? 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. + - Name of the resource. The name must be 1-63 characters long, and comply with RFC1035. + Specifically, the name must be 1-63 characters long and match the regular expression + `[a-z]([-a-z0-9]*[a-z0-9])?` 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: true + 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. required: false + version_added: 2.7 region: description: - - A reference to Region resource. + - URL of the region where the regional address resides. + - This field is not applicable to global addresses. required: true extends_documentation_fragment: gcp +notes: + - "API Reference: U(https://cloud.google.com/compute/docs/reference/beta/addresses)" + - "Reserving a Static External IP Address: U(https://cloud.google.com/compute/docs/instances-and-network)" + - "Reserving a Static Internal IP Address: U(https://cloud.google.com/compute/docs/ip-addresses/reserve-static-internal-ip-address)" ''' EXAMPLES = ''' - name: create a address gcp_compute_address: - name: 'test-address1' - region: 'us-west1' - project: testProject - auth_kind: service_account - service_account_file: /tmp/auth.pem - scopes: - - https://www.googleapis.com/auth/compute + name: test-address1 + region: us-west1 + project: "test_project" + auth_kind: "service_account" + service_account_file: "/tmp/auth.pem" state: present ''' RETURN = ''' address: description: - - The static external IP address represented by this resource. Only - IPv4 is supported. + - The static external IP address represented by this resource. Only IPv4 is supported. + An address may only be specified for INTERNAL address types. The IP address must + be inside the specified subnetwork, if any. + returned: success + type: str + address_type: + description: + - The type of address to reserve, either INTERNAL or EXTERNAL. + - If unspecified, defaults to EXTERNAL. returned: success type: str creation_timestamp: @@ -119,15 +142,20 @@ RETURN = ''' type: int name: description: - - Name of the resource. The name must be 1-63 characters long, and - comply with RFC1035. Specifically, the name must be 1-63 - characters long and match the regular expression - [a-z]([-a-z0-9]*[a-z0-9])? 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. + - Name of the resource. The name must be 1-63 characters long, and comply with RFC1035. + Specifically, the name must be 1-63 characters long and match the regular expression + `[a-z]([-a-z0-9]*[a-z0-9])?` 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. returned: success type: str + 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. + returned: success + type: dict users: description: - The URLs of the resources that are using this address. @@ -135,7 +163,8 @@ RETURN = ''' type: list region: description: - - A reference to Region resource. + - URL of the region where the regional address resides. + - This field is not applicable to global addresses. returned: success type: str ''' @@ -160,12 +189,17 @@ def main(): argument_spec=dict( state=dict(default='present', choices=['present', 'absent'], type='str'), address=dict(type='str'), + address_type=dict(default='EXTERNAL', type='str', choices=['INTERNAL', 'EXTERNAL']), description=dict(type='str'), - name=dict(type='str'), + name=dict(required=True, type='str'), + subnetwork=dict(type='dict'), region=dict(required=True, type='str') ) ) + if not module.params['scopes']: + module.params['scopes'] = ['https://www.googleapis.com/auth/compute'] + state = module.params['state'] kind = 'compute#address' @@ -175,10 +209,10 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - fetch = update(module, self_link(module), kind, fetch) + fetch = update(module, self_link(module), kind) changed = True else: - delete(module, self_link(module), kind, fetch) + delete(module, self_link(module), kind) fetch = {} changed = True else: @@ -198,12 +232,12 @@ def create(module, link, kind): return wait_for_operation(module, auth.post(link, resource_to_request(module))) -def update(module, link, kind, fetch): +def update(module, link, kind): auth = GcpSession(module, 'compute') return wait_for_operation(module, auth.put(link, resource_to_request(module))) -def delete(module, link, kind, fetch): +def delete(module, link, kind): auth = GcpSession(module, 'compute') return wait_for_operation(module, auth.delete(link)) @@ -211,10 +245,11 @@ def delete(module, link, kind, fetch): def resource_to_request(module): request = { u'kind': 'compute#address', - u'region': module.params.get('region'), u'address': module.params.get('address'), + u'addressType': module.params.get('address_type'), u'description': module.params.get('description'), - u'name': module.params.get('name') + u'name': module.params.get('name'), + u'subnetwork': replace_resource_dict(module.params.get(u'subnetwork', {}), 'selfLink') } return_vals = {} for k, v in request.items(): @@ -283,10 +318,12 @@ def is_different(module, response): def response_to_hash(module, response): return { u'address': response.get(u'address'), + u'addressType': response.get(u'addressType'), u'creationTimestamp': response.get(u'creationTimestamp'), u'description': response.get(u'description'), u'id': response.get(u'id'), u'name': response.get(u'name'), + u'subnetwork': response.get(u'subnetwork'), u'users': response.get(u'users') } @@ -303,7 +340,7 @@ def async_op_url(module, extra_data=None): def wait_for_operation(module, response): op_result = return_if_object(module, response, 'compute#operation') if op_result is None: - return None + return {} status = navigate_hash(op_result, ['status']) wait_done = wait_for_completion(status, op_result, module) return fetch_resource(module, navigate_hash(wait_done, ['targetLink']), 'compute#address') diff --git a/lib/ansible/utils/module_docs_fragments/gcp.py b/lib/ansible/utils/module_docs_fragments/gcp.py index ff5841f8b9..e736e5d4de 100644 --- a/lib/ansible/utils/module_docs_fragments/gcp.py +++ b/lib/ansible/utils/module_docs_fragments/gcp.py @@ -6,15 +6,10 @@ class ModuleDocFragment(object): # GCP doc fragment. DOCUMENTATION = ''' options: - state: - description: - - Whether the given zone should or should not be present. - required: true - choices: ["present", "absent"] - default: "present" project: description: - The Google Cloud Platform project to use. + default: null auth_kind: description: - The type of credential used. @@ -30,7 +25,6 @@ options: scopes: description: - Array of scopes to be used. - required: true notes: - For authentication, you can set service_account_file using the C(GCP_SERVICE_ACCOUNT_FILE) env variable. diff --git a/test/integration/targets/gcp_compute_address/tasks/main.yml b/test/integration/targets/gcp_compute_address/tasks/main.yml index 5586dd9799..53eb5f345e 100644 --- a/test/integration/targets/gcp_compute_address/tasks/main.yml +++ b/test/integration/targets/gcp_compute_address/tasks/main.yml @@ -15,24 +15,20 @@ # Pre-test setup - name: delete a address gcp_compute_address: - name: 'test-address1' - region: 'us-west1' + name: test-address1 + region: us-west1 project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" - scopes: - - https://www.googleapis.com/auth/compute state: absent #---------------------------------------------------------- - name: create a address gcp_compute_address: - name: 'test-address1' - region: 'us-west1' + name: test-address1 + region: us-west1 project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" - scopes: - - https://www.googleapis.com/auth/compute state: present register: result - name: assert changed is true @@ -41,23 +37,28 @@ - result.changed == true - "result.kind == 'compute#address'" - name: verify that address was created - shell: | - gcloud compute addresses describe --project="{{ gcp_project}}" --region=us-west1 test-address1 - register: results -- name: verify that command succeeded - assert: - that: - - results.rc == 0 -# ---------------------------------------------------------------------------- -- name: create a address that already exists - gcp_compute_address: - name: 'test-address1' - region: 'us-west1' + gcp_compute_address_facts: + filters: + - name = test-address1 + region: us-west1 project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" scopes: - https://www.googleapis.com/auth/compute + register: results +- name: verify that command succeeded + assert: + that: + - results['items'] | length == 1 +# ---------------------------------------------------------------------------- +- name: create a address that already exists + gcp_compute_address: + name: test-address1 + region: us-west1 + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" state: present register: result - name: assert changed is false @@ -68,13 +69,11 @@ #---------------------------------------------------------- - name: delete a address gcp_compute_address: - name: 'test-address1' - region: 'us-west1' + name: test-address1 + region: us-west1 project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" - scopes: - - https://www.googleapis.com/auth/compute state: absent register: result - name: assert changed is true @@ -83,25 +82,28 @@ - result.changed == true - result.has_key('kind') == False - name: verify that address was deleted - shell: | - gcloud compute addresses describe --project="{{ gcp_project}}" --region=us-west1 test-address1 - register: results - failed_when: results.rc == 0 -- name: verify that command succeeded - assert: - that: - - results.rc == 1 - - "\"'projects/{{ gcp_project }}/regions/us-west1/addresses/test-address1' was not found\" in results.stderr" -# ---------------------------------------------------------------------------- -- name: delete a address that does not exist - gcp_compute_address: - name: 'test-address1' - region: 'us-west1' + gcp_compute_address_facts: + filters: + - name = test-address1 + region: us-west1 project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" scopes: - https://www.googleapis.com/auth/compute + register: results +- name: verify that command succeeded + assert: + that: + - results['items'] | length == 0 +# ---------------------------------------------------------------------------- +- name: delete a address that does not exist + gcp_compute_address: + name: test-address1 + region: us-west1 + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" state: absent register: result - name: assert changed is false