From a05a7be02a05238d6906e4bd5734b56d5a908ad8 Mon Sep 17 00:00:00 2001 From: Jorge Gallegos Date: Wed, 10 Sep 2025 11:24:56 -0700 Subject: [PATCH 1/3] Adding disk reconciliation to gcp_compute_instance There was no logic around attaching disks to existing instances. I am explicitly only attaching persistent disks as per the attachDisk API --- plugins/modules/gcp_compute_instance.py | 33 ++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/plugins/modules/gcp_compute_instance.py b/plugins/modules/gcp_compute_instance.py index ab7ce1c7..c0839d82 100644 --- a/plugins/modules/gcp_compute_instance.py +++ b/plugins/modules/gcp_compute_instance.py @@ -1216,8 +1216,7 @@ def main(): if fetch: if state == 'present': if is_different(module, fetch): - update(module, self_link(module), kind, fetch) - fetch = fetch_resource(module, self_link(module), kind) + fetch = update(module, self_link(module), kind, fetch) changed = True else: delete(module, self_link(module), kind) @@ -1259,8 +1258,36 @@ def update_fields(module, request, response): machine_type_update(module, request, response) if response.get('shieldedInstanceConfig') != request.get('shieldedInstanceConfig'): shielded_instance_config_update(module, request, response) + if response.get('disks') != request.get('disks'): + extra_disks_update(module, request, response) +def extra_disks_update(module, request, response): + auth = GcpSession(module, 'compute') + # fetch all non-boot (i.e. additional) disks to attach + # but discard local disks (if defined) because they can + # only be attached to instances at creation time anyway + req_disks = set() + for d in request.get('disks', []): + if not d.get('boot'): + if d.get('source'): + req_disks.add(d['source']) + else: + module.warn( + 'Non-persistent disks can only be attached at creation time, ' + 'changed status might be incorrect.') + rsp_disks = set() + for d in response.get('disks', []): + if d.get('source') and not d.get('boot'): + rsp_disks.add(d['source']) + for d in req_disks.difference(rsp_disks): + auth.post( + ''.join([ + "https://compute.googleapis.com/compute/v1/", + "projects/{project}/zones/{zone}/instances/{name}/attachDisk"]).format(**module.params), + {u'source': d}, + ) + def label_fingerprint_update(module, request, response): auth = GcpSession(module, 'compute') auth.post( @@ -1375,7 +1402,7 @@ def response_to_hash(module, response): u'cpuPlatform': response.get(u'cpuPlatform'), u'creationTimestamp': response.get(u'creationTimestamp'), u'deletionProtection': response.get(u'deletionProtection'), - u'disks': InstanceDisksArray(module.params.get('disks', []), module).to_request(), + u'disks': InstanceDisksArray(response.get('disks', []), module).from_response(), u'guestAccelerators': InstanceGuestacceleratorsArray(response.get(u'guestAccelerators', []), module).from_response(), u'hostname': response.get(u'hostname'), u'id': response.get(u'id'), From a65d6ebfa5aaeb9cad20190e43e39f4fe234e0ab Mon Sep 17 00:00:00 2001 From: Jorge Gallegos Date: Wed, 10 Sep 2025 11:28:16 -0700 Subject: [PATCH 2/3] Rework gcp_compute_instance integration tests Also adding the test cases for new attachDisk feature --- .../tasks/attach-disks.yml | 104 ++++++++++++++++++ .../gcp_compute_instance/tasks/gvnic.yml | 12 +- .../gcp_compute_instance/tasks/main.yml | 12 +- 3 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 tests/integration/targets/gcp_compute_instance/tasks/attach-disks.yml diff --git a/tests/integration/targets/gcp_compute_instance/tasks/attach-disks.yml b/tests/integration/targets/gcp_compute_instance/tasks/attach-disks.yml new file mode 100644 index 00000000..ac68a767 --- /dev/null +++ b/tests/integration/targets/gcp_compute_instance/tasks/attach-disks.yml @@ -0,0 +1,104 @@ +--- +- block: + - name: Create instance + google.cloud.gcp_compute_instance: + name: "{{ resource_name }}-attach-vm" + machine_type: n1-standard-1 + state: present + disks: + - auto_delete: true + boot: true + initialize_params: + source_image: "{{ gcp_disk_image }}" + disk_type: pd-standard + network_interfaces: + - network: "{{ _network }}" + zone: "{{ gcp_zone }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file | default(omit) }}" + register: _result1 + + - name: Verify instance info post-create + google.cloud.gcp_compute_instance_info: + filters: + - name = {{ _result1.name }} + zone: "{{ gcp_zone }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file | default(omit) }}" + scopes: + - https://www.googleapis.com/auth/compute + register: _info1 + + - name: Create extra disk + google.cloud.gcp_compute_disk: + name: "{{ resource_name }}-extra" + state: present + zone: "{{ gcp_zone }}" + size_gb: 20 + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file | default(omit) }}" + register: _disk + + - name: Attach extra disk to instance + google.cloud.gcp_compute_instance: + name: "{{ resource_name }}-attach-vm" + machine_type: n1-standard-1 + state: present + disks: + - auto_delete: true + boot: true + initialize_params: + source_image: "{{ gcp_disk_image }}" + disk_type: pd-standard + - source: "{{ _disk }}" + network_interfaces: + - network: "{{ _network }}" + zone: "{{ gcp_zone }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file | default(omit) }}" + register: _result2 + + - name: Verify instance info post-change + google.cloud.gcp_compute_instance_info: + filters: + - name = {{ _result2.name }} + zone: "{{ gcp_zone }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file | default(omit) }}" + scopes: + - https://www.googleapis.com/auth/compute + register: _info2 + + - name: Run assertions + ansible.builtin.assert: + that: + - _info1.resources | length > 0 + - _info1.resources[0].disks | length == 1 + - _info2.resources | length > 0 + - _info2.resources[0].disks | length == 2 + + always: + # teardown + - name: Destroy instance + google.cloud.gcp_compute_instance: + name: "{{ resource_name }}-attach-vm" + state: absent + machine_type: n1-standard-1 + zone: "{{ gcp_zone }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file | default(omit) }}" + + - name: Destroy extra disk + google.cloud.gcp_compute_disk: + name: "{{ resource_name }}-extra" + state: absent + zone: "{{ gcp_zone }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file | default(omit) }}" diff --git a/tests/integration/targets/gcp_compute_instance/tasks/gvnic.yml b/tests/integration/targets/gcp_compute_instance/tasks/gvnic.yml index bc5db37c..70442eaa 100644 --- a/tests/integration/targets/gcp_compute_instance/tasks/gvnic.yml +++ b/tests/integration/targets/gcp_compute_instance/tasks/gvnic.yml @@ -9,8 +9,8 @@ google.cloud.gcp_compute_disk: name: "{{ resource_prefix }}-{{ item.key }}" size_gb: 50 - source_image: projects/rhel-cloud/global/images/rhel-9-v20250513 - zone: us-central1-a + source_image: "{{ gcp_disk_image }}" + zone: "{{ gcp_zone }}" project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file | default(omit) }}" @@ -28,7 +28,7 @@ network_interfaces: - network: "{{ _network }}" nic_type: "{{ item.value if item.value != 'default' else omit }}" - zone: us-central1-a + zone: "{{ gcp_zone }}" project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file | default(omit) }}" @@ -39,7 +39,7 @@ google.cloud.gcp_compute_instance_info: filters: - name = {{ resource_name }}-{{ item.key }} - zone: us-central1-a + zone: "{{ gcp_zone }}" project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file | default(omit) }}" @@ -60,7 +60,7 @@ google.cloud.gcp_compute_instance: name: "{{ resource_name }}-{{ item.key }}" machine_type: n1-standard-1 - zone: us-central1-a + zone: "{{ gcp_zone }}" project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file | default(omit) }}" @@ -69,7 +69,7 @@ - name: Delete disk google.cloud.gcp_compute_disk: name: "{{ resource_prefix }}-{{ item.key }}" - zone: us-central1-a + zone: "{{ gcp_zone }}" project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file | default(omit) }}" diff --git a/tests/integration/targets/gcp_compute_instance/tasks/main.yml b/tests/integration/targets/gcp_compute_instance/tasks/main.yml index 1cd682ce..bb66eed7 100644 --- a/tests/integration/targets/gcp_compute_instance/tasks/main.yml +++ b/tests/integration/targets/gcp_compute_instance/tasks/main.yml @@ -2,7 +2,7 @@ - name: Generated tests ansible.builtin.include_tasks: autogen.yml -- name: Test nic_type scenarios +- name: Extra non-autogen tests block: - name: Create network google.cloud.gcp_compute_network: @@ -14,15 +14,23 @@ state: present register: _network - - name: Loop over testcase + - name: Test GVNIC test cases ansible.builtin.include_tasks: gvnic.yml loop: "{{ testcases | dict2items }}" vars: + gcp_disk_image: projects/centos-cloud/global/images/family/centos-stream-9 + gcp_zone: us-central1-a testcases: gvnic: GVNIC virtio: VIRTIO_NET default: default + - name: Test attach disks + ansible.builtin.include_tasks: attach-disks.yml + vars: + gcp_disk_image: projects/centos-cloud/global/images/family/centos-stream-9 + gcp_zone: us-central1-a + always: - name: Delete network google.cloud.gcp_compute_network: From 86f6f53d30b22356901c30f595298ba8b9f2923f Mon Sep 17 00:00:00 2001 From: Jorge Gallegos Date: Wed, 10 Sep 2025 11:41:25 -0700 Subject: [PATCH 3/3] Sanity fix --- plugins/modules/gcp_compute_instance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/gcp_compute_instance.py b/plugins/modules/gcp_compute_instance.py index c0839d82..5b260307 100644 --- a/plugins/modules/gcp_compute_instance.py +++ b/plugins/modules/gcp_compute_instance.py @@ -1288,6 +1288,7 @@ def extra_disks_update(module, request, response): {u'source': d}, ) + def label_fingerprint_update(module, request, response): auth = GcpSession(module, 'compute') auth.post(