diff --git a/plugins/modules/gcp_alloydb_backup.py b/plugins/modules/gcp_alloydb_backup.py index 166377f76..345465659 100644 --- a/plugins/modules/gcp_alloydb_backup.py +++ b/plugins/modules/gcp_alloydb_backup.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # -# Copyright (C) 2017-2025 Google +# Copyright (C) 2017-2026 Google # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # ---------------------------------------------------------------------------- # @@ -48,15 +48,18 @@ - This is distinct from labels. - 'https://google.aip.dev/128 An object containing a list of "key": value pairs.' - 'Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }.' + - '**Note**: This field is non-authoritative, and will only manage the annotations present in your configuration.' type: dict backup_id: description: - The ID of the alloydb backup. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str cluster_name: description: - The full resource name of the backup source cluster (e.g., projects/{project}/locations/{location}/clusters/{clusterId}). + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str description: @@ -74,10 +77,8 @@ kms_key_name: description: - The fully-qualified resource name of the KMS key. - - >- - Each Cloud KMS key is regionalized and has the following format: - - projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME]. + - 'Each Cloud KMS key is regionalized and has the following format: projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME].' + - This property is immutable, to change it, you must delete and recreate the resource. type: str type: dict labels: @@ -85,10 +86,12 @@ - User-defined labels for the alloydb backup. - 'An object containing a list of "key": value pairs.' - 'Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }.' + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict location: description: - The location where the alloydb backup should reside. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str state: @@ -113,7 +116,7 @@ - requests >= 2.18.4 - google-auth >= 2.25.1 short_description: Creates a GCP Alloydb.Backup resource -""" +""" # noqa: E501 EXAMPLES = r""" - name: Create an on-demand backup for a cluster @@ -123,7 +126,7 @@ cluster_name: projects/my-project/locations/us-central1/clusters/my-cluster type: ON_DEMAND location: us-central1 -""" +""" # noqa: E501 RETURN = r""" changed: @@ -244,37 +247,26 @@ - 'Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".' returned: success type: str -""" +""" # noqa: E501 ################################################################################ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports # END Custom imports -def build_link(module, uri): - params = module.params.copy() - - return "https://alloydb.googleapis.com/v1/" + uri.format(**params) - - -class EncryptionConfig(gcp.Resource): +class EncryptionConfig(gcp_v2.Resource): def _request(self): return { "kmsKeyName": self.request.get("kms_key_name"), } - def _response(self): - return { - "kmsKeyName": self.response.get("kmsKeyName"), - } - -class EncryptionInfo(gcp.Resource): +class EncryptionInfo(gcp_v2.Resource): def _response(self): return { "encryptionType": self.response.get("encryptionType"), @@ -282,7 +274,7 @@ def _response(self): } -class ExpiryQuantity(gcp.Resource): +class ExpiryQuantity(gcp_v2.Resource): def _response(self): return { "retentionCount": self.response.get("retentionCount"), @@ -290,37 +282,32 @@ def _response(self): } -class Alloydb(gcp.Resource): +class Alloydb(gcp_v2.Resource): def _request(self): return { "annotations": self.request.get("annotations"), "clusterName": self.request.get("cluster_name"), "description": self.request.get("description"), "displayName": self.request.get("display_name"), - "encryptionConfig": EncryptionConfig(self.request.get("encryption_config", {})).to_request(), + "encryptionConfig": gcp_v2.remove_empties( + EncryptionConfig(self.request.get("encryption_config", {})).to_request() + ), # remove empty values "labels": self.request.get("labels"), "type": self.request.get("type"), } def _response(self): return { - "annotations": self.response.get("annotations"), - "clusterName": self.response.get("clusterName"), "clusterUid": self.response.get("clusterUid"), "createTime": self.response.get("createTime"), "deleteTime": self.response.get("deleteTime"), - "description": self.response.get("description"), - "displayName": self.response.get("displayName"), - "encryptionConfig": EncryptionConfig().from_response(self.response.get("encryptionConfig", {})), "encryptionInfo": EncryptionInfo().from_response(self.response.get("encryptionInfo", {})), "etag": self.response.get("etag"), "expiryQuantity": ExpiryQuantity().from_response(self.response.get("expiryQuantity", {})), "expiryTime": self.response.get("expiryTime"), - "labels": self.response.get("labels"), "name": self.response.get("name"), "reconciling": self.response.get("reconciling"), "sizeBytes": self.response.get("sizeBytes"), - "type": self.response.get("type"), "uid": self.response.get("uid"), "updateTime": self.response.get("updateTime"), } @@ -334,7 +321,7 @@ def _response(self): def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( state=dict( type="str", @@ -363,6 +350,7 @@ def main(): options=dict( kms_key_name=dict( type="str", + no_log=False, ) ), ), @@ -385,10 +373,11 @@ def main(): state = module.params["state"] changed = False - - op_configs = gcp.ResourceOpConfigs( - { - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://alloydb.googleapis.com/v1/", + base_uri="projects/{project}/locations/{location}/backups", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/backups?backupId={backup_id}", "async_uri": "{op_id}", @@ -396,7 +385,7 @@ def main(): "timeout_minutes": 10, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/backups/{backup_id}", "async_uri": "{op_id}", @@ -404,7 +393,7 @@ def main(): "timeout_minutes": 10, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/backups/{backup_id}", "async_uri": "", @@ -412,7 +401,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/backups/{backup_id}", "async_uri": "{op_id}", @@ -420,74 +409,135 @@ def main(): "timeout_minutes": 10, } ), - } + }, ) - params = gcp.remove_nones_from_dict(module.params) - resource = Alloydb(params, module=module, product="Alloydb", kind="alloydb#backup") - existing_obj = resource.get(build_link(module, op_configs.read.uri), allow_not_found=True) + request = gcp_v2.remove_nones(module.params) + resource = Alloydb(request, module=module, product="Alloydb", kind="alloydb#backup", op_configs=op_configs) + + resource._state = state # store the state in the resource object + + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None - if existing_obj is None: + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload + + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) + new_obj = {} + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) + + if custom_diff is not None: + is_different = custom_diff + else: + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( + module, + request=gcp_v2.remove_empties(resource.to_request()), + existing=existing_obj, + post=True, + is_different=is_different, + ) + + if gcp_v2.empty(existing_obj): if state == "present": - is_async = op_configs.create.async_uri != "" - create_link = build_link(module, op_configs.create.uri) - create_retries = op_configs.create.timeout - create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(module, "") + op_configs.create.async_uri - # --------- BEGIN custom pre-create code --------- - # --------- END custom pre-create code --------- + gcp_v2.debug(module, action="create") try: - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + # --------- BEGIN create code --------- + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") + create_retries = op_configs.create.timeout + create_func = getattr(resource, op_configs.create.verb) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) + + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - changed = True + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) + # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) + + changed = True else: pass # nothing to do else: if state == "absent": - is_async = op_configs.delete.async_uri != "" - delete_link = build_link(module, op_configs.delete.uri) - delete_retries = op_configs.delete.timeout - delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(module, "") + op_configs.delete.async_uri - # --------- BEGIN custom pre-delete code --------- - # --------- END custom pre-delete code --------- + gcp_v2.debug(module, action="delete") try: - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") + delete_retries = op_configs.delete.timeout + delete_func = getattr(resource, op_configs.delete.verb) + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( + module, + msg="Destroying resource", + delete_link=delete_link, + async_uri=delete_async_uri, + ) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) - changed = True + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) + # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) + + changed = True else: - if resource.diff(existing_obj): - is_async = op_configs.update.async_uri != "" - update_link = build_link(module, op_configs.update.uri) - update_retries = op_configs.update.timeout - update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(module, "") + op_configs.update.async_uri - # --------- BEGIN custom pre-update code --------- - # --------- END custom pre-update code --------- + if is_different: + gcp_v2.debug(module, action="update") try: - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") + update_retries = op_configs.update.timeout + update_func = getattr(resource, op_configs.update.verb) + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( + module, + msg="Updating resource", + update_link=update_link, + async_uri=update_async_uri, + ) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) + # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) - changed = resource.diff(new_obj) - new_obj = resource.get(build_link(module, op_configs.read.uri), allow_not_found=True) - new_obj = resource.from_response(new_obj or {}) + changed = True + else: + new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_alloydb_cluster.py b/plugins/modules/gcp_alloydb_cluster.py index aafd9935a..76595725b 100644 --- a/plugins/modules/gcp_alloydb_cluster.py +++ b/plugins/modules/gcp_alloydb_cluster.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # -# Copyright (C) 2017-2025 Google +# Copyright (C) 2017-2026 Google # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # ---------------------------------------------------------------------------- # @@ -48,6 +48,7 @@ - This is distinct from labels. - 'https://google.aip.dev/128 An object containing a list of "key": value pairs.' - 'Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }.' + - '**Note**: This field is non-authoritative, and will only manage the annotations present in your configuration.' type: dict automated_backup_policy: description: @@ -75,10 +76,7 @@ kms_key_name: description: - The fully-qualified resource name of the KMS key. - - >- - Each Cloud KMS key is regionalized and has the following format: - - projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME]. + - 'Each Cloud KMS key is regionalized and has the following format: projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME].' type: str type: dict labels: @@ -157,6 +155,7 @@ cluster_id: description: - The ID of the alloydb cluster. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str cluster_type: @@ -187,10 +186,7 @@ kms_key_name: description: - The fully-qualified resource name of the KMS key. - - >- - Each Cloud KMS key is regionalized and has the following format: - - projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME]. + - 'Each Cloud KMS key is regionalized and has the following format: projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME].' type: str type: dict recovery_window_days: @@ -206,6 +202,34 @@ - This is an optional field and it's populated at the Cluster creation time. - 'Note: Changing this field to a higer version results in upgrading the AlloyDB cluster which is an irreversible change.' type: str + dataplex_config: + description: + - Configuration for Dataplex integration. + - This is an optional field. + - If not set, Dataplex integration will be enabled by default. + suboptions: + enabled: + description: + - Indicates whether Dataplex integration is enabled for the cluster. + required: true + type: bool + type: dict + deletion_policy: + default: DEFAULT + description: + - Policy to determine if the cluster should be deleted forcefully. + - Deleting a cluster forcefully, deletes the cluster and all its associated instances within the cluster. + - Deleting a Secondary cluster with a secondary instance REQUIRES setting deletion_policy = "FORCE" otherwise an error is returned. + - This is needed as there is no support to delete just the secondary instance, and the only way to delete secondary instance is to delete the associated secondary cluster forcefully which also deletes the secondary instance. + - 'Possible values: DEFAULT, FORCE.' + type: str + deletion_protection: + default: true + description: + - Whether Terraform will be prevented from destroying the cluster. + - When the field is set to true or unset in Terraform state, a `terraform apply` or `terraform destroy` that would delete the cluster will fail. + - When the field is set to false, deleting the cluster is allowed. + type: bool display_name: description: - User-settable and human-readable display name for the Cluster. @@ -217,10 +241,8 @@ kms_key_name: description: - The fully-qualified resource name of the KMS key. - - >- - Each Cloud KMS key is regionalized and has the following format: - - projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME]. + - 'Each Cloud KMS key is regionalized and has the following format: projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME].' + - This property is immutable, to change it, you must delete and recreate the resource. type: str type: dict etag: @@ -230,11 +252,12 @@ initial_user: description: - Initial user to setup during cluster creation. + - If unset for new Clusters, a postgres role with null password is created. + - You will need to create additional users or set the password in order to log in. suboptions: password: description: - The initial password for the user. - required: true type: str user: description: @@ -244,10 +267,12 @@ labels: description: - User-defined labels for the alloydb cluster. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict location: description: - The location where the alloydb cluster should reside. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str maintenance_update_policy: @@ -331,37 +356,73 @@ type: bool service_owned_project_number: description: - - >- - The project number that needs to be allowlisted on the network attachment to enable outbound connectivity, if the network attachment is - - configured to ACCEPT_MANUAL connections. + - The project number that needs to be allowlisted on the network attachment to enable outbound connectivity, if the network attachment is configured to ACCEPT_MANUAL connections. - In case the network attachment is configured to ACCEPT_AUTOMATIC, this project number does not need to be allowlisted explicitly. type: int type: dict restore_backup_source: description: - The source when restoring from a backup. - - Conflicts with 'restore_continuous_backup_source', both can't be set together. + - Conflicts with 'restore_continuous_backup_source', 'restore_backupdr_backup_source' and 'restore_backupdr_pitr_source', they can't be set together. + - This property is immutable, to change it, you must delete and recreate the resource. suboptions: backup_name: description: - The name of the backup that this cluster is restored from. + - This property is immutable, to change it, you must delete and recreate the resource. + required: true + type: str + type: dict + restore_backupdr_backup_source: + description: + - The source when restoring from a backup. + - Conflicts with 'restore_continuous_backup_source', 'restore_backup_source' and 'restore_backupdr_pitr_source', they can't be set together. + - This property is immutable, to change it, you must delete and recreate the resource. + suboptions: + backup: + description: + - The name of the BackupDR backup that this cluster is restored from. + - It must be of the format "projects/[PROJECT]/locations/[LOCATION]/backupVaults/[VAULT_ID]/dataSources/[DATASOURCE_ID]/backups/[BACKUP_ID]". + - This property is immutable, to change it, you must delete and recreate the resource. + required: true + type: str + type: dict + restore_backupdr_pitr_source: + description: + - The BackupDR source used for point in time recovery. + - Conflicts with 'restore_backupdr_backup_source', 'restore_continuous_backup_source' and 'restore_backupdr_backup_source', they can't be set togeter. + - This property is immutable, to change it, you must delete and recreate the resource. + suboptions: + data_source: + description: + - The name of the BackupDR data source that this cluster is restore from. + - It must be of the format "projects/[PROJECT]/locations/[LOCATION]/backupVaults/[VAULT_ID]/dataSources/[DATASOURCE_ID]". + - This property is immutable, to change it, you must delete and recreate the resource. + required: true + type: str + point_in_time: + description: + - The point in time that this cluster is restored to, in RFC 3339 format. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str type: dict restore_continuous_backup_source: description: - The source when restoring via point in time recovery (PITR). - - Conflicts with 'restore_backup_source', both can't be set together. + - Conflicts with 'restore_backup_source', 'restore_backupdr_backup_source' and 'restore_backupdr_pitr_source', they can't be set together. + - This property is immutable, to change it, you must delete and recreate the resource. suboptions: cluster: description: - The name of the source cluster that this cluster is restored from. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str point_in_time: description: - The point in time that this cluster is restored to, in RFC 3339 format. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str type: dict @@ -376,6 +437,12 @@ required: true type: str type: dict + skip_await_major_version_upgrade: + default: true + description: + - Set to true to skip awaiting on the major version upgrade of the cluster. + - 'Possible values: true, false Default value: "true".' + type: bool state: choices: - present @@ -396,7 +463,7 @@ - requests >= 2.18.4 - google-auth >= 2.25.1 short_description: Creates a GCP Alloydb.Cluster resource -""" +""" # noqa: E501 EXAMPLES = r""" - name: Create basic alloydb cluster @@ -421,6 +488,9 @@ state: present location: us-central1 cluster_type: PRIMARY + initial_user: + user: pgroot + password: Test123Test network_config: network: "projects/{{ gcp_project }}/global/networks/default" project: "{{ gcp_project }}" @@ -441,7 +511,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" -""" +""" # noqa: E501 RETURN = r""" backupSource: @@ -455,6 +525,17 @@ - Cluster created from backup. returned: success type: dict +backupdrBackupSource: + contains: + backup: + description: + - The name of the BackupDR backup resource. + returned: when set + type: str + description: + - Cluster created from a BackupDR backup. + returned: success + type: dict changed: description: Whether the resource was changed. returned: always @@ -554,10 +635,7 @@ description: - Output only. - Reconciling (https://google.aip.dev/128#reconciliation). - - >- - Set to true if the current state of Cluster does not match the user's intended state, and the service is actively updating the resource to - - reconcile them. + - Set to true if the current state of Cluster does not match the user's intended state, and the service is actively updating the resource to reconcile them. - This can happen due to user-triggered updates or system actions like failover or maintenance. returned: success type: bool @@ -598,101 +676,64 @@ - The system-generated UID of the resource. returned: success type: str -""" +""" # noqa: E501 ################################################################################ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports # END Custom imports -def build_link(module, uri): - params = module.params.copy() - - return "https://alloydb.googleapis.com/v1/" + uri.format(**params) - - -class AutomatedBackupPolicy(gcp.Resource): +class AutomatedBackupPolicy(gcp_v2.Resource): def _request(self): return { "backupWindow": self.request.get("backup_window"), "enabled": self.request.get("enabled"), - "encryptionConfig": AutomatedBackupPolicyEncryptionConfig( - self.request.get("encryption_config", {}) - ).to_request(), + "encryptionConfig": gcp_v2.remove_empties( + AutomatedBackupPolicyEncryptionConfig(self.request.get("encryption_config", {})).to_request() + ), # remove empty values "labels": self.request.get("labels"), "location": self.request.get("location"), - "quantityBasedRetention": AutomatedBackupPolicyQuantityBasedRetention( - self.request.get("quantity_based_retention", {}) - ).to_request(), - "timeBasedRetention": AutomatedBackupPolicyTimeBasedRetention( - self.request.get("time_based_retention", {}) - ).to_request(), - "weeklySchedule": AutomatedBackupPolicyWeeklySchedule(self.request.get("weekly_schedule", {})).to_request(), - } - - def _response(self): - return { - "backupWindow": self.response.get("backupWindow"), - "enabled": self.response.get("enabled"), - "encryptionConfig": AutomatedBackupPolicyEncryptionConfig().from_response( - self.response.get("encryptionConfig", {}) - ), - "labels": self.response.get("labels"), - "location": self.response.get("location"), - "quantityBasedRetention": AutomatedBackupPolicyQuantityBasedRetention().from_response( - self.response.get("quantityBasedRetention", {}) - ), - "timeBasedRetention": AutomatedBackupPolicyTimeBasedRetention().from_response( - self.response.get("timeBasedRetention", {}) - ), - "weeklySchedule": AutomatedBackupPolicyWeeklySchedule().from_response( - self.response.get("weeklySchedule", {}) - ), + "quantityBasedRetention": gcp_v2.remove_empties( + AutomatedBackupPolicyQuantityBasedRetention( + self.request.get("quantity_based_retention", {}) + ).to_request() + ), # remove empty values + "timeBasedRetention": gcp_v2.remove_empties( + AutomatedBackupPolicyTimeBasedRetention(self.request.get("time_based_retention", {})).to_request() + ), # remove empty values + "weeklySchedule": gcp_v2.remove_empties( + AutomatedBackupPolicyWeeklySchedule(self.request.get("weekly_schedule", {})).to_request() + ), # remove empty values } -class AutomatedBackupPolicyEncryptionConfig(gcp.Resource): +class AutomatedBackupPolicyEncryptionConfig(gcp_v2.Resource): def _request(self): return { "kmsKeyName": self.request.get("kms_key_name"), } - def _response(self): - return { - "kmsKeyName": self.response.get("kmsKeyName"), - } - -class AutomatedBackupPolicyQuantityBasedRetention(gcp.Resource): +class AutomatedBackupPolicyQuantityBasedRetention(gcp_v2.Resource): def _request(self): return { "count": self.request.get("count"), } - def _response(self): - return { - "count": self.response.get("count"), - } - -class AutomatedBackupPolicyTimeBasedRetention(gcp.Resource): +class AutomatedBackupPolicyTimeBasedRetention(gcp_v2.Resource): def _request(self): return { "retentionPeriod": self.request.get("retention_period"), } - def _response(self): - return { - "retentionPeriod": self.response.get("retentionPeriod"), - } - -class AutomatedBackupPolicyWeeklySchedule(gcp.Resource): +class AutomatedBackupPolicyWeeklySchedule(gcp_v2.Resource): def _request(self): return { "daysOfWeek": self.request.get("days_of_week"), @@ -702,17 +743,8 @@ def _request(self): ], } - def _response(self): - return { - "daysOfWeek": self.response.get("daysOfWeek"), - "startTimes": [ - AutomatedBackupPolicyWeeklyScheduleStartTime().from_response(item) - for item in (self.response.get("startTimes") or []) - ], - } - -class AutomatedBackupPolicyWeeklyScheduleStartTime(gcp.Resource): +class AutomatedBackupPolicyWeeklyScheduleStartTime(gcp_v2.Resource): def _request(self): return { "hours": self.request.get("hours"), @@ -721,60 +753,40 @@ def _request(self): "seconds": self.request.get("seconds"), } - def _response(self): - return { - "hours": self.response.get("hours"), - "minutes": self.response.get("minutes"), - "nanos": self.response.get("nanos"), - "seconds": self.response.get("seconds"), - } - -class BackupSource(gcp.Resource): +class BackupSource(gcp_v2.Resource): def _request(self): return { "backupName": self.request.get("backup_name"), } - def _response(self): + +class BackupdrBackupSource(gcp_v2.Resource): + def _request(self): return { - "backupName": self.response.get("backupName"), + "backup": self.request.get("backup"), } -class ContinuousBackupConfig(gcp.Resource): +class ContinuousBackupConfig(gcp_v2.Resource): def _request(self): return { "enabled": self.request.get("enabled"), - "encryptionConfig": ContinuousBackupConfigEncryptionConfig( - self.request.get("encryption_config", {}) - ).to_request(), + "encryptionConfig": gcp_v2.remove_empties( + ContinuousBackupConfigEncryptionConfig(self.request.get("encryption_config", {})).to_request() + ), # remove empty values "recoveryWindowDays": self.request.get("recovery_window_days"), } - def _response(self): - return { - "enabled": self.response.get("enabled"), - "encryptionConfig": ContinuousBackupConfigEncryptionConfig().from_response( - self.response.get("encryptionConfig", {}) - ), - "recoveryWindowDays": self.response.get("recoveryWindowDays"), - } - -class ContinuousBackupConfigEncryptionConfig(gcp.Resource): +class ContinuousBackupConfigEncryptionConfig(gcp_v2.Resource): def _request(self): return { "kmsKeyName": self.request.get("kms_key_name"), } - def _response(self): - return { - "kmsKeyName": self.response.get("kmsKeyName"), - } - -class ContinuousBackupInfo(gcp.Resource): +class ContinuousBackupInfo(gcp_v2.Resource): def _response(self): return { "earliestRestorableTime": self.response.get("earliestRestorableTime"), @@ -786,7 +798,7 @@ def _response(self): } -class ContinuousBackupInfoEncryptionInfo(gcp.Resource): +class ContinuousBackupInfoEncryptionInfo(gcp_v2.Resource): def _response(self): return { "encryptionType": self.response.get("encryptionType"), @@ -794,19 +806,21 @@ def _response(self): } -class EncryptionConfig(gcp.Resource): +class DataplexConfig(gcp_v2.Resource): def _request(self): return { - "kmsKeyName": self.request.get("kms_key_name"), + "enabled": self.request.get("enabled"), } - def _response(self): + +class EncryptionConfig(gcp_v2.Resource): + def _request(self): return { - "kmsKeyName": self.response.get("kmsKeyName"), + "kmsKeyName": self.request.get("kms_key_name"), } -class EncryptionInfo(gcp.Resource): +class EncryptionInfo(gcp_v2.Resource): def _response(self): return { "encryptionType": self.response.get("encryptionType"), @@ -814,21 +828,15 @@ def _response(self): } -class InitialUser(gcp.Resource): +class InitialUser(gcp_v2.Resource): def _request(self): return { "password": self.request.get("password"), "user": self.request.get("user"), } - def _response(self): - return { - "password": self.response.get("password"), - "user": self.response.get("user"), - } - -class MaintenanceUpdatePolicy(gcp.Resource): +class MaintenanceUpdatePolicy(gcp_v2.Resource): def _request(self): return { "maintenanceWindows": [ @@ -837,34 +845,18 @@ def _request(self): ], } - def _response(self): - return { - "maintenanceWindows": [ - MaintenanceUpdatePolicyMaintenanceWindow().from_response(item) - for item in (self.response.get("maintenanceWindows") or []) - ], - } - -class MaintenanceUpdatePolicyMaintenanceWindow(gcp.Resource): +class MaintenanceUpdatePolicyMaintenanceWindow(gcp_v2.Resource): def _request(self): return { "day": self.request.get("day"), - "startTime": MaintenanceUpdatePolicyMaintenanceWindowStartTime( - self.request.get("start_time", {}) - ).to_request(), - } - - def _response(self): - return { - "day": self.response.get("day"), - "startTime": MaintenanceUpdatePolicyMaintenanceWindowStartTime().from_response( - self.response.get("startTime", {}) - ), + "startTime": gcp_v2.remove_empties( + MaintenanceUpdatePolicyMaintenanceWindowStartTime(self.request.get("start_time", {})).to_request() + ), # remove empty values } -class MaintenanceUpdatePolicyMaintenanceWindowStartTime(gcp.Resource): +class MaintenanceUpdatePolicyMaintenanceWindowStartTime(gcp_v2.Resource): def _request(self): return { "hours": self.request.get("hours"), @@ -873,16 +865,8 @@ def _request(self): "seconds": self.request.get("seconds"), } - def _response(self): - return { - "hours": self.response.get("hours"), - "minutes": self.response.get("minutes"), - "nanos": self.response.get("nanos"), - "seconds": self.response.get("seconds"), - } - -class MigrationSource(gcp.Resource): +class MigrationSource(gcp_v2.Resource): def _request(self): return { "hostPort": self.request.get("host_port"), @@ -890,29 +874,16 @@ def _request(self): "sourceType": self.request.get("source_type"), } - def _response(self): - return { - "hostPort": self.response.get("hostPort"), - "referenceId": self.response.get("referenceId"), - "sourceType": self.response.get("sourceType"), - } - -class NetworkConfig(gcp.Resource): +class NetworkConfig(gcp_v2.Resource): def _request(self): return { "allocatedIpRange": self.request.get("allocated_ip_range"), "network": self.request.get("network"), } - def _response(self): - return { - "allocatedIpRange": self.response.get("allocatedIpRange"), - "network": self.response.get("network"), - } - -class PscConfig(gcp.Resource): +class PscConfig(gcp_v2.Resource): def _request(self): return { "pscEnabled": self.request.get("psc_enabled"), @@ -920,50 +891,48 @@ def _request(self): def _response(self): return { - "pscEnabled": self.response.get("pscEnabled"), "serviceOwnedProjectNumber": self.response.get("serviceOwnedProjectNumber"), } -class RestoreBackupSource(gcp.Resource): +class RestoreBackupSource(gcp_v2.Resource): def _request(self): return { "backupName": self.request.get("backup_name"), } - def _response(self): + +class RestoreBackupdrBackupSource(gcp_v2.Resource): + def _request(self): return { - "backupName": self.response.get("backupName"), + "backup": self.request.get("backup"), } -class RestoreContinuousBackupSource(gcp.Resource): +class RestoreBackupdrPitrSource(gcp_v2.Resource): def _request(self): return { - "cluster": self.request.get("cluster"), + "dataSource": self.request.get("data_source"), "pointInTime": self.request.get("point_in_time"), } - def _response(self): + +class RestoreContinuousBackupSource(gcp_v2.Resource): + def _request(self): return { - "cluster": self.response.get("cluster"), - "pointInTime": self.response.get("pointInTime"), + "cluster": self.request.get("cluster"), + "pointInTime": self.request.get("point_in_time"), } -class SecondaryConfig(gcp.Resource): +class SecondaryConfig(gcp_v2.Resource): def _request(self): return { "primaryClusterName": self.request.get("primary_cluster_name"), } - def _response(self): - return { - "primaryClusterName": self.response.get("primaryClusterName"), - } - -class TrialMetadata(gcp.Resource): +class TrialMetadata(gcp_v2.Resource): def _request(self): return { "endTime": self.request.get("end_time"), @@ -972,78 +941,67 @@ def _request(self): "upgradeTime": self.request.get("upgrade_time"), } - def _response(self): - return { - "endTime": self.response.get("endTime"), - "graceEndTime": self.response.get("graceEndTime"), - "startTime": self.response.get("startTime"), - "upgradeTime": self.response.get("upgradeTime"), - } - -class Alloydb(gcp.Resource): +class Alloydb(gcp_v2.Resource): def _request(self): return { "annotations": self.request.get("annotations"), - "automatedBackupPolicy": AutomatedBackupPolicy( - self.request.get("automated_backup_policy", {}) - ).to_request(), + "automatedBackupPolicy": gcp_v2.remove_empties( + AutomatedBackupPolicy(self.request.get("automated_backup_policy", {})).to_request() + ), # remove empty values "clusterType": self.request.get("cluster_type"), - "continuousBackupConfig": ContinuousBackupConfig( - self.request.get("continuous_backup_config", {}) - ).to_request(), + "continuousBackupConfig": gcp_v2.remove_empties( + ContinuousBackupConfig(self.request.get("continuous_backup_config", {})).to_request() + ), # remove empty values "databaseVersion": self.request.get("database_version"), + "dataplexConfig": gcp_v2.remove_empties( + DataplexConfig(self.request.get("dataplex_config", {})).to_request() + ), # remove empty values "displayName": self.request.get("display_name"), - "encryptionConfig": EncryptionConfig(self.request.get("encryption_config", {})).to_request(), + "encryptionConfig": gcp_v2.remove_empties( + EncryptionConfig(self.request.get("encryption_config", {})).to_request() + ), # remove empty values "etag": self.request.get("etag"), - "initialUser": InitialUser(self.request.get("initial_user", {})).to_request(), + "initialUser": gcp_v2.remove_empties( + InitialUser(self.request.get("initial_user", {})).to_request() + ), # remove empty values "labels": self.request.get("labels"), - "maintenanceUpdatePolicy": MaintenanceUpdatePolicy( - self.request.get("maintenance_update_policy", {}) - ).to_request(), - "networkConfig": NetworkConfig(self.request.get("network_config", {})).to_request(), - "pscConfig": PscConfig(self.request.get("psc_config", {})).to_request(), - "restoreBackupSource": RestoreBackupSource(self.request.get("restore_backup_source", {})).to_request(), - "restoreContinuousBackupSource": RestoreContinuousBackupSource( - self.request.get("restore_continuous_backup_source", {}) - ).to_request(), - "secondaryConfig": SecondaryConfig(self.request.get("secondary_config", {})).to_request(), + "maintenanceUpdatePolicy": gcp_v2.remove_empties( + MaintenanceUpdatePolicy(self.request.get("maintenance_update_policy", {})).to_request() + ), # remove empty values + "networkConfig": gcp_v2.remove_empties( + NetworkConfig(self.request.get("network_config", {})).to_request() + ), # remove empty values + "pscConfig": gcp_v2.remove_empties( + PscConfig(self.request.get("psc_config", {})).to_request() + ), # remove empty values + "restoreBackupSource": gcp_v2.remove_empties( + RestoreBackupSource(self.request.get("restore_backup_source", {})).to_request() + ), # remove empty values + "restoreBackupdrBackupSource": gcp_v2.remove_empties( + RestoreBackupdrBackupSource(self.request.get("restore_backupdr_backup_source", {})).to_request() + ), # remove empty values + "restoreBackupdrPitrSource": gcp_v2.remove_empties( + RestoreBackupdrPitrSource(self.request.get("restore_backupdr_pitr_source", {})).to_request() + ), # remove empty values + "restoreContinuousBackupSource": gcp_v2.remove_empties( + RestoreContinuousBackupSource(self.request.get("restore_continuous_backup_source", {})).to_request() + ), # remove empty values + "secondaryConfig": gcp_v2.remove_empties( + SecondaryConfig(self.request.get("secondary_config", {})).to_request() + ), # remove empty values "subscriptionType": self.request.get("subscription_type"), } def _response(self): return { - "annotations": self.response.get("annotations"), - "automatedBackupPolicy": AutomatedBackupPolicy().from_response( - self.response.get("automatedBackupPolicy", {}) - ), "backupSource": BackupSource().from_response(self.response.get("backupSource", {})), - "clusterType": self.response.get("clusterType"), - "continuousBackupConfig": ContinuousBackupConfig().from_response( - self.response.get("continuousBackupConfig", {}) - ), + "backupdrBackupSource": BackupdrBackupSource().from_response(self.response.get("backupdrBackupSource", {})), "continuousBackupInfo": ContinuousBackupInfo().from_response(self.response.get("continuousBackupInfo", {})), - "databaseVersion": self.response.get("databaseVersion"), - "displayName": self.response.get("displayName"), - "encryptionConfig": EncryptionConfig().from_response(self.response.get("encryptionConfig", {})), "encryptionInfo": EncryptionInfo().from_response(self.response.get("encryptionInfo", {})), - "etag": self.response.get("etag"), - "initialUser": InitialUser().from_response(self.response.get("initialUser", {})), - "labels": self.response.get("labels"), - "maintenanceUpdatePolicy": MaintenanceUpdatePolicy().from_response( - self.response.get("maintenanceUpdatePolicy", {}) - ), "migrationSource": MigrationSource().from_response(self.response.get("migrationSource", {})), "name": self.response.get("name"), - "networkConfig": NetworkConfig().from_response(self.response.get("networkConfig", {})), - "pscConfig": PscConfig().from_response(self.response.get("pscConfig", {})), "reconciling": self.response.get("reconciling"), - "restoreBackupSource": RestoreBackupSource().from_response(self.response.get("restoreBackupSource", {})), - "restoreContinuousBackupSource": RestoreContinuousBackupSource().from_response( - self.response.get("restoreContinuousBackupSource", {}) - ), - "secondaryConfig": SecondaryConfig().from_response(self.response.get("secondaryConfig", {})), - "subscriptionType": self.response.get("subscriptionType"), "trialMetadata": TrialMetadata().from_response(self.response.get("trialMetadata", {})), "uid": self.response.get("uid"), } @@ -1057,7 +1015,7 @@ def _response(self): def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( state=dict( type="str", @@ -1081,6 +1039,7 @@ def main(): options=dict( kms_key_name=dict( type="str", + no_log=False, ) ), ), @@ -1135,7 +1094,7 @@ def main(): ), ), ), - mutually_exclusive=[["quantity_based_retention", "time_based_retention"]], + mutually_exclusive=[("quantity_based_retention", "time_based_retention")], ), cluster_id=dict( type="str", @@ -1158,6 +1117,7 @@ def main(): options=dict( kms_key_name=dict( type="str", + no_log=False, ) ), ), @@ -1169,6 +1129,23 @@ def main(): database_version=dict( type="str", ), + dataplex_config=dict( + type="dict", + options=dict( + enabled=dict( + type="bool", + required=True, + ) + ), + ), + deletion_policy=dict( + type="str", + default="DEFAULT", + ), + deletion_protection=dict( + type="bool", + default=True, + ), display_name=dict( type="str", ), @@ -1177,6 +1154,7 @@ def main(): options=dict( kms_key_name=dict( type="str", + no_log=False, ) ), ), @@ -1188,13 +1166,13 @@ def main(): options=dict( password=dict( type="str", - required=True, no_log=True, ), user=dict( type="str", ), ), + required_one_of=[("password", "user")], ), labels=dict( type="dict", @@ -1269,6 +1247,28 @@ def main(): ) ), ), + restore_backupdr_backup_source=dict( + type="dict", + options=dict( + backup=dict( + type="str", + required=True, + ) + ), + ), + restore_backupdr_pitr_source=dict( + type="dict", + options=dict( + data_source=dict( + type="str", + required=True, + ), + point_in_time=dict( + type="str", + required=True, + ), + ), + ), restore_continuous_backup_source=dict( type="dict", options=dict( @@ -1291,12 +1291,23 @@ def main(): ) ), ), + skip_await_major_version_upgrade=dict( + type="bool", + default=True, + ), subscription_type=dict( type="str", choices=["TRIAL", "STANDARD"], ), ), - mutually_exclusive=[["restore_backup_source", "restore_continuous_backup_source"]], + mutually_exclusive=[ + ( + "restore_backup_source", + "restore_backupdr_backup_source", + "restore_backupdr_pitr_source", + "restore_continuous_backup_source", + ) + ], ) if not module.params["scopes"]: @@ -1304,10 +1315,11 @@ def main(): state = module.params["state"] changed = False - - op_configs = gcp.ResourceOpConfigs( - { - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://alloydb.googleapis.com/v1/", + base_uri="projects/{project}/locations/{location}/clusters", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/clusters?clusterId={cluster_id}", "async_uri": "{op_id}", @@ -1315,7 +1327,7 @@ def main(): "timeout_minutes": 120, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/clusters/{cluster_id}", "async_uri": "{op_id}", @@ -1323,7 +1335,7 @@ def main(): "timeout_minutes": 120, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/clusters/{cluster_id}", "async_uri": "", @@ -1331,7 +1343,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/clusters/{cluster_id}", "async_uri": "{op_id}", @@ -1339,110 +1351,178 @@ def main(): "timeout_minutes": 120, } ), - } + }, ) - params = gcp.remove_nones_from_dict(module.params) - resource = Alloydb(params, module=module, product="Alloydb", kind="alloydb#cluster") - existing_obj = resource.get(build_link(module, op_configs.read.uri), allow_not_found=True) + request = gcp_v2.remove_nones(module.params) + resource = Alloydb(request, module=module, product="Alloydb", kind="alloydb#cluster", op_configs=op_configs) + + resource._state = state # store the state in the resource object + + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload + + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) + new_obj = {} + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) - if existing_obj is None: + if custom_diff is not None: + is_different = custom_diff + else: + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( + module, + request=gcp_v2.remove_empties(resource.to_request()), + existing=existing_obj, + post=True, + is_different=is_different, + ) + + if gcp_v2.empty(existing_obj): if state == "present": - is_async = op_configs.create.async_uri != "" - create_link = build_link(module, op_configs.create.uri) - create_retries = op_configs.create.timeout - create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(module, "") + op_configs.create.async_uri - # --------- BEGIN custom pre-create code --------- - secondary_config = module.params.get("secondary_config") - cluster_type = module.params.get("cluster_type") - if (cluster_type == "SECONDARY" and secondary_config is None) or ( - cluster_type == "PRIMARY" and secondary_config is not None - ): - module.fail_json(msg="A secondary_config is ONLY needed when cluster_type=SECONDARY") - - # for secondary clusters, the creation link changes slightly - if cluster_type == "SECONDARY": - create_link = create_link.replace("clusters?clusterId", "clusters:createsecondary?cluster_id") - # for primary clusters, require an initial_user - if cluster_type == "PRIMARY": - initial_user = module.params.get("initial_user") - if not initial_user: - module.fail_json(msg="An initial_user is required when cluster_type=PRIMARY") - # --------- END custom pre-create code --------- + gcp_v2.debug(module, action="create") try: - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + # --------- BEGIN create code --------- + create_link: str = "" # give it a chance for pre-create to overload + # --------- BEGIN pre-create custom code --------- + secondary_config = module.params.get("secondary_config") + cluster_type = module.params.get("cluster_type") + if (cluster_type == "SECONDARY" and secondary_config is None) or ( + cluster_type == "PRIMARY" and secondary_config is not None + ): + module.fail_json(msg="A secondary_config is ONLY needed when cluster_type=SECONDARY") + + # for secondary clusters, the creation link changes slightly + if cluster_type == "SECONDARY": + create_link = resource.build_link("create") + create_link = create_link.replace("clusters?clusterId", "clusters:createsecondary?cluster_id") + + # for primary clusters, require an initial_user + if cluster_type == "PRIMARY": + initial_user = module.params.get("initial_user") + if not initial_user: + module.fail_json(msg="An initial_user is required when cluster_type=PRIMARY") + + # --------- END pre-create custom code --------- + if create_link == "": + create_link = resource.build_link("create") + create_retries = op_configs.create.timeout + create_func = getattr(resource, op_configs.create.verb) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) + + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - changed = True + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) + # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) + + changed = True else: pass # nothing to do else: if state == "absent": - is_async = op_configs.delete.async_uri != "" - delete_link = build_link(module, op_configs.delete.uri) - delete_retries = op_configs.delete.timeout - delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(module, "") + op_configs.delete.async_uri - # --------- BEGIN custom pre-delete code --------- - # noop - # --------- END custom pre-delete code --------- + gcp_v2.debug(module, action="delete") try: - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") + delete_retries = op_configs.delete.timeout + delete_func = getattr(resource, op_configs.delete.verb) + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( + module, + msg="Destroying resource", + delete_link=delete_link, + async_uri=delete_async_uri, + ) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) - changed = True + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) + # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) - else: - if resource.diff(existing_obj): - is_async = op_configs.update.async_uri != "" - update_link = build_link(module, op_configs.update.uri) - update_retries = op_configs.update.timeout - update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(module, "") + op_configs.update.async_uri - # --------- BEGIN custom pre-update code --------- - secondary_config = module.params.get("secondary_config") - cluster_type = module.params.get("cluster_type") - db_version = module.params.get("database_version") - # validate you're not sending secondary config when cluster is primary and vice versa - if (cluster_type == "SECONDARY" and secondary_config is None) or ( - cluster_type == "PRIMARY" and secondary_config is not None - ): - module.fail_json(msg="A secondary_config is ONLY needed when cluster_type=SECONDARY") - - # handle cluster promotions primary<->secondary - if cluster_type != existing_obj.get("clusterType"): - module.fail_json(msg="Cluster promotion not supported yet") - - # handle database engine migration - if db_version is not None and db_version != existing_obj.get("databaseVersion"): - module.fail_json(msg="Database migration not supported yet") - - # finally, need to build the updateMask for the fields in our module - update_link += "?updateMask=" + ",".join(resource.dot_fields()) - # --------- END custom pre-update code --------- + changed = True + else: + if is_different: + gcp_v2.debug(module, action="update") try: - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload + # --------- BEGIN pre-update custom code --------- + secondary_config = module.params.get("secondary_config") + cluster_type = module.params.get("cluster_type") + db_version = module.params.get("database_version") + + # validate you're not sending secondary config when cluster is primary and vice versa + if (cluster_type == "SECONDARY" and secondary_config is None) or ( + cluster_type == "PRIMARY" and secondary_config is not None + ): + module.fail_json(msg="A secondary_config is ONLY needed when cluster_type=SECONDARY") + + # handle cluster promotions primary<->secondary + if cluster_type != existing_obj.get("clusterType"): + module.fail_json(msg="Cluster promotion not supported yet") + + # handle database engine migration + if db_version is not None and db_version != existing_obj.get("databaseVersion"): + module.fail_json(msg="Database migration not supported yet") + + # finally, need to build the updateMask for the fields in our module + update_link = resource.build_link("update") + "?updateMask=" + ",".join(resource.dot_fields()) + + # --------- END pre-update custom code --------- + if update_link == "": + update_link = resource.build_link("update") + update_retries = op_configs.update.timeout + update_func = getattr(resource, op_configs.update.verb) + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( + module, + msg="Updating resource", + update_link=update_link, + async_uri=update_async_uri, + ) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) + # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) - changed = resource.diff(new_obj) - new_obj = resource.get(build_link(module, op_configs.read.uri), allow_not_found=True) - new_obj = resource.from_response(new_obj or {}) + changed = True + else: + new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_alloydb_instance.py b/plugins/modules/gcp_alloydb_instance.py index 27a1de6ce..d69ffd04a 100644 --- a/plugins/modules/gcp_alloydb_instance.py +++ b/plugins/modules/gcp_alloydb_instance.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # -# Copyright (C) 2017-2025 Google +# Copyright (C) 2017-2026 Google # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # ---------------------------------------------------------------------------- # @@ -51,10 +51,7 @@ - '''Specifies whether an instance needs to spin up.' - Once the instance is active, the activation policy can be updated to the `NEVER` to stop the instance. - Likewise, the activation policy can be updated to `ALWAYS` to start the instance. - - >- - There are restrictions around when an instance can/cannot be activated (for example, a read pool instance should be stopped before stopping - - primary etc.). + - There are restrictions around when an instance can/cannot be activated (for example, a read pool instance should be stopped before stopping primary etc.). - Please refer to the API documentation for more details. - 'Possible values are: `ACTIVATION_POLICY_UNSPECIFIED`, `ALWAYS`, `NEVER`.''.' type: str @@ -62,6 +59,7 @@ description: - Annotations to allow client tools to store small amount of arbitrary data. - This is distinct from labels. + - '**Note**: This field is non-authoritative, and will only manage the annotations present in your configuration.' type: dict availability_type: choices: @@ -107,8 +105,29 @@ - This field is a reference to a Cluster resource in GCP. - 'It can be specified in two ways: First, you can place a dictionary with key ''name'' matching your resource.' - 'Alternatively, you can add `register: name-of-resource` to a Cluster task and then set this field to `{{ name-of-resource }}`.' + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: dict + connection_pool_config: + description: + - Configuration for Managed Connection Pool. + suboptions: + enabled: + description: + - Whether to enabled Managed Connection Pool. + required: true + type: bool + flags: + description: + - Flags for configuring managed connection pooling when it is enabled. + - These flags will only be set if `connection_pool_config.enabled` is true. + - Please see https://cloud.google.com/alloydb/docs/configure-managed-connection-pooling#configuration-options for a comprehensive list of flags that can be set. + type: dict + pooler_count: + description: + - The number of running poolers per instance. + type: int + type: dict database_flags: description: - Database flags. @@ -124,16 +143,14 @@ type: str gce_zone: description: - - >- - The Compute Engine zone that the instance should serve from, per https://cloud.google.com/compute/docs/regions-zones This can ONLY be - - specified for ZONAL instances. + - The Compute Engine zone that the instance should serve from, per https://cloud.google.com/compute/docs/regions-zones This can ONLY be specified for ZONAL instances. - If present for a REGIONAL instance, an error will be thrown. - If this is absent for a ZONAL instance, instance is created in a random zone with available capacity. type: str instance_id: description: - The ID of the alloydb instance. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str instance_type: @@ -144,11 +161,13 @@ description: - The type of the instance. - If the instance type is SECONDARY, point to the cluster_type of the associated secondary cluster instead of mentioning SECONDARY. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str labels: description: - User-defined labels for the alloydb instance. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict machine_config: description: @@ -170,6 +189,14 @@ description: - Instance level network configuration. suboptions: + allocated_ip_range_override: + description: + - 'Name of the allocated IP range for the private IP AlloyDB instance, for example: "google-managed-services-default".' + - If set, the instance IPs will be created from this allocated range and will override the IP range used by the parent cluster. + - The range name must comply with RFC 1035. + - Specifically, the name must be 1-63 characters long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])?. + - This property is immutable, to change it, you must delete and recreate the resource. + type: str authorized_external_networks: description: - A list of external networks authorized to access this instance. @@ -298,10 +325,7 @@ service_attachment_link: description: - The service attachment created when Private Service Connect (PSC) is enabled for the instance. - - >- - The name of the resource will be in the format of - - `projects//regions//serviceAttachments/`. + - The name of the resource will be in the format of `projects//regions//serviceAttachments/`. type: str type: dict query_insights_config: @@ -356,7 +380,7 @@ - requests >= 2.18.4 - google-auth >= 2.25.1 short_description: Creates a GCP Alloydb.Instance resource -""" +""" # noqa: E501 EXAMPLES = r""" - name: Create a basic primary alloydb instance @@ -370,7 +394,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" -""" +""" # noqa: E501 RETURN = r""" changed: @@ -410,10 +434,7 @@ type: str reconciling: description: - - >- - Set to true if the current state of Instance does not match the user's intended state, and the service is actively updating the resource to - - reconcile them. + - Set to true if the current state of Instance does not match the user's intended state, and the service is actively updating the resource to reconcile them. - This can happen due to user-triggered updates or system actions like failover or maintenance. returned: success type: bool @@ -432,69 +453,57 @@ - Time the Instance was updated in UTC. returned: success type: str -""" +""" # noqa: E501 ################################################################################ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp - -# BEGIN Custom imports -# None -# END Custom imports - +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 -def build_link(module, uri): - params = module.params.copy() - params["cluster"] = gcp.replace_resource_dict(module.params["cluster"], "name") - return "https://alloydb.googleapis.com/v1/" + uri.format(**params) - - -class ClientConnectionConfig(gcp.Resource): +class ClientConnectionConfig(gcp_v2.Resource): def _request(self): return { "requireConnectors": self.request.get("require_connectors"), - "sslConfig": ClientConnectionConfigSslConfig(self.request.get("ssl_config", {})).to_request(), + "sslConfig": gcp_v2.remove_empties( + ClientConnectionConfigSslConfig(self.request.get("ssl_config", {})).to_request() + ), # remove empty values } - def _response(self): + +class ClientConnectionConfigSslConfig(gcp_v2.Resource): + def _request(self): return { - "requireConnectors": self.response.get("requireConnectors"), - "sslConfig": ClientConnectionConfigSslConfig().from_response(self.response.get("sslConfig", {})), + "sslMode": self.request.get("ssl_mode"), } -class ClientConnectionConfigSslConfig(gcp.Resource): +class ConnectionPoolConfig(gcp_v2.Resource): def _request(self): return { - "sslMode": self.request.get("ssl_mode"), + "enabled": self.request.get("enabled"), + "flags": self.request.get("flags"), } def _response(self): return { - "sslMode": self.response.get("sslMode"), + "poolerCount": self.response.get("poolerCount"), } -class MachineConfig(gcp.Resource): +class MachineConfig(gcp_v2.Resource): def _request(self): return { "cpuCount": self.request.get("cpu_count"), "machineType": self.request.get("machine_type"), } - def _response(self): - return { - "cpuCount": self.response.get("cpuCount"), - "machineType": self.response.get("machineType"), - } - -class NetworkConfig(gcp.Resource): +class NetworkConfig(gcp_v2.Resource): def _request(self): return { + "allocatedIpRangeOverride": self.request.get("allocated_ip_range_override"), "authorizedExternalNetworks": [ NetworkConfigAuthorizedExternalNetwork(item).to_request() for item in (self.request.get("authorized_external_networks") or []) @@ -503,30 +512,15 @@ def _request(self): "enablePublicIp": self.request.get("enable_public_ip"), } - def _response(self): - return { - "authorizedExternalNetworks": [ - NetworkConfigAuthorizedExternalNetwork().from_response(item) - for item in (self.response.get("authorizedExternalNetworks") or []) - ], - "enableOutboundPublicIp": self.response.get("enableOutboundPublicIp"), - "enablePublicIp": self.response.get("enablePublicIp"), - } - -class NetworkConfigAuthorizedExternalNetwork(gcp.Resource): +class NetworkConfigAuthorizedExternalNetwork(gcp_v2.Resource): def _request(self): return { "cidrRange": self.request.get("cidr_range"), } - def _response(self): - return { - "cidrRange": self.response.get("cidrRange"), - } - -class ObservabilityConfig(gcp.Resource): +class ObservabilityConfig(gcp_v2.Resource): def _request(self): return { "assistiveExperiencesEnabled": self.request.get("assistive_experiences_enabled"), @@ -540,21 +534,8 @@ def _request(self): "trackWaitEvents": self.request.get("track_wait_events"), } - def _response(self): - return { - "assistiveExperiencesEnabled": self.response.get("assistiveExperiencesEnabled"), - "enabled": self.response.get("enabled"), - "maxQueryStringLength": self.response.get("maxQueryStringLength"), - "preserveComments": self.response.get("preserveComments"), - "queryPlansPerMinute": self.response.get("queryPlansPerMinute"), - "recordApplicationTags": self.response.get("recordApplicationTags"), - "trackActiveQueries": self.response.get("trackActiveQueries"), - "trackWaitEventTypes": self.response.get("trackWaitEventTypes"), - "trackWaitEvents": self.response.get("trackWaitEvents"), - } - -class PscInstanceConfig(gcp.Resource): +class PscInstanceConfig(gcp_v2.Resource): def _request(self): return { "allowedConsumerProjects": self.request.get("allowed_consumer_projects"), @@ -570,21 +551,12 @@ def _request(self): def _response(self): return { - "allowedConsumerProjects": self.response.get("allowedConsumerProjects"), - "pscAutoConnections": [ - PscInstanceConfigPscAutoConnection().from_response(item) - for item in (self.response.get("pscAutoConnections") or []) - ], "pscDnsName": self.response.get("pscDnsName"), - "pscInterfaceConfigs": [ - PscInstanceConfigPscInterfaceConfig().from_response(item) - for item in (self.response.get("pscInterfaceConfigs") or []) - ], "serviceAttachmentLink": self.response.get("serviceAttachmentLink"), } -class PscInstanceConfigPscAutoConnection(gcp.Resource): +class PscInstanceConfigPscAutoConnection(gcp_v2.Resource): def _request(self): return { "consumerNetwork": self.request.get("consumer_network"), @@ -593,27 +565,20 @@ def _request(self): def _response(self): return { - "consumerNetwork": self.response.get("consumerNetwork"), "consumerNetworkStatus": self.response.get("consumerNetworkStatus"), - "consumerProject": self.response.get("consumerProject"), "ipAddress": self.response.get("ipAddress"), "status": self.response.get("status"), } -class PscInstanceConfigPscInterfaceConfig(gcp.Resource): +class PscInstanceConfigPscInterfaceConfig(gcp_v2.Resource): def _request(self): return { "networkAttachmentResource": self.request.get("network_attachment_resource"), } - def _response(self): - return { - "networkAttachmentResource": self.response.get("networkAttachmentResource"), - } - -class QueryInsightsConfig(gcp.Resource): +class QueryInsightsConfig(gcp_v2.Resource): def _request(self): return { "queryPlansPerMinute": self.request.get("query_plans_per_minute"), @@ -622,73 +587,58 @@ def _request(self): "recordClientAddress": self.request.get("record_client_address"), } - def _response(self): - return { - "queryPlansPerMinute": self.response.get("queryPlansPerMinute"), - "queryStringLength": self.response.get("queryStringLength"), - "recordApplicationTags": self.response.get("recordApplicationTags"), - "recordClientAddress": self.response.get("recordClientAddress"), - } - -class ReadPoolConfig(gcp.Resource): +class ReadPoolConfig(gcp_v2.Resource): def _request(self): return { "nodeCount": self.request.get("node_count"), } - def _response(self): - return { - "nodeCount": self.response.get("nodeCount"), - } - -class Alloydb(gcp.Resource): +class Alloydb(gcp_v2.Resource): def _request(self): return { "activationPolicy": self.request.get("activation_policy"), "annotations": self.request.get("annotations"), "availabilityType": self.request.get("availability_type"), - "clientConnectionConfig": ClientConnectionConfig( - self.request.get("client_connection_config", {}) - ).to_request(), + "clientConnectionConfig": gcp_v2.remove_empties( + ClientConnectionConfig(self.request.get("client_connection_config", {})).to_request() + ), # remove empty values + "connectionPoolConfig": gcp_v2.remove_empties( + ConnectionPoolConfig(self.request.get("connection_pool_config", {})).to_request() + ), # remove empty values "databaseFlags": self.request.get("database_flags"), "displayName": self.request.get("display_name"), "gceZone": self.request.get("gce_zone"), "instanceType": self.request.get("instance_type"), "labels": self.request.get("labels"), - "machineConfig": MachineConfig(self.request.get("machine_config", {})).to_request(), - "networkConfig": NetworkConfig(self.request.get("network_config", {})).to_request(), - "observabilityConfig": ObservabilityConfig(self.request.get("observability_config", {})).to_request(), - "pscInstanceConfig": PscInstanceConfig(self.request.get("psc_instance_config", {})).to_request(), - "queryInsightsConfig": QueryInsightsConfig(self.request.get("query_insights_config", {})).to_request(), - "readPoolConfig": ReadPoolConfig(self.request.get("read_pool_config", {})).to_request(), + "machineConfig": gcp_v2.remove_empties( + MachineConfig(self.request.get("machine_config", {})).to_request() + ), # remove empty values + "networkConfig": gcp_v2.remove_empties( + NetworkConfig(self.request.get("network_config", {})).to_request() + ), # remove empty values + "observabilityConfig": gcp_v2.remove_empties( + ObservabilityConfig(self.request.get("observability_config", {})).to_request() + ), # remove empty values + "pscInstanceConfig": gcp_v2.remove_empties( + PscInstanceConfig(self.request.get("psc_instance_config", {})).to_request() + ), # remove empty values + "queryInsightsConfig": gcp_v2.remove_empties( + QueryInsightsConfig(self.request.get("query_insights_config", {})).to_request() + ), # remove empty values + "readPoolConfig": gcp_v2.remove_empties( + ReadPoolConfig(self.request.get("read_pool_config", {})).to_request() + ), # remove empty values } def _response(self): return { - "activationPolicy": self.response.get("activationPolicy"), - "annotations": self.response.get("annotations"), - "availabilityType": self.response.get("availabilityType"), - "clientConnectionConfig": ClientConnectionConfig().from_response( - self.response.get("clientConnectionConfig", {}) - ), "createTime": self.response.get("createTime"), - "databaseFlags": self.response.get("databaseFlags"), - "displayName": self.response.get("displayName"), - "gceZone": self.response.get("gceZone"), - "instanceType": self.response.get("instanceType"), "ipAddress": self.response.get("ipAddress"), - "labels": self.response.get("labels"), - "machineConfig": MachineConfig().from_response(self.response.get("machineConfig", {})), "name": self.response.get("name"), - "networkConfig": NetworkConfig().from_response(self.response.get("networkConfig", {})), - "observabilityConfig": ObservabilityConfig().from_response(self.response.get("observabilityConfig", {})), "outboundPublicIpAddresses": [str(item) for item in (self.response.get("outboundPublicIpAddresses") or [])], - "pscInstanceConfig": PscInstanceConfig().from_response(self.response.get("pscInstanceConfig", {})), "publicIpAddress": self.response.get("publicIpAddress"), - "queryInsightsConfig": QueryInsightsConfig().from_response(self.response.get("queryInsightsConfig", {})), - "readPoolConfig": ReadPoolConfig().from_response(self.response.get("readPoolConfig", {})), "reconciling": self.response.get("reconciling"), "uid": self.response.get("uid"), "updateTime": self.response.get("updateTime"), @@ -703,7 +653,7 @@ def _response(self): def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( state=dict( type="str", @@ -742,6 +692,21 @@ def main(): type="dict", required=True, ), + connection_pool_config=dict( + type="dict", + options=dict( + enabled=dict( + type="bool", + required=True, + ), + flags=dict( + type="dict", + ), + pooler_count=dict( + type="int", + ), + ), + ), database_flags=dict( type="dict", ), @@ -777,9 +742,13 @@ def main(): network_config=dict( type="dict", options=dict( + allocated_ip_range_override=dict( + type="str", + ), authorized_external_networks=dict( type="list", elements="dict", + no_log=False, options=dict( cidr_range=dict( type="str", @@ -793,7 +762,7 @@ def main(): type="bool", ), ), - required_together=[["authorized_external_networks", "enable_public_ip"]], + required_together=[("authorized_external_networks", "enable_public_ip")], ), observability_config=dict( type="dict", @@ -905,10 +874,11 @@ def main(): state = module.params["state"] changed = False - - op_configs = gcp.ResourceOpConfigs( - { - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://alloydb.googleapis.com/v1/", + base_uri="{cluster}/instances", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "{cluster}/instances?instanceId={instance_id}", "async_uri": "{op_id}", @@ -916,7 +886,7 @@ def main(): "timeout_minutes": 120, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "{cluster}/instances/{instance_id}", "async_uri": "{op_id}", @@ -924,10 +894,10 @@ def main(): "timeout_minutes": 120, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{"uri": "{cluster}/instances/{instance_id}", "async_uri": "", "verb": "GET", "timeout_minutes": 0} ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "{cluster}/instances/{instance_id}", "async_uri": "{op_id}", @@ -935,76 +905,136 @@ def main(): "timeout_minutes": 120, } ), - } + }, ) - params = gcp.remove_nones_from_dict(module.params) - resource = Alloydb(params, module=module, product="Alloydb", kind="alloydb#instance") - existing_obj = resource.get(build_link(module, op_configs.read.uri), allow_not_found=True) + request = gcp_v2.remove_nones(module.params) + resource = Alloydb(request, module=module, product="Alloydb", kind="alloydb#instance", op_configs=op_configs) + + resource._state = state # store the state in the resource object + + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + resource.url_params["cluster"] = gcp_v2.resource_ref(module.params["cluster"], "name") + # END massaging ResourceRef properties - if existing_obj is None: + read_link: str = "" # give it a chance for pre-read to overload + + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) + new_obj = {} + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) + + if custom_diff is not None: + is_different = custom_diff + else: + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( + module, + request=gcp_v2.remove_empties(resource.to_request()), + existing=existing_obj, + post=True, + is_different=is_different, + ) + + if gcp_v2.empty(existing_obj): if state == "present": - is_async = op_configs.create.async_uri != "" - create_link = build_link(module, op_configs.create.uri) - create_retries = op_configs.create.timeout - create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(module, "") + op_configs.create.async_uri - # --------- BEGIN custom pre-create code --------- - # 'templates/terraform/pre_create/alloydb_instance.go.tmpl' - # --------- END custom pre-create code --------- + gcp_v2.debug(module, action="create") try: - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + # --------- BEGIN create code --------- + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") + create_retries = op_configs.create.timeout + create_func = getattr(resource, op_configs.create.verb) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) + + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - changed = True + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) + # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) + + changed = True else: pass # nothing to do else: if state == "absent": - is_async = op_configs.delete.async_uri != "" - delete_link = build_link(module, op_configs.delete.uri) - delete_retries = op_configs.delete.timeout - delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(module, "") + op_configs.delete.async_uri - # --------- BEGIN custom pre-delete code --------- - # 'templates/terraform/pre_delete/alloydb_instance.go.tmpl' - # --------- END custom pre-delete code --------- + gcp_v2.debug(module, action="delete") try: - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") + delete_retries = op_configs.delete.timeout + delete_func = getattr(resource, op_configs.delete.verb) + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( + module, + msg="Destroying resource", + delete_link=delete_link, + async_uri=delete_async_uri, + ) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) - changed = True + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) + # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) + + changed = True else: - if resource.diff(existing_obj): - is_async = op_configs.update.async_uri != "" - update_link = build_link(module, op_configs.update.uri) - update_retries = op_configs.update.timeout - update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(module, "") + op_configs.update.async_uri - # --------- BEGIN custom pre-update code --------- - # --------- END custom pre-update code --------- + if is_different: + gcp_v2.debug(module, action="update") try: - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") + update_retries = op_configs.update.timeout + update_func = getattr(resource, op_configs.update.verb) + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( + module, + msg="Updating resource", + update_link=update_link, + async_uri=update_async_uri, + ) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) + # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) - changed = resource.diff(new_obj) - new_obj = resource.get(build_link(module, op_configs.read.uri), allow_not_found=True) - new_obj = resource.from_response(new_obj or {}) + changed = True + else: + new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_alloydb_user.py b/plugins/modules/gcp_alloydb_user.py index 01f0a68e8..ab0dda8e5 100644 --- a/plugins/modules/gcp_alloydb_user.py +++ b/plugins/modules/gcp_alloydb_user.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # -# Copyright (C) 2017-2025 Google +# Copyright (C) 2017-2026 Google # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # ---------------------------------------------------------------------------- # @@ -49,6 +49,7 @@ - This field is a reference to a Cluster resource in GCP. - 'It can be specified in two ways: First, you can place a dictionary with key ''name'' matching your resource.' - 'Alternatively, you can add `register: name-of-resource` to a Cluster task and then set this field to `{{ name-of-resource }}`.' + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: dict database_roles: @@ -71,6 +72,7 @@ user_id: description: - The database role name of the user. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str user_type: @@ -79,6 +81,7 @@ - ALLOYDB_IAM_USER description: - The type of this user. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str requirements: @@ -86,7 +89,7 @@ - requests >= 2.18.4 - google-auth >= 2.25.1 short_description: Creates a GCP Alloydb.User resource -""" +""" # noqa: E501 EXAMPLES = r""" - name: Create user for cluster @@ -99,7 +102,7 @@ - alloydbsuperuser cluster: name: projects/my-project/locations/us-central1/clusters/my-cluster -""" +""" # noqa: E501 RETURN = r""" changed: @@ -115,27 +118,16 @@ description: The current state of the resource. returned: always type: str -""" +""" # noqa: E501 ################################################################################ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 -# BEGIN Custom imports -# noop -# END Custom imports - -def build_link(module, uri): - params = module.params.copy() - params["cluster"] = gcp.replace_resource_dict(module.params["cluster"], "name") - - return "https://alloydb.googleapis.com/v1/" + uri.format(**params) - - -class Alloydb(gcp.Resource): +class Alloydb(gcp_v2.Resource): def _request(self): return { "databaseRoles": [str(item) for item in (self.request.get("database_roles") or [])], @@ -145,10 +137,7 @@ def _request(self): def _response(self): return { - "databaseRoles": [str(item) for item in (self.response.get("databaseRoles") or [])], "name": self.response.get("name"), - "password": self.response.get("password"), - "userType": self.response.get("userType"), } @@ -160,7 +149,7 @@ def _response(self): def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( state=dict( type="str", @@ -196,89 +185,152 @@ def main(): state = module.params["state"] changed = False - - op_configs = gcp.ResourceOpConfigs( - { - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://alloydb.googleapis.com/v1/", + base_uri="{cluster}/users", + configs={ + "create": gcp_v2.ResourceOpConfig( **{"uri": "{cluster}/users?userId={user_id}", "async_uri": "", "verb": "POST", "timeout_minutes": 20} ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{"uri": "{cluster}/users/{user_id}", "async_uri": "", "verb": "DELETE", "timeout_minutes": 20} ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{"uri": "{cluster}/users/{user_id}", "async_uri": "", "verb": "GET", "timeout_minutes": 0} ), - "update": gcp.ResourceOpConfig( - **{"uri": "{cluster}/users?userId={user_id}", "async_uri": "", "verb": "POST", "timeout_minutes": 20} + "update": gcp_v2.ResourceOpConfig( + **{"uri": "{cluster}/users/{user_id}", "async_uri": "", "verb": "PATCH", "timeout_minutes": 20} ), - } + }, ) - params = gcp.remove_nones_from_dict(module.params) - resource = Alloydb(params, module=module, product="Alloydb", kind="alloydb#user") - existing_obj = resource.get(build_link(module, op_configs.read.uri), allow_not_found=True) + request = gcp_v2.remove_nones(module.params) + resource = Alloydb(request, module=module, product="Alloydb", kind="alloydb#user", op_configs=op_configs) + + resource._state = state # store the state in the resource object + + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + resource.url_params["cluster"] = gcp_v2.resource_ref(module.params["cluster"], "name") + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload + + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) + new_obj = {} + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) - if existing_obj is None: + if custom_diff is not None: + is_different = custom_diff + else: + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( + module, + request=gcp_v2.remove_empties(resource.to_request()), + existing=existing_obj, + post=True, + is_different=is_different, + ) + + if gcp_v2.empty(existing_obj): if state == "present": - is_async = op_configs.create.async_uri != "" - create_link = build_link(module, op_configs.create.uri) - create_retries = op_configs.create.timeout - create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(module, "") + op_configs.create.async_uri - # --------- BEGIN custom pre-create code --------- - # --------- END custom pre-create code --------- + gcp_v2.debug(module, action="create") try: - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + # --------- BEGIN create code --------- + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") + create_retries = op_configs.create.timeout + create_func = getattr(resource, op_configs.create.verb) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) + + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - changed = True + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) + # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) + + changed = True else: pass # nothing to do else: if state == "absent": - is_async = op_configs.delete.async_uri != "" - delete_link = build_link(module, op_configs.delete.uri) - delete_retries = op_configs.delete.timeout - delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(module, "") + op_configs.delete.async_uri - # --------- BEGIN custom pre-delete code --------- - # --------- END custom pre-delete code --------- + gcp_v2.debug(module, action="delete") try: - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") + delete_retries = op_configs.delete.timeout + delete_func = getattr(resource, op_configs.delete.verb) + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( + module, + msg="Destroying resource", + delete_link=delete_link, + async_uri=delete_async_uri, + ) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) - changed = True + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) + # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) + + changed = True else: - if resource.diff(existing_obj): - is_async = op_configs.update.async_uri != "" - update_link = build_link(module, op_configs.update.uri) - update_retries = op_configs.update.timeout - update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(module, "") + op_configs.update.async_uri - # --------- BEGIN custom pre-update code --------- - # --------- END custom pre-update code --------- + if is_different: + gcp_v2.debug(module, action="update") try: - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") + update_retries = op_configs.update.timeout + update_func = getattr(resource, op_configs.update.verb) + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( + module, + msg="Updating resource", + update_link=update_link, + async_uri=update_async_uri, + ) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) + # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) - changed = resource.diff(new_obj) - new_obj = resource.get(build_link(module, op_configs.read.uri), allow_not_found=True) - new_obj = resource.from_response(new_obj or {}) + changed = True + else: + new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_cloudbuild_trigger.py b/plugins/modules/gcp_cloudbuild_trigger.py index b36f6a0f6..d2a3f7279 100644 --- a/plugins/modules/gcp_cloudbuild_trigger.py +++ b/plugins/modules/gcp_cloudbuild_trigger.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # -# Copyright (C) 2017-2025 Google +# Copyright (C) 2017-2026 Google # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # ---------------------------------------------------------------------------- # @@ -116,10 +116,7 @@ description: - Slug of the repository. - A repository slug is a URL-friendly version of a repository name, automatically generated by Bitbucket for use in the URL. - - >- - For example, if the repository name is 'test repo', in the URL it would become 'test-repo' as in - - https://mybitbucket.server/projects/TEST/repos/test-repo. + - For example, if the repository name is 'test repo', in the URL it would become 'test-repo' as in https://mybitbucket.server/projects/TEST/repos/test-repo. required: true type: str type: dict @@ -164,10 +161,7 @@ type: str repository: description: - - >- - Artifact Registry repository, in the form "https://$REGION-maven.pkg.dev/$PROJECT/$REPOSITORY" Artifact in the workspace specified by path - - will be uploaded to Artifact Registry with this location as a prefix. + - Artifact Registry repository, in the form "https://$REGION-maven.pkg.dev/$PROJECT/$REPOSITORY" Artifact in the workspace specified by path will be uploaded to Artifact Registry with this location as a prefix. type: str version: description: @@ -189,19 +183,13 @@ type: str repository: description: - - >- - Artifact Registry repository, in the form "https://$REGION-npm.pkg.dev/$PROJECT/$REPOSITORY" Npm package in the workspace specified by path - - will be zipped and uploaded to Artifact Registry with this location as a prefix. + - Artifact Registry repository, in the form "https://$REGION-npm.pkg.dev/$PROJECT/$REPOSITORY" Npm package in the workspace specified by path will be zipped and uploaded to Artifact Registry with this location as a prefix. type: str type: list objects: description: - A list of objects to be uploaded to Cloud Storage upon successful completion of all build steps. - - >- - Files in the workspace matching specified paths globs will be uploaded to the Cloud Storage location using the builder service account's - - credentials. + - Files in the workspace matching specified paths globs will be uploaded to the Cloud Storage location using the builder service account's credentials. - The location and generation of the uploaded objects will be stored in the Build resource's results field. - If any objects fail to be pushed, the build is marked FAILURE. suboptions: @@ -250,10 +238,7 @@ type: list repository: description: - - >- - Artifact Registry repository, in the form "https://$REGION-python.pkg.dev/$PROJECT/$REPOSITORY" Files in the workspace matching any path - - pattern will be uploaded to Artifact Registry with this location as a prefix. + - Artifact Registry repository, in the form "https://$REGION-python.pkg.dev/$PROJECT/$REPOSITORY" Files in the workspace matching any path pattern will be uploaded to Artifact Registry with this location as a prefix. type: str type: list type: dict @@ -517,24 +502,15 @@ description: - A list of arguments that will be presented to the step when it is started. - If the image used to run the step's container has an entrypoint, the args are used as arguments to that entrypoint. - - >- - If the image does not define an entrypoint, the first element in args is used as the entrypoint, and the remainder will be used as - - arguments. + - If the image does not define an entrypoint, the first element in args is used as the entrypoint, and the remainder will be used as arguments. elements: str type: list dir: description: - Working directory to use when running this step's container. - If this value is a relative path, it is relative to the build's working directory. - - >- - If this value is absolute, it may be outside the build's working directory, in which case the contents of the path may not be persisted - - across build step executions, unless a `volume` for that path is specified. - - >- - If the build specifies a `RepoSource` with `dir` and a step with a `dir`, which specifies an absolute path, the `RepoSource` `dir` is - - ignored for the step's execution. + - If this value is absolute, it may be outside the build's working directory, in which case the contents of the path may not be persisted across build step executions, unless a `volume` for that path is specified. + - If the build specifies a `RepoSource` with `dir` and a step with a `dir`, which specifies an absolute path, the `RepoSource` `dir` is ignored for the step's execution. type: str entrypoint: description: @@ -556,18 +532,9 @@ - The name of the container image that will run this particular build step. - If the image is available in the host's Docker daemon's cache, it will be run directly. - If not, the host will attempt to pull the image first, using the builder service account's credentials if necessary. - - >- - The Docker daemon's cache will already have the latest versions of all of the officially supported build steps (see - - https://github.com/GoogleCloudPlatform/cloud-builders for images and examples). - - >- - The Docker daemon will also have cached many of the layers for some popular images, like "ubuntu", "debian", but they will be refreshed at - - the time you attempt to use them. - - >- - If you built an image in a previous build step, it will be stored in the host's Docker daemon's cache and is available to use as the name - - for a later build step. + - The Docker daemon's cache will already have the latest versions of all of the officially supported build steps (see https://github.com/GoogleCloudPlatform/cloud-builders for images and examples). + - The Docker daemon will also have cached many of the layers for some popular images, like "ubuntu", "debian", but they will be refreshed at the time you attempt to use them. + - If you built an image in a previous build step, it will be stored in the host's Docker daemon's cache and is available to use as the name for a later build step. required: true type: str script: @@ -645,6 +612,66 @@ description: - Human-readable description of the trigger. type: str + developer_connect_event_config: + description: + - Configuration for triggers that respond to Developer Connect events. + suboptions: + git_repository_link: + description: + - The Developer Connect Git repository link, formatted as `projects/*/locations/*/connections/*/gitRepositoryLink/*`. + required: true + type: str + git_repository_link_type: + choices: + - GIT_REPOSITORY_LINK_TYPE_UNSPECIFIED + - GITHUB + - GITHUB_ENTERPRISE + - GITLAB + - GITLAB_ENTERPRISE + - BITBUCKET_DATA_CENTER + - BITBUCKET_CLOUD + description: + - The type of DeveloperConnect GitRepositoryLink. + type: str + pull_request: + description: + - Filter to match changes in pull requests. + suboptions: + branch: + description: + - Regex of branches to match. + type: str + comment_control: + choices: + - COMMENTS_DISABLED + - COMMENTS_ENABLED + - COMMENTS_ENABLED_FOR_EXTERNAL_CONTRIBUTORS_ONLY + description: + - Configure builds to run whether a repository owner or collaborator need to comment `/gcbrun`. + type: str + invert_regex: + description: + - If true, branches that do NOT match the git_ref will trigger a build. + type: bool + type: dict + push: + description: + - Filter to match changes in refs like branches and tags. + suboptions: + branch: + description: + - Regex of branches to match. + type: str + invert_regex: + description: + - If true, only trigger a build if the revision regex does NOT match the git_ref regex. + type: bool + tag: + description: + - Regex of tags to match. + type: str + type: dict + type: dict disabled: description: - Whether the trigger is disabled or not. @@ -702,10 +729,7 @@ revision: description: - The branch, tag, arbitrary ref, or SHA version of the repo to use when resolving the filename (optional). - - >- - This field respects the same syntax/resolution as described here: https://git-scm.com/docs/gitrevisions If unspecified, the revision from - - which the trigger invocation originated is assumed to be the revision from which to read the specified path. + - 'This field respects the same syntax/resolution as described here: https://git-scm.com/docs/gitrevisions If unspecified, the revision from which the trigger invocation originated is assumed to be the revision from which to read the specified path.' type: str uri: description: @@ -716,7 +740,6 @@ github: description: - Describes the configuration of a trigger that creates a build whenever a GitHub event is received. - - One of `trigger_template`, `github`, `pubsub_config` or `webhook_config` must be provided. suboptions: enterprise_config_resource_name: description: @@ -796,14 +819,8 @@ included_files: description: - ignoredFiles and includedFiles are file glob matches using https://golang.org/pkg/path/filepath/#Match extended with support for `**`. - - >- - If any of the files altered in the commit pass the ignoredFiles filter and includedFiles is empty, then as far as this filter is concerned, - - we should trigger the build. - - >- - If any of the files altered in the commit pass the ignoredFiles filter and includedFiles is not empty, then we make sure that at least one - - of those files matches a includedFiles glob. + - If any of the files altered in the commit pass the ignoredFiles filter and includedFiles is empty, then as far as this filter is concerned, we should trigger the build. + - If any of the files altered in the commit pass the ignoredFiles filter and includedFiles is not empty, then we make sure that at least one of those files matches a includedFiles glob. - If not, then we do not trigger a build. elements: str type: list @@ -812,6 +829,7 @@ description: - The [Cloud Build location](https://cloud.google.com/build/docs/locations) for the trigger. - If not specified, "global" is used. + - This property is immutable, to change it, you must delete and recreate the resource. type: str name: description: @@ -821,7 +839,6 @@ pubsub_config: description: - PubsubConfig describes the configuration of a trigger that creates a build whenever a Pub/Sub message is published. - - One of `trigger_template`, `github`, `pubsub_config` `webhook_config` or `source_to_build` must be provided. suboptions: service_account_email: description: @@ -905,7 +922,6 @@ - This field is used only for those triggers that do not respond to SCM events. - Triggers that respond to such events build source at whatever commit caused the event. - This field is currently only used by Webhook, Pub/Sub, Manual, and Cron triggers. - - One of `trigger_template`, `github`, `pubsub_config` `webhook_config` or `source_to_build` must be provided. suboptions: bitbucket_server_config: description: @@ -967,7 +983,6 @@ - Template describing the types of source changes to trigger a build. - Branch and tag names in trigger templates are interpreted as regular expressions. - Any branch or tag change that matches that regular expression will trigger a build. - - One of `trigger_template`, `github`, `pubsub_config`, `webhook_config` or `source_to_build` must be provided. suboptions: branch_name: description: @@ -1011,7 +1026,6 @@ webhook_config: description: - WebhookConfig describes the configuration of a trigger that creates a build whenever a webhook is sent to a trigger's webhook URL. - - One of `trigger_template`, `github`, `pubsub_config` `webhook_config` or `source_to_build` must be provided. suboptions: secret: description: @@ -1029,7 +1043,7 @@ - requests >= 2.18.4 - google-auth >= 2.25.1 short_description: Creates a GCP CloudBuild.Trigger resource -""" +""" # noqa: E501 EXAMPLES = r""" - name: Trigger build with filename @@ -1168,7 +1182,7 @@ push: branch: main repository: "{{ repository_name }}" -""" +""" # noqa: E501 RETURN = r""" changed: @@ -1189,60 +1203,41 @@ - The unique identifier for the trigger. returned: success type: str -""" +""" # noqa: E501 ################################################################################ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import copy +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports # END Custom imports -def build_link(module, uri): - params = module.params.copy() - - return "https://cloudbuild.googleapis.com/v1/" + uri.format(**params) - - -class ApprovalConfig(gcp.Resource): +class ApprovalConfig(gcp_v2.Resource): def _request(self): return { "approvalRequired": self.request.get("approval_required"), } - def _response(self): - return { - "approvalRequired": self.response.get("approvalRequired"), - } - -class BitbucketServerTriggerConfig(gcp.Resource): +class BitbucketServerTriggerConfig(gcp_v2.Resource): def _request(self): return { "bitbucketServerConfigResource": self.request.get("bitbucket_server_config_resource"), "projectKey": self.request.get("project_key"), - "pullRequest": BitbucketServerTriggerConfigPullRequest(self.request.get("pull_request", {})).to_request(), - "push": BitbucketServerTriggerConfigPush(self.request.get("push", {})).to_request(), + "pullRequest": gcp_v2.remove_empties( + BitbucketServerTriggerConfigPullRequest(self.request.get("pull_request", {})).to_request() + ), # remove empty values + "push": gcp_v2.remove_empties( + BitbucketServerTriggerConfigPush(self.request.get("push", {})).to_request() + ), # remove empty values "repoSlug": self.request.get("repo_slug"), } - def _response(self): - return { - "bitbucketServerConfigResource": self.response.get("bitbucketServerConfigResource"), - "projectKey": self.response.get("projectKey"), - "pullRequest": BitbucketServerTriggerConfigPullRequest().from_response( - self.response.get("pullRequest", {}) - ), - "push": BitbucketServerTriggerConfigPush().from_response(self.response.get("push", {})), - "repoSlug": self.response.get("repoSlug"), - } - -class BitbucketServerTriggerConfigPullRequest(gcp.Resource): +class BitbucketServerTriggerConfigPullRequest(gcp_v2.Resource): def _request(self): return { "branch": self.request.get("branch"), @@ -1250,15 +1245,8 @@ def _request(self): "invertRegex": self.request.get("invert_regex"), } - def _response(self): - return { - "branch": self.response.get("branch"), - "commentControl": self.response.get("commentControl"), - "invertRegex": self.response.get("invertRegex"), - } - -class BitbucketServerTriggerConfigPush(gcp.Resource): +class BitbucketServerTriggerConfigPush(gcp_v2.Resource): def _request(self): return { "branch": self.request.get("branch"), @@ -1266,49 +1254,34 @@ def _request(self): "tag": self.request.get("tag"), } - def _response(self): - return { - "branch": self.response.get("branch"), - "invertRegex": self.response.get("invertRegex"), - "tag": self.response.get("tag"), - } - -class Build(gcp.Resource): +class Build(gcp_v2.Resource): def _request(self): return { - "artifacts": BuildArtifacts(self.request.get("artifacts", {})).to_request(), - "availableSecrets": BuildAvailableSecrets(self.request.get("available_secrets", {})).to_request(), + "artifacts": gcp_v2.remove_empties( + BuildArtifacts(self.request.get("artifacts", {})).to_request() + ), # remove empty values + "availableSecrets": gcp_v2.remove_empties( + BuildAvailableSecrets(self.request.get("available_secrets", {})).to_request() + ), # remove empty values "images": self.request.get("images"), "logsBucket": self.request.get("logs_bucket"), - "options": BuildOptions(self.request.get("options", {})).to_request(), + "options": gcp_v2.remove_empties( + BuildOptions(self.request.get("options", {})).to_request() + ), # remove empty values "queueTtl": self.request.get("queue_ttl"), "secrets": [BuildSecret(item).to_request() for item in (self.request.get("secret") or [])], - "source": BuildSource(self.request.get("source", {})).to_request(), + "source": gcp_v2.remove_empties( + BuildSource(self.request.get("source", {})).to_request() + ), # remove empty values "steps": [BuildStep(item).to_request() for item in (self.request.get("step") or [])], "substitutions": self.request.get("substitutions"), "tags": self.request.get("tags"), "timeout": self.request.get("timeout"), } - def _response(self): - return { - "artifacts": BuildArtifacts().from_response(self.response.get("artifacts", {})), - "availableSecrets": BuildAvailableSecrets().from_response(self.response.get("availableSecrets", {})), - "images": self.response.get("images"), - "logsBucket": self.response.get("logsBucket"), - "options": BuildOptions().from_response(self.response.get("options", {})), - "queueTtl": self.response.get("queueTtl"), - "secrets": [BuildSecret().from_response(item) for item in (self.response.get("secrets") or [])], - "source": BuildSource().from_response(self.response.get("source", {})), - "steps": [BuildStep().from_response(item) for item in (self.response.get("steps") or [])], - "substitutions": self.response.get("substitutions"), - "tags": self.response.get("tags"), - "timeout": self.response.get("timeout"), - } - -class BuildArtifacts(gcp.Resource): +class BuildArtifacts(gcp_v2.Resource): def _request(self): return { "images": self.request.get("images"), @@ -1318,31 +1291,16 @@ def _request(self): "npmPackages": [ BuildArtifactsNpmPackage(item).to_request() for item in (self.request.get("npm_packages") or []) ], - "objects": BuildArtifactsObjects(self.request.get("objects", {})).to_request(), + "objects": gcp_v2.remove_empties( + BuildArtifactsObjects(self.request.get("objects", {})).to_request() + ), # remove empty values "pythonPackages": [ BuildArtifactsPythonPackage(item).to_request() for item in (self.request.get("python_packages") or []) ], } - def _response(self): - return { - "images": self.response.get("images"), - "mavenArtifacts": [ - BuildArtifactsMavenArtifact().from_response(item) - for item in (self.response.get("mavenArtifacts") or []) - ], - "npmPackages": [ - BuildArtifactsNpmPackage().from_response(item) for item in (self.response.get("npmPackages") or []) - ], - "objects": BuildArtifactsObjects().from_response(self.response.get("objects", {})), - "pythonPackages": [ - BuildArtifactsPythonPackage().from_response(item) - for item in (self.response.get("pythonPackages") or []) - ], - } - -class BuildArtifactsMavenArtifact(gcp.Resource): +class BuildArtifactsMavenArtifact(gcp_v2.Resource): def _request(self): return { "artifactId": self.request.get("artifact_id"), @@ -1352,31 +1310,16 @@ def _request(self): "version": self.request.get("version"), } - def _response(self): - return { - "artifactId": self.response.get("artifactId"), - "groupId": self.response.get("groupId"), - "path": self.response.get("path"), - "repository": self.response.get("repository"), - "version": self.response.get("version"), - } - -class BuildArtifactsNpmPackage(gcp.Resource): +class BuildArtifactsNpmPackage(gcp_v2.Resource): def _request(self): return { "packagePath": self.request.get("package_path"), "repository": self.request.get("repository"), } - def _response(self): - return { - "packagePath": self.response.get("packagePath"), - "repository": self.response.get("repository"), - } - -class BuildArtifactsObjects(gcp.Resource): +class BuildArtifactsObjects(gcp_v2.Resource): def _request(self): return { "location": self.request.get("location"), @@ -1385,41 +1328,27 @@ def _request(self): def _response(self): return { - "location": self.response.get("location"), - "paths": self.response.get("paths"), "timing": BuildArtifactsObjectsTiming().from_response(self.response.get("timing", {})), } -class BuildArtifactsObjectsTiming(gcp.Resource): +class BuildArtifactsObjectsTiming(gcp_v2.Resource): def _request(self): return { "endTime": self.request.get("end_time"), "startTime": self.request.get("start_time"), } - def _response(self): - return { - "endTime": self.response.get("endTime"), - "startTime": self.response.get("startTime"), - } - -class BuildArtifactsPythonPackage(gcp.Resource): +class BuildArtifactsPythonPackage(gcp_v2.Resource): def _request(self): return { "paths": self.request.get("paths"), "repository": self.request.get("repository"), } - def _response(self): - return { - "paths": self.response.get("paths"), - "repository": self.response.get("repository"), - } - -class BuildAvailableSecrets(gcp.Resource): +class BuildAvailableSecrets(gcp_v2.Resource): def _request(self): return { "secretManager": [ @@ -1428,30 +1357,16 @@ def _request(self): ], } - def _response(self): - return { - "secretManager": [ - BuildAvailableSecretsSecretManager().from_response(item) - for item in (self.response.get("secretManager") or []) - ], - } - -class BuildAvailableSecretsSecretManager(gcp.Resource): +class BuildAvailableSecretsSecretManager(gcp_v2.Resource): def _request(self): return { "env": self.request.get("env"), "versionName": self.request.get("version_name"), } - def _response(self): - return { - "env": self.response.get("env"), - "versionName": self.response.get("versionName"), - } - -class BuildOptions(gcp.Resource): +class BuildOptions(gcp_v2.Resource): def _request(self): return { "diskSizeGb": self.request.get("disk_size_gb"), @@ -1468,66 +1383,36 @@ def _request(self): "workerPool": self.request.get("worker_pool"), } - def _response(self): - return { - "diskSizeGb": self.response.get("diskSizeGb"), - "dynamicSubstitutions": self.response.get("dynamicSubstitutions"), - "env": self.response.get("env"), - "logStreamingOption": self.response.get("logStreamingOption"), - "logging": self.response.get("logging"), - "machineType": self.response.get("machineType"), - "requestedVerifyOption": self.response.get("requestedVerifyOption"), - "secretEnv": self.response.get("secretEnv"), - "sourceProvenanceHash": self.response.get("sourceProvenanceHash"), - "substitutionOption": self.response.get("substitutionOption"), - "volumes": [BuildOptionsVolume().from_response(item) for item in (self.response.get("volumes") or [])], - "workerPool": self.response.get("workerPool"), - } - -class BuildOptionsVolume(gcp.Resource): +class BuildOptionsVolume(gcp_v2.Resource): def _request(self): return { "name": self.request.get("name"), "path": self.request.get("path"), } - def _response(self): - return { - "name": self.response.get("name"), - "path": self.response.get("path"), - } - -class BuildSecret(gcp.Resource): +class BuildSecret(gcp_v2.Resource): def _request(self): return { "kmsKeyName": self.request.get("kms_key_name"), "secretEnv": self.request.get("secret_env"), } - def _response(self): - return { - "kmsKeyName": self.response.get("kmsKeyName"), - "secretEnv": self.response.get("secretEnv"), - } - -class BuildSource(gcp.Resource): +class BuildSource(gcp_v2.Resource): def _request(self): return { - "repoSource": BuildSourceRepoSource(self.request.get("repo_source", {})).to_request(), - "storageSource": BuildSourceStorageSource(self.request.get("storage_source", {})).to_request(), + "repoSource": gcp_v2.remove_empties( + BuildSourceRepoSource(self.request.get("repo_source", {})).to_request() + ), # remove empty values + "storageSource": gcp_v2.remove_empties( + BuildSourceStorageSource(self.request.get("storage_source", {})).to_request() + ), # remove empty values } - def _response(self): - return { - "repoSource": BuildSourceRepoSource().from_response(self.response.get("repoSource", {})), - "storageSource": BuildSourceStorageSource().from_response(self.response.get("storageSource", {})), - } - -class BuildSourceRepoSource(gcp.Resource): +class BuildSourceRepoSource(gcp_v2.Resource): def _request(self): return { "branchName": self.request.get("branch_name"), @@ -1540,20 +1425,8 @@ def _request(self): "tagName": self.request.get("tag_name"), } - def _response(self): - return { - "branchName": self.response.get("branchName"), - "commitSha": self.response.get("commitSha"), - "dir": self.response.get("dir"), - "invertRegex": self.response.get("invertRegex"), - "projectId": self.response.get("projectId"), - "repoName": self.response.get("repoName"), - "substitutions": self.response.get("substitutions"), - "tagName": self.response.get("tagName"), - } - -class BuildSourceStorageSource(gcp.Resource): +class BuildSourceStorageSource(gcp_v2.Resource): def _request(self): return { "bucket": self.request.get("bucket"), @@ -1561,15 +1434,8 @@ def _request(self): "object": self.request.get("object"), } - def _response(self): - return { - "bucket": self.response.get("bucket"), - "generation": self.response.get("generation"), - "object": self.response.get("object"), - } - -class BuildStep(gcp.Resource): +class BuildStep(gcp_v2.Resource): def _request(self): return { "allowExitCodes": self.request.get("allow_exit_codes"), @@ -1588,40 +1454,52 @@ def _request(self): "waitFor": self.request.get("wait_for"), } - def _response(self): + +class BuildStepVolume(gcp_v2.Resource): + def _request(self): return { - "allowExitCodes": self.response.get("allowExitCodes"), - "allowFailure": self.response.get("allowFailure"), - "args": self.response.get("args"), - "dir": self.response.get("dir"), - "entrypoint": self.response.get("entrypoint"), - "env": self.response.get("env"), - "id": self.response.get("id"), - "name": self.response.get("name"), - "script": self.response.get("script"), - "secretEnv": self.response.get("secretEnv"), - "timeout": self.response.get("timeout"), - "timing": self.response.get("timing"), - "volumes": [BuildStepVolume().from_response(item) for item in (self.response.get("volumes") or [])], - "waitFor": self.response.get("waitFor"), + "name": self.request.get("name"), + "path": self.request.get("path"), } -class BuildStepVolume(gcp.Resource): +class DeveloperConnectEventConfig(gcp_v2.Resource): def _request(self): return { - "name": self.request.get("name"), - "path": self.request.get("path"), + "gitRepositoryLink": self.request.get("git_repository_link"), + "pullRequest": gcp_v2.remove_empties( + DeveloperConnectEventConfigPullRequest(self.request.get("pull_request", {})).to_request() + ), # remove empty values + "push": gcp_v2.remove_empties( + DeveloperConnectEventConfigPush(self.request.get("push", {})).to_request() + ), # remove empty values } def _response(self): return { - "name": self.response.get("name"), - "path": self.response.get("path"), + "gitRepositoryLinkType": self.response.get("gitRepositoryLinkType"), } -class GitFileSource(gcp.Resource): +class DeveloperConnectEventConfigPullRequest(gcp_v2.Resource): + def _request(self): + return { + "branch": self.request.get("branch"), + "commentControl": self.request.get("comment_control"), + "invertRegex": self.request.get("invert_regex"), + } + + +class DeveloperConnectEventConfigPush(gcp_v2.Resource): + def _request(self): + return { + "branch": self.request.get("branch"), + "invertRegex": self.request.get("invert_regex"), + "tag": self.request.get("tag"), + } + + +class GitFileSource(gcp_v2.Resource): def _request(self): return { "bitbucketServerConfig": self.request.get("bitbucket_server_config"), @@ -1633,39 +1511,21 @@ def _request(self): "uri": self.request.get("uri"), } - def _response(self): - return { - "bitbucketServerConfig": self.response.get("bitbucketServerConfig"), - "githubEnterpriseConfig": self.response.get("githubEnterpriseConfig"), - "path": self.response.get("path"), - "repoType": self.response.get("repoType"), - "repository": self.response.get("repository"), - "revision": self.response.get("revision"), - "uri": self.response.get("uri"), - } - -class Github(gcp.Resource): +class Github(gcp_v2.Resource): def _request(self): return { "enterpriseConfigResourceName": self.request.get("enterprise_config_resource_name"), "name": self.request.get("name"), "owner": self.request.get("owner"), - "pullRequest": GithubPullRequest(self.request.get("pull_request", {})).to_request(), - "push": GithubPush(self.request.get("push", {})).to_request(), - } - - def _response(self): - return { - "enterpriseConfigResourceName": self.response.get("enterpriseConfigResourceName"), - "name": self.response.get("name"), - "owner": self.response.get("owner"), - "pullRequest": GithubPullRequest().from_response(self.response.get("pullRequest", {})), - "push": GithubPush().from_response(self.response.get("push", {})), + "pullRequest": gcp_v2.remove_empties( + GithubPullRequest(self.request.get("pull_request", {})).to_request() + ), # remove empty values + "push": gcp_v2.remove_empties(GithubPush(self.request.get("push", {})).to_request()), # remove empty values } -class GithubPullRequest(gcp.Resource): +class GithubPullRequest(gcp_v2.Resource): def _request(self): return { "branch": self.request.get("branch"), @@ -1673,15 +1533,8 @@ def _request(self): "invertRegex": self.request.get("invert_regex"), } - def _response(self): - return { - "branch": self.response.get("branch"), - "commentControl": self.response.get("commentControl"), - "invertRegex": self.response.get("invertRegex"), - } - -class GithubPush(gcp.Resource): +class GithubPush(gcp_v2.Resource): def _request(self): return { "branch": self.request.get("branch"), @@ -1689,47 +1542,35 @@ def _request(self): "tag": self.request.get("tag"), } - def _response(self): - return { - "branch": self.response.get("branch"), - "invertRegex": self.response.get("invertRegex"), - "tag": self.response.get("tag"), - } - -class PubsubConfig(gcp.Resource): +class PubsubConfig(gcp_v2.Resource): def _request(self): return { - "service_account_email": self.request.get("service_account_email"), + "serviceAccountEmail": self.request.get("service_account_email"), "topic": self.request.get("topic"), } def _response(self): return { - "service_account_email": self.response.get("service_account_email"), "state": self.response.get("state"), "subscription": self.response.get("subscription"), - "topic": self.response.get("topic"), } -class RepositoryEventConfig(gcp.Resource): +class RepositoryEventConfig(gcp_v2.Resource): def _request(self): return { - "pullRequest": RepositoryEventConfigPullRequest(self.request.get("pull_request", {})).to_request(), - "push": RepositoryEventConfigPush(self.request.get("push", {})).to_request(), + "pullRequest": gcp_v2.remove_empties( + RepositoryEventConfigPullRequest(self.request.get("pull_request", {})).to_request() + ), # remove empty values + "push": gcp_v2.remove_empties( + RepositoryEventConfigPush(self.request.get("push", {})).to_request() + ), # remove empty values "repository": self.request.get("repository"), } - def _response(self): - return { - "pullRequest": RepositoryEventConfigPullRequest().from_response(self.response.get("pullRequest", {})), - "push": RepositoryEventConfigPush().from_response(self.response.get("push", {})), - "repository": self.response.get("repository"), - } - -class RepositoryEventConfigPullRequest(gcp.Resource): +class RepositoryEventConfigPullRequest(gcp_v2.Resource): def _request(self): return { "branch": self.request.get("branch"), @@ -1737,15 +1578,8 @@ def _request(self): "invertRegex": self.request.get("invert_regex"), } - def _response(self): - return { - "branch": self.response.get("branch"), - "commentControl": self.response.get("commentControl"), - "invertRegex": self.response.get("invertRegex"), - } - -class RepositoryEventConfigPush(gcp.Resource): +class RepositoryEventConfigPush(gcp_v2.Resource): def _request(self): return { "branch": self.request.get("branch"), @@ -1753,15 +1587,8 @@ def _request(self): "tag": self.request.get("tag"), } - def _response(self): - return { - "branch": self.response.get("branch"), - "invertRegex": self.response.get("invertRegex"), - "tag": self.response.get("tag"), - } - -class SourceToBuild(gcp.Resource): +class SourceToBuild(gcp_v2.Resource): def _request(self): return { "bitbucketServerConfig": self.request.get("bitbucket_server_config"), @@ -1772,18 +1599,8 @@ def _request(self): "uri": self.request.get("uri"), } - def _response(self): - return { - "bitbucketServerConfig": self.response.get("bitbucketServerConfig"), - "githubEnterpriseConfig": self.response.get("githubEnterpriseConfig"), - "ref": self.response.get("ref"), - "repoType": self.response.get("repoType"), - "repository": self.response.get("repository"), - "uri": self.response.get("uri"), - } - -class TriggerTemplate(gcp.Resource): +class TriggerTemplate(gcp_v2.Resource): def _request(self): return { "branchName": self.request.get("branch_name"), @@ -1795,19 +1612,8 @@ def _request(self): "tagName": self.request.get("tag_name"), } - def _response(self): - return { - "branchName": self.response.get("branchName"), - "commitSha": self.response.get("commitSha"), - "dir": self.response.get("dir"), - "invertRegex": self.response.get("invertRegex"), - "projectId": self.response.get("projectId"), - "repoName": self.response.get("repoName"), - "tagName": self.response.get("tagName"), - } - -class WebhookConfig(gcp.Resource): +class WebhookConfig(gcp_v2.Resource): def _request(self): return { "secret": self.request.get("secret"), @@ -1815,70 +1621,59 @@ def _request(self): def _response(self): return { - "secret": self.response.get("secret"), "state": self.response.get("state"), } -class CloudBuild(gcp.Resource): +class CloudBuild(gcp_v2.Resource): def _request(self): return { - "approvalConfig": ApprovalConfig(self.request.get("approval_config", {})).to_request(), - "bitbucketServerTriggerConfig": BitbucketServerTriggerConfig( - self.request.get("bitbucket_server_trigger_config", {}) - ).to_request(), - "build": Build(self.request.get("build", {})).to_request(), + "approvalConfig": gcp_v2.remove_empties( + ApprovalConfig(self.request.get("approval_config", {})).to_request() + ), # remove empty values + "bitbucketServerTriggerConfig": gcp_v2.remove_empties( + BitbucketServerTriggerConfig(self.request.get("bitbucket_server_trigger_config", {})).to_request() + ), # remove empty values + "build": gcp_v2.remove_empties(Build(self.request.get("build", {})).to_request()), # remove empty values "description": self.request.get("description"), + "developerConnectEventConfig": gcp_v2.remove_empties( + DeveloperConnectEventConfig(self.request.get("developer_connect_event_config", {})).to_request() + ), # remove empty values "disabled": self.request.get("disabled"), "filename": self.request.get("filename"), "filter": self.request.get("filter"), - "gitFileSource": GitFileSource(self.request.get("git_file_source", {})).to_request(), - "github": Github(self.request.get("github", {})).to_request(), + "gitFileSource": gcp_v2.remove_empties( + GitFileSource(self.request.get("git_file_source", {})).to_request() + ), # remove empty values + "github": gcp_v2.remove_empties(Github(self.request.get("github", {})).to_request()), # remove empty values "ignoredFiles": [str(item) for item in (self.request.get("ignored_files") or [])], "includeBuildLogs": self.request.get("include_build_logs"), "includedFiles": [str(item) for item in (self.request.get("included_files") or [])], "name": self.request.get("name"), - "pubsubConfig": PubsubConfig(self.request.get("pubsub_config", {})).to_request(), - "repositoryEventConfig": RepositoryEventConfig( - self.request.get("repository_event_config", {}) - ).to_request(), + "pubsubConfig": gcp_v2.remove_empties( + PubsubConfig(self.request.get("pubsub_config", {})).to_request() + ), # remove empty values + "repositoryEventConfig": gcp_v2.remove_empties( + RepositoryEventConfig(self.request.get("repository_event_config", {})).to_request() + ), # remove empty values "serviceAccount": self.request.get("service_account"), - "sourceToBuild": SourceToBuild(self.request.get("source_to_build", {})).to_request(), + "sourceToBuild": gcp_v2.remove_empties( + SourceToBuild(self.request.get("source_to_build", {})).to_request() + ), # remove empty values "substitutions": self.request.get("substitutions"), "tags": [str(item) for item in (self.request.get("tags") or [])], - "triggerTemplate": TriggerTemplate(self.request.get("trigger_template", {})).to_request(), - "webhookConfig": WebhookConfig(self.request.get("webhook_config", {})).to_request(), + "triggerTemplate": gcp_v2.remove_empties( + TriggerTemplate(self.request.get("trigger_template", {})).to_request() + ), # remove empty values + "webhookConfig": gcp_v2.remove_empties( + WebhookConfig(self.request.get("webhook_config", {})).to_request() + ), # remove empty values } def _response(self): return { - "approvalConfig": ApprovalConfig().from_response(self.response.get("approvalConfig", {})), - "bitbucketServerTriggerConfig": BitbucketServerTriggerConfig().from_response( - self.response.get("bitbucketServerTriggerConfig", {}) - ), - "build": Build().from_response(self.response.get("build", {})), "createTime": self.response.get("createTime"), - "description": self.response.get("description"), - "disabled": self.response.get("disabled"), - "filename": self.response.get("filename"), - "filter": self.response.get("filter"), - "gitFileSource": GitFileSource().from_response(self.response.get("gitFileSource", {})), - "github": Github().from_response(self.response.get("github", {})), - "ignoredFiles": [str(item) for item in (self.response.get("ignoredFiles") or [])], - "includeBuildLogs": self.response.get("includeBuildLogs"), - "includedFiles": [str(item) for item in (self.response.get("includedFiles") or [])], - "name": self.response.get("name"), - "pubsubConfig": PubsubConfig().from_response(self.response.get("pubsubConfig", {})), - "repositoryEventConfig": RepositoryEventConfig().from_response( - self.response.get("repositoryEventConfig", {}) - ), - "serviceAccount": self.response.get("serviceAccount"), - "sourceToBuild": SourceToBuild().from_response(self.response.get("sourceToBuild", {})), - "substitutions": self.response.get("substitutions"), - "tags": [str(item) for item in (self.response.get("tags") or [])], - "triggerTemplate": TriggerTemplate().from_response(self.response.get("triggerTemplate", {})), "id": self.response.get("id"), - "webhookConfig": WebhookConfig().from_response(self.response.get("webhookConfig", {})), } @@ -1887,40 +1682,10 @@ def _response(self): ################################################################################ -def encode(obj): - """ - The encoder is a function which take the `obj` map after it has been - read from the module caller and mutates it before it is sent to the server - """ - - if obj is None: - return None - r = copy.deepcopy(obj) - # --------- BEGIN custom encoder code --------- - # --------- END custom encoder code --------- - - return r - - -def decode(obj): - """ - The decoder is a function which takes the `obj` map after the read succeeds - and mutates it before it is returned to the module caller - """ - - if obj is None: - return None - r = copy.deepcopy(obj) - # --------- BEGIN custom decoder code --------- - # --------- END custom decoder code --------- - - return r - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -2319,6 +2084,60 @@ def main(): description=dict( type="str", ), + developer_connect_event_config=dict( + type="dict", + options=dict( + git_repository_link=dict( + type="str", + required=True, + ), + git_repository_link_type=dict( + type="str", + choices=[ + "GIT_REPOSITORY_LINK_TYPE_UNSPECIFIED", + "GITHUB", + "GITHUB_ENTERPRISE", + "GITLAB", + "GITLAB_ENTERPRISE", + "BITBUCKET_DATA_CENTER", + "BITBUCKET_CLOUD", + ], + ), + pull_request=dict( + type="dict", + options=dict( + branch=dict( + type="str", + ), + comment_control=dict( + type="str", + choices=[ + "COMMENTS_DISABLED", + "COMMENTS_ENABLED", + "COMMENTS_ENABLED_FOR_EXTERNAL_CONTRIBUTORS_ONLY", + ], + ), + invert_regex=dict( + type="bool", + ), + ), + ), + push=dict( + type="dict", + options=dict( + branch=dict( + type="str", + ), + invert_regex=dict( + type="bool", + ), + tag=dict( + type="str", + ), + ), + ), + ), + ), disabled=dict( type="bool", ), @@ -2564,18 +2383,11 @@ def main(): state = module.params["state"] changed = False - - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{location}/triggers", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://cloudbuild.googleapis.com/v1/", + base_uri="projects/{project}/locations/{location}/triggers", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/triggers", "async_uri": "", @@ -2583,7 +2395,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/triggers/{trigger_id}", "async_uri": "", @@ -2591,7 +2403,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/triggers/{trigger_id}", "async_uri": "", @@ -2599,7 +2411,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/triggers/{trigger_id}", "async_uri": "", @@ -2607,115 +2419,158 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones_from_dict(module.params) + request = gcp_v2.remove_nones(module.params) resource = CloudBuild( - params, module=module, product="CloudBuild", kind="cloudbuild#trigger", encoder=encode, decoder=decode + request, module=module, product="CloudBuild", kind="cloudbuild#trigger", op_configs=op_configs + ) + + resource._state = state # store the state in the resource object + + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload + + # --------- BEGIN pre-read custom code --------- + # if set from a registered variable, get the resource basename + resource.url_params["trigger_id"] = module.params["name"].split("/")[-1] + + # --------- END pre-read custom code --------- + + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) + new_obj = {} + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) + + if custom_diff is not None: + is_different = custom_diff + else: + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( + module, + request=gcp_v2.remove_empties(resource.to_request()), + existing=existing_obj, + post=True, + is_different=is_different, ) - # --------- BEGIN custom pre-read code --------- - # set the trigger ID from name before reading - module.params["trigger_id"] = module.params["name"] - # --------- END custom pre-read code --------- - read_url = build_link(module, op_configs.read.uri) - existing_obj = resource.get(read_url, allow_not_found=True) - - if existing_obj is None: + + if gcp_v2.empty(existing_obj): if state == "present": - # --------- BEGIN custom pre-create code --------- - # set the trigger ID from name before creation - module.params["trigger_id"] = module.params["name"] - # --------- END custom pre-create code --------- - is_async = op_configs.create.async_uri != "" - create_link = build_link(module, op_configs.create.uri) - create_retries = op_configs.create.timeout - create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(module, "") + op_configs.create.async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + gcp_v2.debug(module, action="create") try: - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + # --------- BEGIN create code --------- + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") + create_retries = op_configs.create.timeout + create_func = getattr(resource, op_configs.create.verb) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) + + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - changed = True + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) + # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) + + changed = True else: pass # nothing to do else: if state == "absent": - # --------- BEGIN custom pre-delete code --------- - # set the trigger ID from the existing object's ID before delete - if existing_obj.get("id") is None: - module.params["trigger_id"] = existing_obj["name"] - else: - module.params["trigger_id"] = existing_obj["id"] - # --------- END custom pre-delete code --------- - is_async = op_configs.delete.async_uri != "" - delete_link = build_link(module, op_configs.delete.uri) - delete_retries = op_configs.delete.timeout - delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(module, "") + op_configs.delete.async_uri - gcp.debug( - module, - msg="Destroying resource", - delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, - ) + gcp_v2.debug(module, action="delete") try: - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload + # --------- BEGIN pre-delete custom code --------- + # set the trigger ID from the existing object's ID before delete + if existing_obj.get("id"): + module.url_params["trigger_id"] = existing_obj["id"] + else: + module.url_params["trigger_id"] = existing_obj["name"].split("/")[-1] + + # --------- END pre-delete custom code --------- + if delete_link == "": + delete_link = resource.build_link("delete") + delete_retries = op_configs.delete.timeout + delete_func = getattr(resource, op_configs.delete.verb) + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( + module, + msg="Destroying resource", + delete_link=delete_link, + async_uri=delete_async_uri, + ) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) - changed = True + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) + # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) + + changed = True else: - gcp.debug(module, existing=existing_obj, request=resource.to_request()) - if resource.diff(existing_obj): - # --------- BEGIN custom pre-update code --------- - # set the trigger ID from the existing object's ID before update - if existing_obj.get("id") is None: - module.params["trigger_id"] = existing_obj["name"] - else: - module.params["trigger_id"] = existing_obj["id"] - # --------- END custom pre-update code --------- - is_async = op_configs.update.async_uri != "" - update_link = build_link(module, op_configs.update.uri) - update_retries = op_configs.update.timeout - update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(module, "") + op_configs.update.async_uri - gcp.debug( - module, - msg="Updating resource", - update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, - ) + if is_different: + gcp_v2.debug(module, action="update") try: - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload + # --------- BEGIN pre-update custom code --------- + # set the trigger ID from the existing object's ID before update + if existing_obj.get("id"): + module.url_params["trigger_id"] = existing_obj["id"] + else: + module.url_params["trigger_id"] = existing_obj["name"].split("/")[-1] + # --------- END pre-update custom code --------- + if update_link == "": + update_link = resource.build_link("update") + update_retries = op_configs.update.timeout + update_func = getattr(resource, op_configs.update.verb) + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( + module, + msg="Updating resource", + update_link=update_link, + async_uri=update_async_uri, + ) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) + # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) - changed = True - new_obj = resource.get(read_url, allow_not_found=True) - new_obj = resource.from_response(new_obj or {}) + changed = True + else: + new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_cloudbuildv2_connection.py b/plugins/modules/gcp_cloudbuildv2_connection.py index 190bb1d0e..b82b0a5fd 100644 --- a/plugins/modules/gcp_cloudbuildv2_connection.py +++ b/plugins/modules/gcp_cloudbuildv2_connection.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # -# Copyright (C) 2017-2025 Google +# Copyright (C) 2017-2026 Google # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # ---------------------------------------------------------------------------- # @@ -45,6 +45,7 @@ annotations: description: - Allows clients to store small amounts of arbitrary data. + - '**Note**: This field is non-authoritative, and will only manage the annotations present in your configuration.' type: dict bitbucket_cloud_config: description: @@ -325,6 +326,7 @@ location: description: - The location for the resource. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str name: @@ -346,7 +348,7 @@ - requests >= 2.18.4 - google-auth >= 2.25.1 short_description: Creates a GCP Cloudbuildv2.Connection resource -""" +""" # noqa: E501 EXAMPLES = r""" - name: Create github enterprise connection @@ -397,7 +399,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" -""" +""" # noqa: E501 RETURN = r""" changed: @@ -412,10 +414,7 @@ type: str etag: description: - - >- - This checksum is computed by the server based on the value of other fields, and may be sent on update and delete requests to ensure the - - client has an up-to-date value before proceeding. + - This checksum is computed by the server based on the value of other fields, and may be sent on update and delete requests to ensure the client has an up-to-date value before proceeding. returned: success type: str installationState: @@ -461,52 +460,37 @@ - Server assigned timestamp for when the connection was updated. returned: success type: str -""" +""" # noqa: E501 ################################################################################ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import copy +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports -# END Custom imports - - -def build_link(module, uri): - params = module.params.copy() +import copy - return "https://cloudbuild.googleapis.com/v2/" + uri.format(**params) +# END Custom imports -class BitbucketCloudConfig(gcp.Resource): +class BitbucketCloudConfig(gcp_v2.Resource): def _request(self): return { - "authorizerCredential": BitbucketCloudConfigAuthorizerCredential( - self.request.get("authorizer_credential", {}) - ).to_request(), - "readAuthorizerCredential": BitbucketCloudConfigReadAuthorizerCredential( - self.request.get("read_authorizer_credential", {}) - ).to_request(), + "authorizerCredential": gcp_v2.remove_empties( + BitbucketCloudConfigAuthorizerCredential(self.request.get("authorizer_credential", {})).to_request() + ), # remove empty values + "readAuthorizerCredential": gcp_v2.remove_empties( + BitbucketCloudConfigReadAuthorizerCredential( + self.request.get("read_authorizer_credential", {}) + ).to_request() + ), # remove empty values "webhookSecretSecretVersion": self.request.get("webhook_secret_secret_version"), "workspace": self.request.get("workspace"), } - def _response(self): - return { - "authorizerCredential": BitbucketCloudConfigAuthorizerCredential().from_response( - self.response.get("authorizerCredential", {}) - ), - "readAuthorizerCredential": BitbucketCloudConfigReadAuthorizerCredential().from_response( - self.response.get("readAuthorizerCredential", {}) - ), - "webhookSecretSecretVersion": self.response.get("webhookSecretSecretVersion"), - "workspace": self.response.get("workspace"), - } - -class BitbucketCloudConfigAuthorizerCredential(gcp.Resource): +class BitbucketCloudConfigAuthorizerCredential(gcp_v2.Resource): def _request(self): return { "userTokenSecretVersion": self.request.get("user_token_secret_version"), @@ -514,12 +498,11 @@ def _request(self): def _response(self): return { - "userTokenSecretVersion": self.response.get("userTokenSecretVersion"), "username": self.response.get("username"), } -class BitbucketCloudConfigReadAuthorizerCredential(gcp.Resource): +class BitbucketCloudConfigReadAuthorizerCredential(gcp_v2.Resource): def _request(self): return { "userTokenSecretVersion": self.request.get("user_token_secret_version"), @@ -527,47 +510,40 @@ def _request(self): def _response(self): return { - "userTokenSecretVersion": self.response.get("userTokenSecretVersion"), "username": self.response.get("username"), } -class BitbucketDataCenterConfig(gcp.Resource): +class BitbucketDataCenterConfig(gcp_v2.Resource): def _request(self): return { - "authorizerCredential": BitbucketDataCenterConfigAuthorizerCredential( - self.request.get("authorizer_credential", {}) - ).to_request(), + "authorizerCredential": gcp_v2.remove_empties( + BitbucketDataCenterConfigAuthorizerCredential( + self.request.get("authorizer_credential", {}) + ).to_request() + ), # remove empty values "hostUri": self.request.get("host_uri"), - "readAuthorizerCredential": BitbucketDataCenterConfigReadAuthorizerCredential( - self.request.get("read_authorizer_credential", {}) - ).to_request(), - "serviceDirectoryConfig": BitbucketDataCenterConfigServiceDirectoryConfig( - self.request.get("service_directory_config", {}) - ).to_request(), + "readAuthorizerCredential": gcp_v2.remove_empties( + BitbucketDataCenterConfigReadAuthorizerCredential( + self.request.get("read_authorizer_credential", {}) + ).to_request() + ), # remove empty values + "serviceDirectoryConfig": gcp_v2.remove_empties( + BitbucketDataCenterConfigServiceDirectoryConfig( + self.request.get("service_directory_config", {}) + ).to_request() + ), # remove empty values "sslCa": self.request.get("ssl_ca"), "webhookSecretSecretVersion": self.request.get("webhook_secret_secret_version"), } def _response(self): return { - "authorizerCredential": BitbucketDataCenterConfigAuthorizerCredential().from_response( - self.response.get("authorizerCredential", {}) - ), - "hostUri": self.response.get("hostUri"), - "readAuthorizerCredential": BitbucketDataCenterConfigReadAuthorizerCredential().from_response( - self.response.get("readAuthorizerCredential", {}) - ), "serverVersion": self.response.get("serverVersion"), - "serviceDirectoryConfig": BitbucketDataCenterConfigServiceDirectoryConfig().from_response( - self.response.get("serviceDirectoryConfig", {}) - ), - "sslCa": self.response.get("sslCa"), - "webhookSecretSecretVersion": self.response.get("webhookSecretSecretVersion"), } -class BitbucketDataCenterConfigAuthorizerCredential(gcp.Resource): +class BitbucketDataCenterConfigAuthorizerCredential(gcp_v2.Resource): def _request(self): return { "userTokenSecretVersion": self.request.get("user_token_secret_version"), @@ -575,12 +551,11 @@ def _request(self): def _response(self): return { - "userTokenSecretVersion": self.response.get("userTokenSecretVersion"), "username": self.response.get("username"), } -class BitbucketDataCenterConfigReadAuthorizerCredential(gcp.Resource): +class BitbucketDataCenterConfigReadAuthorizerCredential(gcp_v2.Resource): def _request(self): return { "userTokenSecretVersion": self.request.get("user_token_secret_version"), @@ -588,42 +563,28 @@ def _request(self): def _response(self): return { - "userTokenSecretVersion": self.response.get("userTokenSecretVersion"), "username": self.response.get("username"), } -class BitbucketDataCenterConfigServiceDirectoryConfig(gcp.Resource): +class BitbucketDataCenterConfigServiceDirectoryConfig(gcp_v2.Resource): def _request(self): return { "service": self.request.get("service"), } - def _response(self): - return { - "service": self.response.get("service"), - } - -class GithubConfig(gcp.Resource): +class GithubConfig(gcp_v2.Resource): def _request(self): return { "appInstallationId": self.request.get("app_installation_id"), - "authorizerCredential": GithubConfigAuthorizerCredential( - self.request.get("authorizer_credential", {}) - ).to_request(), - } - - def _response(self): - return { - "appInstallationId": self.response.get("appInstallationId"), - "authorizerCredential": GithubConfigAuthorizerCredential().from_response( - self.response.get("authorizerCredential", {}) - ), + "authorizerCredential": gcp_v2.remove_empties( + GithubConfigAuthorizerCredential(self.request.get("authorizer_credential", {})).to_request() + ), # remove empty values } -class GithubConfigAuthorizerCredential(gcp.Resource): +class GithubConfigAuthorizerCredential(gcp_v2.Resource): def _request(self): return { "oauthTokenSecretVersion": self.request.get("oauth_token_secret_version"), @@ -631,12 +592,11 @@ def _request(self): def _response(self): return { - "oauthTokenSecretVersion": self.response.get("oauthTokenSecretVersion"), "username": self.response.get("username"), } -class GithubEnterpriseConfig(gcp.Resource): +class GithubEnterpriseConfig(gcp_v2.Resource): def _request(self): return { "appId": self.request.get("app_id"), @@ -644,76 +604,47 @@ def _request(self): "appSlug": self.request.get("app_slug"), "hostUri": self.request.get("host_uri"), "privateKeySecretVersion": self.request.get("private_key_secret_version"), - "serviceDirectoryConfig": GithubEnterpriseConfigServiceDirectoryConfig( - self.request.get("service_directory_config", {}) - ).to_request(), + "serviceDirectoryConfig": gcp_v2.remove_empties( + GithubEnterpriseConfigServiceDirectoryConfig( + self.request.get("service_directory_config", {}) + ).to_request() + ), # remove empty values "sslCa": self.request.get("ssl_ca"), "webhookSecretSecretVersion": self.request.get("webhook_secret_secret_version"), } - def _response(self): - return { - "appId": self.response.get("appId"), - "appInstallationId": self.response.get("appInstallationId"), - "appSlug": self.response.get("appSlug"), - "hostUri": self.response.get("hostUri"), - "privateKeySecretVersion": self.response.get("privateKeySecretVersion"), - "serviceDirectoryConfig": GithubEnterpriseConfigServiceDirectoryConfig().from_response( - self.response.get("serviceDirectoryConfig", {}) - ), - "sslCa": self.response.get("sslCa"), - "webhookSecretSecretVersion": self.response.get("webhookSecretSecretVersion"), - } - -class GithubEnterpriseConfigServiceDirectoryConfig(gcp.Resource): +class GithubEnterpriseConfigServiceDirectoryConfig(gcp_v2.Resource): def _request(self): return { "service": self.request.get("service"), } - def _response(self): - return { - "service": self.response.get("service"), - } - -class GitlabConfig(gcp.Resource): +class GitlabConfig(gcp_v2.Resource): def _request(self): return { - "authorizerCredential": GitlabConfigAuthorizerCredential( - self.request.get("authorizer_credential", {}) - ).to_request(), + "authorizerCredential": gcp_v2.remove_empties( + GitlabConfigAuthorizerCredential(self.request.get("authorizer_credential", {})).to_request() + ), # remove empty values "hostUri": self.request.get("host_uri"), - "readAuthorizerCredential": GitlabConfigReadAuthorizerCredential( - self.request.get("read_authorizer_credential", {}) - ).to_request(), - "serviceDirectoryConfig": GitlabConfigServiceDirectoryConfig( - self.request.get("service_directory_config", {}) - ).to_request(), + "readAuthorizerCredential": gcp_v2.remove_empties( + GitlabConfigReadAuthorizerCredential(self.request.get("read_authorizer_credential", {})).to_request() + ), # remove empty values + "serviceDirectoryConfig": gcp_v2.remove_empties( + GitlabConfigServiceDirectoryConfig(self.request.get("service_directory_config", {})).to_request() + ), # remove empty values "sslCa": self.request.get("ssl_ca"), "webhookSecretSecretVersion": self.request.get("webhook_secret_secret_version"), } def _response(self): return { - "authorizerCredential": GitlabConfigAuthorizerCredential().from_response( - self.response.get("authorizerCredential", {}) - ), - "hostUri": self.response.get("hostUri"), - "readAuthorizerCredential": GitlabConfigReadAuthorizerCredential().from_response( - self.response.get("readAuthorizerCredential", {}) - ), "serverVersion": self.response.get("serverVersion"), - "serviceDirectoryConfig": GitlabConfigServiceDirectoryConfig().from_response( - self.response.get("serviceDirectoryConfig", {}) - ), - "sslCa": self.response.get("sslCa"), - "webhookSecretSecretVersion": self.response.get("webhookSecretSecretVersion"), } -class GitlabConfigAuthorizerCredential(gcp.Resource): +class GitlabConfigAuthorizerCredential(gcp_v2.Resource): def _request(self): return { "userTokenSecretVersion": self.request.get("user_token_secret_version"), @@ -721,12 +652,11 @@ def _request(self): def _response(self): return { - "userTokenSecretVersion": self.response.get("userTokenSecretVersion"), "username": self.response.get("username"), } -class GitlabConfigReadAuthorizerCredential(gcp.Resource): +class GitlabConfigReadAuthorizerCredential(gcp_v2.Resource): def _request(self): return { "userTokenSecretVersion": self.request.get("user_token_secret_version"), @@ -734,24 +664,18 @@ def _request(self): def _response(self): return { - "userTokenSecretVersion": self.response.get("userTokenSecretVersion"), "username": self.response.get("username"), } -class GitlabConfigServiceDirectoryConfig(gcp.Resource): +class GitlabConfigServiceDirectoryConfig(gcp_v2.Resource): def _request(self): return { "service": self.request.get("service"), } - def _response(self): - return { - "service": self.response.get("service"), - } - -class InstallationState(gcp.Resource): +class InstallationState(gcp_v2.Resource): def _response(self): return { "actionUri": self.response.get("actionUri"), @@ -760,96 +684,76 @@ def _response(self): } -class Cloudbuildv2(gcp.Resource): +class Cloudbuildv2(gcp_v2.Resource): def _request(self): return { "annotations": self.request.get("annotations"), - "bitbucketCloudConfig": BitbucketCloudConfig(self.request.get("bitbucket_cloud_config", {})).to_request(), - "bitbucketDataCenterConfig": BitbucketDataCenterConfig( - self.request.get("bitbucket_data_center_config", {}) - ).to_request(), + "bitbucketCloudConfig": gcp_v2.remove_empties( + BitbucketCloudConfig(self.request.get("bitbucket_cloud_config", {})).to_request() + ), # remove empty values + "bitbucketDataCenterConfig": gcp_v2.remove_empties( + BitbucketDataCenterConfig(self.request.get("bitbucket_data_center_config", {})).to_request() + ), # remove empty values "disabled": self.request.get("disabled"), - "githubConfig": GithubConfig(self.request.get("github_config", {})).to_request(), - "githubEnterpriseConfig": GithubEnterpriseConfig( - self.request.get("github_enterprise_config", {}) - ).to_request(), - "gitlabConfig": GitlabConfig(self.request.get("gitlab_config", {})).to_request(), + "githubConfig": gcp_v2.remove_empties( + GithubConfig(self.request.get("github_config", {})).to_request() + ), # remove empty values + "githubEnterpriseConfig": gcp_v2.remove_empties( + GithubEnterpriseConfig(self.request.get("github_enterprise_config", {})).to_request() + ), # remove empty values + "gitlabConfig": gcp_v2.remove_empties( + GitlabConfig(self.request.get("gitlab_config", {})).to_request() + ), # remove empty values } def _response(self): return { - "annotations": self.response.get("annotations"), - "bitbucketCloudConfig": BitbucketCloudConfig().from_response(self.response.get("bitbucketCloudConfig", {})), - "bitbucketDataCenterConfig": BitbucketDataCenterConfig().from_response( - self.response.get("bitbucketDataCenterConfig", {}) - ), "createTime": self.response.get("createTime"), - "disabled": self.response.get("disabled"), "etag": self.response.get("etag"), - "githubConfig": GithubConfig().from_response(self.response.get("githubConfig", {})), - "githubEnterpriseConfig": GithubEnterpriseConfig().from_response( - self.response.get("githubEnterpriseConfig", {}) - ), - "gitlabConfig": GitlabConfig().from_response(self.response.get("gitlabConfig", {})), "installationState": InstallationState().from_response(self.response.get("installationState", {})), "reconciling": self.response.get("reconciling"), "updateTime": self.response.get("updateTime"), } + def decode(self, response): + "Custom decoder function, mutates the response object before it is returned to the module caller." + + # --------- BEGIN custom decoder code --------- + # when creating, response is empty (except for "kind" maybe) + if len(response) <= 1: + return response + + r = copy.deepcopy(response) + + # disabled key doesn't seem to appear consistently for gitlab, + # set to the default if missing + r["disabled"] = response.get("disabled", False) + + # the github app id returned from the API shows as a string, + # and the input is an int, when running comparison they will be + # incorrectly show as diff + if r.get("githubConfig") is not None: + r["githubConfig"]["appInstallationId"] = int(response["githubConfig"]["appInstallationId"]) + # same for GHE + if r.get("githubEnterpriseConfig") is not None: + r["githubEnterpriseConfig"]["appInstallationId"] = int( + response["githubEnterpriseConfig"]["appInstallationId"] + ) -################################################################################ -# Main -################################################################################ - - -def encode(obj): - """ - The encoder is a function which take the `obj` map after it has been - assembled in either "Create" or "Update" and mutate it before it is sent to - the server - """ - - if obj is None: - return None - r = copy.deepcopy(obj) - # --------- BEGIN custom encoder code --------- - # --------- END custom encoder code --------- - - return r - - -def decode(obj): - """ - The decoder is a function which takes the `obj` map after the read succeeds - and mutates it before it is returned to the module caller - """ + return r - if obj is None: - return None - r = copy.deepcopy(obj) - # --------- BEGIN custom decoder code --------- - # disabled key doesn't seem to appear consistently for gitlab, - # set to the default if missing - if "disabled" not in r.keys(): - r["disabled"] = False + # --------- END custom decoder code --------- - # the github app id returned from the API shows as a string, - # and the input is an int, when running comparison they will be - # incorrectly show as diff - if r.get("githubConfig") is not None: - r["githubConfig"]["appInstallationId"] = int(obj["githubConfig"]["appInstallationId"]) - # same for GHE - if r.get("githubEnterpriseConfig") is not None: - r["githubEnterpriseConfig"]["appInstallationId"] = int(obj["githubEnterpriseConfig"]["appInstallationId"]) - # --------- END custom decoder code --------- - return r +################################################################################ +# Main +################################################################################ def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -1092,13 +996,13 @@ def main(): ), ), mutually_exclusive=[ - [ + ( "bitbucket_cloud_config", "bitbucket_data_center_config", "github_config", "github_enterprise_config", "gitlab_config", - ] + ) ], ) @@ -1107,10 +1011,11 @@ def main(): state = module.params["state"] changed = False - - op_configs = gcp.ResourceOpConfigs( - { - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://cloudbuild.googleapis.com/v2/", + base_uri="projects/{project}/locations/{location}/connections", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/connections?connectionId={name}", "async_uri": "{op_id}", @@ -1118,7 +1023,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/connections/{name}", "async_uri": "{op_id}", @@ -1126,7 +1031,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/connections/{name}", "async_uri": "", @@ -1134,7 +1039,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/connections/{name}", "async_uri": "{op_id}", @@ -1142,96 +1047,137 @@ def main(): "timeout_minutes": 20, } ), - } + }, + ) + + request = gcp_v2.remove_nones(module.params) + resource = Cloudbuildv2( + request, module=module, product="Cloudbuildv2", kind="cloudbuildv2#connection", op_configs=op_configs ) - params = gcp.remove_nones_from_dict(module.params) - resource = Cloudbuildv2(params, module=module, product="Cloudbuildv2", kind="cloudbuildv2#connection") - existing_obj = decode(resource.get(build_link(module, op_configs.read.uri), allow_not_found=True)) + resource._state = state # store the state in the resource object - if existing_obj is None: + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload + + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) + new_obj = {} + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) + + if custom_diff is not None: + is_different = custom_diff + else: + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( + module, + request=gcp_v2.remove_empties(resource.to_request()), + existing=existing_obj, + post=True, + is_different=is_different, + ) + + if gcp_v2.empty(existing_obj): if state == "present": - is_async = op_configs.create.async_uri != "" - create_link = build_link(module, op_configs.create.uri) - create_retries = op_configs.create.timeout - create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(module, "") + op_configs.create.async_uri - # --------- BEGIN custom pre-create code --------- - # --------- END custom pre-create code --------- - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + gcp_v2.debug(module, action="create") try: - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + # --------- BEGIN create code --------- + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") + create_retries = op_configs.create.timeout + create_func = getattr(resource, op_configs.create.verb) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) + + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - changed = True + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) + # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) + + changed = True else: pass # nothing to do else: if state == "absent": - is_async = op_configs.delete.async_uri != "" - delete_link = build_link(module, op_configs.delete.uri) - delete_retries = op_configs.delete.timeout - delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(module, "") + op_configs.delete.async_uri - # --------- BEGIN custom pre-delete code --------- - # --------- END custom pre-delete code --------- - gcp.debug( - module, - msg="Destroying resource", - delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, - ) + gcp_v2.debug(module, action="delete") try: - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") + delete_retries = op_configs.delete.timeout + delete_func = getattr(resource, op_configs.delete.verb) + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( + module, + msg="Destroying resource", + delete_link=delete_link, + async_uri=delete_async_uri, + ) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) - changed = True + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) + # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) + + changed = True else: - gcp.debug(module, existing=existing_obj, request=resource.to_request()) - if resource.diff(existing_obj): - is_async = op_configs.update.async_uri != "" - update_link = build_link(module, op_configs.update.uri) - update_retries = op_configs.update.timeout - update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(module, "") + op_configs.update.async_uri - # --------- BEGIN custom pre-update code --------- - # --------- END custom pre-update code --------- - gcp.debug( - module, - msg="Updating resource", - update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, - ) + if is_different: + gcp_v2.debug(module, action="update") try: - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") + update_retries = op_configs.update.timeout + update_func = getattr(resource, op_configs.update.verb) + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( + module, + msg="Updating resource", + update_link=update_link, + async_uri=update_async_uri, + ) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) + # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) - changed = True - new_obj = decode(resource.get(build_link(module, op_configs.read.uri), allow_not_found=True)) - new_obj = resource.from_response(new_obj or {}) + changed = True + else: + new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_cloudbuildv2_repository.py b/plugins/modules/gcp_cloudbuildv2_repository.py index 122e97dd8..36b820861 100644 --- a/plugins/modules/gcp_cloudbuildv2_repository.py +++ b/plugins/modules/gcp_cloudbuildv2_repository.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # -# Copyright (C) 2017-2025 Google +# Copyright (C) 2017-2026 Google # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # ---------------------------------------------------------------------------- # @@ -45,14 +45,18 @@ annotations: description: - Allows clients to store small amounts of arbitrary data. + - '**Note**: This field is non-authoritative, and will only manage the annotations present in your configuration.' + - This property is immutable, to change it, you must delete and recreate the resource. type: dict location: description: - The location for the resource. + - This property is immutable, to change it, you must delete and recreate the resource. type: str name: description: - Name of the repository. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str parent_connection: @@ -61,11 +65,13 @@ - This field is a reference to a connection resource in GCP. - 'It can be specified in two ways: First, you can place a dictionary with key ''name'' matching your resource.' - 'Alternatively, you can add `register: name-of-resource` to a connection task and then set this field to `{{ name-of-resource }}`.' + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: dict remote_uri: description: - Git Clone HTTPS URI. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str state: @@ -81,7 +87,7 @@ - requests >= 2.18.4 - google-auth >= 2.25.1 short_description: Creates a GCP Cloudbuildv2.Repository resource -""" +""" # noqa: E501 EXAMPLES = r""" # repository_ghe_doc @@ -89,7 +95,7 @@ ################################################################################ # repository_github_doc -""" +""" # noqa: E501 RETURN = r""" changed: @@ -104,10 +110,7 @@ type: str etag: description: - - >- - This checksum is computed by the server based on the value of other fields, and may be sent on update and delete requests to ensure the - - client has an up-to-date value before proceeding. + - This checksum is computed by the server based on the value of other fields, and may be sent on update and delete requests to ensure the client has an up-to-date value before proceeding. returned: success type: str state: @@ -120,27 +123,21 @@ - Server assigned timestamp for when the connection was updated. returned: success type: str -""" +""" # noqa: E501 ################################################################################ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import copy +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports -# END Custom imports - - -def build_link(module, uri): - params = module.params.copy() - params["parent_connection"] = gcp.replace_resource_dict(module.params["parent_connection"], "name") +import copy - return "https://cloudbuild.googleapis.com/v2/" + uri.format(**params) +# END Custom imports -class Cloudbuildv2(gcp.Resource): +class Cloudbuildv2(gcp_v2.Resource): def _request(self): return { "annotations": self.request.get("annotations"), @@ -150,62 +147,43 @@ def _request(self): def _response(self): return { - "annotations": self.response.get("annotations"), "createTime": self.response.get("createTime"), "etag": self.response.get("etag"), - "name": self.response.get("name"), - "remoteUri": self.response.get("remoteUri"), "updateTime": self.response.get("updateTime"), } + def decode(self, response): + "Custom decoder function, mutates the response object before it is returned to the module caller." -################################################################################ -# Main -################################################################################ + # --------- BEGIN custom decoder code --------- + # when creating, response is empty except for "kind" + if len(response) <= 1: + return response + r = copy.deepcopy(response) -def encode(obj): - """ - The encoder is a function which take the `obj` map after it has been - assembled in either "Create" or "Update" and mutate it before it is sent to - the server - """ + # the name returned from the API is FQDN, but the name + # we send is just the "base name", convert it back to + # what we need but maintain the parent connection ref + if response.get("name", "") != "": + parts = response["name"].split("/") + r["parentConnection"] = "/".join(parts[:-2]) + r["name"] = parts[-1] - if obj is None: - return None - r = copy.deepcopy(obj) - # --------- BEGIN custom encoder code --------- - # no encoder needed - # --------- END custom encoder code --------- + return r - return r + # --------- END custom decoder code --------- -def decode(obj): - """ - The decoder is a function which takes the `obj` map after the read succeeds - and mutates it before it is returned to the module caller - """ - - if obj is None: - return None - r = copy.deepcopy(obj) - # --------- BEGIN custom decoder code --------- - # the name returned from the API is FQDN, but the name - # we send is just the "base name", convert it back to - # what we need but maintain the parent connection ref - parts = obj["name"].split("/") - r["parentConnection"] = "/".join(parts[:-2]) - r["name"] = parts[-1] - # --------- END custom decoder code --------- - - return r +################################################################################ +# Main +################################################################################ def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -238,10 +216,11 @@ def main(): state = module.params["state"] changed = False - - op_configs = gcp.ResourceOpConfigs( - { - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://cloudbuild.googleapis.com/v2/", + base_uri="{parent_connection}/repositories", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "{parent_connection}/repositories?repositoryId={name}", "async_uri": "{op_id}", @@ -249,7 +228,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "{parent_connection}/repositories/{name}", "async_uri": "{op_id}", @@ -257,7 +236,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "{parent_connection}/repositories/{name}", "async_uri": "", @@ -265,7 +244,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "{parent_connection}/repositories/{name}", "async_uri": "{op_id}", @@ -273,96 +252,138 @@ def main(): "timeout_minutes": 20, } ), - } + }, + ) + + request = gcp_v2.remove_nones(module.params) + resource = Cloudbuildv2( + request, module=module, product="Cloudbuildv2", kind="cloudbuildv2#repository", op_configs=op_configs ) - params = gcp.remove_nones_from_dict(module.params) - resource = Cloudbuildv2(params, module=module, product="Cloudbuildv2", kind="cloudbuildv2#repository") - existing_obj = decode(resource.get(build_link(module, op_configs.read.uri), allow_not_found=True)) + resource._state = state # store the state in the resource object - if existing_obj is None: + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + resource.url_params["parent_connection"] = gcp_v2.resource_ref(module.params["parent_connection"], "name") + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload + + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) + new_obj = {} + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) + + if custom_diff is not None: + is_different = custom_diff + else: + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( + module, + request=gcp_v2.remove_empties(resource.to_request()), + existing=existing_obj, + post=True, + is_different=is_different, + ) + + if gcp_v2.empty(existing_obj): if state == "present": - is_async = op_configs.create.async_uri != "" - create_link = build_link(module, op_configs.create.uri) - create_retries = op_configs.create.timeout - create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(module, "") + op_configs.create.async_uri - # --------- BEGIN custom pre-create code --------- - # --------- END custom pre-create code --------- - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + gcp_v2.debug(module, action="create") try: - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + # --------- BEGIN create code --------- + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") + create_retries = op_configs.create.timeout + create_func = getattr(resource, op_configs.create.verb) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) + + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - changed = True + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) + # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) + + changed = True else: pass # nothing to do else: if state == "absent": - is_async = op_configs.delete.async_uri != "" - delete_link = build_link(module, op_configs.delete.uri) - delete_retries = op_configs.delete.timeout - delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(module, "") + op_configs.delete.async_uri - # --------- BEGIN custom pre-delete code --------- - # --------- END custom pre-delete code --------- - gcp.debug( - module, - msg="Destroying resource", - delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, - ) + gcp_v2.debug(module, action="delete") try: - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") + delete_retries = op_configs.delete.timeout + delete_func = getattr(resource, op_configs.delete.verb) + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( + module, + msg="Destroying resource", + delete_link=delete_link, + async_uri=delete_async_uri, + ) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) - changed = True + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) + # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) + + changed = True else: - gcp.debug(module, existing=existing_obj, request=resource.to_request()) - if resource.diff(existing_obj): - is_async = op_configs.update.async_uri != "" - update_link = build_link(module, op_configs.update.uri) - update_retries = op_configs.update.timeout - update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(module, "") + op_configs.update.async_uri - # --------- BEGIN custom pre-update code --------- - # --------- END custom pre-update code --------- - gcp.debug( - module, - msg="Updating resource", - update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, - ) + if is_different: + gcp_v2.debug(module, action="update") try: - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") + update_retries = op_configs.update.timeout + update_func = getattr(resource, op_configs.update.verb) + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( + module, + msg="Updating resource", + update_link=update_link, + async_uri=update_async_uri, + ) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) + # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) - changed = True - new_obj = decode(resource.get(build_link(module, op_configs.read.uri), allow_not_found=True)) - new_obj = resource.from_response(new_obj or {}) + changed = True + else: + new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_colab_notebook_execution.py b/plugins/modules/gcp_colab_notebook_execution.py index c883b228b..ea7bbdf0f 100644 --- a/plugins/modules/gcp_colab_notebook_execution.py +++ b/plugins/modules/gcp_colab_notebook_execution.py @@ -313,45 +313,28 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports - # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{location}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class CustomEnvironmentSpec(gcp.Resource): +class CustomEnvironmentSpec(gcp_v2.Resource): def _request(self): return { - "machineSpec": gcp.remove_empties( + "machineSpec": gcp_v2.remove_empties( CustomEnvironmentSpecMachineSpec(self.request.get("machine_spec", {})).to_request() ), # remove empty values - "networkSpec": gcp.remove_empties( + "networkSpec": gcp_v2.remove_empties( CustomEnvironmentSpecNetworkSpec(self.request.get("network_spec", {})).to_request() ), # remove empty values - "persistentDiskSpec": gcp.remove_empties( + "persistentDiskSpec": gcp_v2.remove_empties( CustomEnvironmentSpecPersistentDiskSpec(self.request.get("persistent_disk_spec", {})).to_request() ), # remove empty values } - def _response(self): - return { - "machineSpec": CustomEnvironmentSpecMachineSpec().from_response(self.response.get("machineSpec", {})), - "networkSpec": CustomEnvironmentSpecNetworkSpec().from_response(self.response.get("networkSpec", {})), - "persistentDiskSpec": CustomEnvironmentSpecPersistentDiskSpec().from_response( - self.response.get("persistentDiskSpec", {}) - ), - } - -class CustomEnvironmentSpecMachineSpec(gcp.Resource): +class CustomEnvironmentSpecMachineSpec(gcp_v2.Resource): def _request(self): return { "acceleratorCount": self.request.get("accelerator_count"), @@ -359,15 +342,8 @@ def _request(self): "machineType": self.request.get("machine_type"), } - def _response(self): - return { - "acceleratorCount": self.response.get("acceleratorCount"), - "acceleratorType": self.response.get("acceleratorType"), - "machineType": self.response.get("machineType"), - } - -class CustomEnvironmentSpecNetworkSpec(gcp.Resource): +class CustomEnvironmentSpecNetworkSpec(gcp_v2.Resource): def _request(self): return { "enableInternetAccess": self.request.get("enable_internet_access"), @@ -375,84 +351,54 @@ def _request(self): "subnetwork": self.request.get("subnetwork"), } - def _response(self): - return { - "enableInternetAccess": self.response.get("enableInternetAccess"), - "network": self.response.get("network"), - "subnetwork": self.response.get("subnetwork"), - } - -class CustomEnvironmentSpecPersistentDiskSpec(gcp.Resource): +class CustomEnvironmentSpecPersistentDiskSpec(gcp_v2.Resource): def _request(self): return { "diskSizeGb": self.request.get("disk_size_gb"), "diskType": self.request.get("disk_type"), } - def _response(self): - return { - "diskSizeGb": self.response.get("diskSizeGb"), - "diskType": self.response.get("diskType"), - } - -class DataformRepositorySource(gcp.Resource): +class DataformRepositorySource(gcp_v2.Resource): def _request(self): return { "commitSha": self.request.get("commit_sha"), "dataformRepositoryResourceName": self.request.get("dataform_repository_resource_name"), } - def _response(self): - return { - "commitSha": self.response.get("commitSha"), - "dataformRepositoryResourceName": self.response.get("dataformRepositoryResourceName"), - } - -class DirectNotebookSource(gcp.Resource): +class DirectNotebookSource(gcp_v2.Resource): def _request(self): return { "content": self.request.get("content"), } - def _response(self): - return { - "content": self.response.get("content"), - } - -class GcsNotebookSource(gcp.Resource): +class GcsNotebookSource(gcp_v2.Resource): def _request(self): return { "generation": self.request.get("generation"), "uri": self.request.get("uri"), } - def _response(self): - return { - "generation": self.response.get("generation"), - "uri": self.response.get("uri"), - } - -class Colab(gcp.Resource): +class Colab(gcp_v2.Resource): def _request(self): return { - "customEnvironmentSpec": gcp.remove_empties( + "customEnvironmentSpec": gcp_v2.remove_empties( CustomEnvironmentSpec(self.request.get("custom_environment_spec", {})).to_request() ), # remove empty values - "dataformRepositorySource": gcp.remove_empties( + "dataformRepositorySource": gcp_v2.remove_empties( DataformRepositorySource(self.request.get("dataform_repository_source", {})).to_request() ), # remove empty values - "directNotebookSource": gcp.remove_empties( + "directNotebookSource": gcp_v2.remove_empties( DirectNotebookSource(self.request.get("direct_notebook_source", {})).to_request() ), # remove empty values "displayName": self.request.get("display_name"), "executionTimeout": self.request.get("execution_timeout"), "executionUser": self.request.get("execution_user"), - "gcsNotebookSource": gcp.remove_empties( + "gcsNotebookSource": gcp_v2.remove_empties( GcsNotebookSource(self.request.get("gcs_notebook_source", {})).to_request() ), # remove empty values "gcsOutputUri": self.request.get("gcs_output_uri"), @@ -461,22 +407,7 @@ def _request(self): } def _response(self): - return { - "customEnvironmentSpec": CustomEnvironmentSpec().from_response( - self.response.get("customEnvironmentSpec", {}) - ), - "dataformRepositorySource": DataformRepositorySource().from_response( - self.response.get("dataformRepositorySource", {}) - ), - "directNotebookSource": DirectNotebookSource().from_response(self.response.get("directNotebookSource", {})), - "displayName": self.response.get("displayName"), - "executionTimeout": self.response.get("executionTimeout"), - "executionUser": self.response.get("executionUser"), - "gcsNotebookSource": GcsNotebookSource().from_response(self.response.get("gcsNotebookSource", {})), - "gcsOutputUri": self.response.get("gcsOutputUri"), - "notebookRuntimeTemplateResourceName": self.response.get("notebookRuntimeTemplateResourceName"), - "serviceAccount": self.response.get("serviceAccount"), - } + return {} ################################################################################ @@ -484,26 +415,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( state=dict( type="str", @@ -614,17 +529,7 @@ def main(): service_account=dict( type="str", ), - ), - mutually_exclusive=[ - ["custom_environment_spec", "notebook_runtime_template_resource_name"], - ["dataform_repository_source", "direct_notebook_source", "gcs_notebook_source"], - ["execution_user", "service_account"], - ], - required_one_of=[ - ["custom_environment_spec", "notebook_runtime_template_resource_name"], - ["dataform_repository_source", "direct_notebook_source", "gcs_notebook_source"], - ["execution_user", "service_account"], - ], + ) ) if not module.params["scopes"]: @@ -632,17 +537,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{location}/notebookExecutionJobs", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{location}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{location}/notebookExecutionJobs", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/notebookExecutionJobs?notebook_execution_job_id={notebook_execution_job_id}", "async_uri": "{op_id}", @@ -650,7 +549,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/notebookExecutionJobs/{notebook_execution_job_id}", "async_uri": "{op_id}", @@ -658,7 +557,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/notebookExecutionJobs/{notebook_execution_job_id}", "async_uri": "", @@ -666,7 +565,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/notebookExecutionJobs/{notebook_execution_job_id}", "async_uri": "{op_id}", @@ -674,36 +573,39 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = Colab(params, module=module, product="Colab", kind="colab#notebookExecution") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = Colab(request, module=module, product="Colab", kind="colab#notebookExecution", op_configs=op_configs) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload # --------- BEGIN pre-read custom code --------- # for this module, we're hitting the list endpoint and filtering on display name - read_uri = op_configs.base_url.uri + "?filter=displayName=" + params.get("display_name") + read_link = resource.build_link("list") + "?filter=displayName=" + request.get("display_name") # --------- END pre-read custom code --------- - read_url = build_link(params, read_uri) - existing_obj = resource.decode_func(resource.get(read_url, allow_not_found=True) or {}) + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) # --------- BEGIN post-read custom code --------- # if there are existing notebook executions, the call would have returned a list - if not gcp.empty(existing_obj): + if not gcp_v2.empty(existing_obj): for nb in existing_obj.get("notebookExecutionJobs", []): - if nb.get("displayName") == params.get("display_name"): + if nb.get("displayName") == request.get("display_name"): existing_obj = nb break @@ -712,47 +614,42 @@ def main(): if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- + create_link: str = "" # give it a chance for pre-create to overload # --------- BEGIN pre-create custom code --------- # if not provided, blank the notebook execution job id - params["notebook_execution_job_id"] = params.get("notebook_execution_job_id", "") + resource.url_params["notebook_execution_job_id"] = request.get("notebook_execution_job_id", "") # --------- END pre-create custom code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - new_obj = resource.decode_func(new_obj) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -762,32 +659,35 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload # --------- BEGIN pre-delete custom code --------- # need to set required parameter "notebook_execution_job_id" from existing resource name - params["notebook_execution_job_id"] = existing_obj["name"].split("/")[-1] + resource.url_params["notebook_execution_job_id"] = existing_obj["name"].split("/")[-1] + # --------- END pre-delete custom code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) - new_obj = resource.decode_func(new_obj) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -795,8 +695,7 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN custom update code --------- raise Exception("Updating a notebook execution is not supported") @@ -808,9 +707,8 @@ def main(): else: new_obj = existing_obj - new_obj = resource.from_response(resource.get(read_url, allow_not_found=True) or {}) new_obj.update({"changed": changed}) - gcp.debug(module, final_obj=new_obj, changed=changed) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_colab_runtime.py b/plugins/modules/gcp_colab_runtime.py index 7ef596150..122a33905 100644 --- a/plugins/modules/gcp_colab_runtime.py +++ b/plugins/modules/gcp_colab_runtime.py @@ -176,8 +176,7 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports import copy @@ -186,30 +185,19 @@ # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{location}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class NotebookRuntimeTemplateRef(gcp.Resource): +class NotebookRuntimeTemplateRef(gcp_v2.Resource): def _request(self): return { "notebookRuntimeTemplate": self.request.get("notebook_runtime_template"), } - def _response(self): - return { - "notebookRuntimeTemplate": self.response.get("notebookRuntimeTemplate"), - } - -class Colab(gcp.Resource): +class Colab(gcp_v2.Resource): def _request(self): return { "description": self.request.get("description"), "displayName": self.request.get("display_name"), - "notebookRuntimeTemplateRef": gcp.remove_empties( + "notebookRuntimeTemplateRef": gcp_v2.remove_empties( NotebookRuntimeTemplateRef(self.request.get("notebook_runtime_template_ref", {})).to_request() ), # remove empty values "runtimeUser": self.request.get("runtime_user"), @@ -217,57 +205,40 @@ def _request(self): def _response(self): return { - "description": self.response.get("description"), - "displayName": self.response.get("displayName"), "expirationTime": self.response.get("expirationTime"), "isUpgradable": self.response.get("isUpgradable"), - "notebookRuntimeTemplateRef": NotebookRuntimeTemplateRef().from_response( - self.response.get("notebookRuntimeTemplateRef", {}) - ), "notebookRuntimeType": self.response.get("notebookRuntimeType"), - "runtimeUser": self.response.get("runtimeUser"), } + def encode(self, request): + "Custom encoder function, mutates the request object before it is sent to the API." -################################################################################ -# Main -################################################################################ - + # --------- BEGIN custom encoder code --------- + if self._state in ["absent", "update"]: # no encoding when making a DELETE or POST request + return {} + self.debug(func="encoder", encoded=False, request=request) + r = {} + tmp = copy.deepcopy(request) + tplRef = tmp.pop("notebookRuntimeTemplateRef", None) + if tplRef: + r["notebookRuntimeTemplate"] = tplRef["notebookRuntimeTemplate"] # sub-field is required + r["notebookRuntime"] = tmp + self.debug(func="encoder", encoded=True, request=r) -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - # --------- BEGIN custom encoder code --------- - if self._state in ["absent", "update"]: # no encoding when making a DELETE or POST request - return {} - self.debug(func="encoder", encoded=False, obj=obj) - r = {} - tmp = copy.deepcopy(obj) - tplRef = tmp.pop("notebookRuntimeTemplateRef", None) - if tplRef: - r["notebookRuntimeTemplate"] = tplRef["notebookRuntimeTemplate"] # sub-field is required - r["notebookRuntime"] = tmp - self.debug(func="encoder", encoded=True, obj=r) + return r - return r + # --------- END custom encoder code --------- - # --------- END custom encoder code --------- - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj +################################################################################ +# Main +################################################################################ def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -318,17 +289,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{location}/notebookRuntimes", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{location}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{location}/notebookRuntimes", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/notebookRuntimes:assign?notebook_runtime_id={name}", "async_uri": "{op_id}", @@ -336,7 +301,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/notebookRuntimes/{name}", "async_uri": "{op_id}", @@ -344,7 +309,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/notebookRuntimes/{name}", "async_uri": "", @@ -352,7 +317,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/notebookRuntimes/{name}", "async_uri": "{op_id}", @@ -360,91 +325,89 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = Colab(params, module=module, product="Colab", kind="colab#runtime") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = Colab(request, module=module, product="Colab", kind="colab#runtime", op_configs=op_configs) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload # --------- BEGIN pre-read custom code --------- - def change_state(state_link, async_state_link, resource, retries, verb): + def change_state(state_link, async_uri, resource, retries, verb): """Change the state of the runtime""" resource._state = "update" async_state_func = getattr(resource, "post_async") - return async_state_func(state_link + f":{verb}", async_link=async_state_link, retries=retries) + return async_state_func(state_link + f":{verb}", async_uri=async_uri, retries=retries) # --------- END pre-read custom code --------- - read_url = build_link(params, read_uri) - existing_obj = resource.decode_func(resource.get(read_url, allow_not_found=True) or {}) + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - new_obj = resource.decode_func(new_obj) - gcp.debug(module, new=new_obj, action="create", post=False) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) # --------- BEGIN post-create custom code --------- # stop runtime if desired_state is STOPPED - if params.get("desired_state") == "STOPPED": - gcp.debug(module, msg="Changing runtime state to STOPPED") - state_link = build_link(params, op_configs.update.uri) - state_async_uri = op_configs.update.async_uri - async_state_link = build_link(params, "") + state_async_uri - new_obj = change_state(state_link, async_state_link, resource, op_configs.update.timeout, "stop") + if module.params.get("desired_state") == "STOPPED": + gcp_v2.debug(module, msg="Changing runtime state to STOPPED") + state_link = resource.build_link("update") + new_obj = change_state( + state_link, op_configs.update.async_uri, resource, op_configs.update.timeout, "stop" + ) # wait for runtime to reach desired state while True: - new_obj = resource.get(read_url) - gcp.debug(module, polling=new_obj, action="post_create") - if new_obj.get("runtimeState") == params.get("desired_state"): + new_obj = resource.get(read_link) + gcp_v2.debug(module, polling=new_obj, action="post_create") + if new_obj.get("runtimeState") == module.params.get("desired_state"): break time.sleep(1) # --------- END post-create custom code --------- - gcp.debug(module, new=new_obj, action="create", post=True) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -454,28 +417,30 @@ def change_state(state_link, async_state_link, resource, retries, verb): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) - new_obj = resource.decode_func(new_obj) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -483,36 +448,35 @@ def change_state(state_link, async_state_link, resource, retries, verb): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN custom update code --------- - desired_state = params.get("desired_state") - state_link = build_link(params, update_uri) - async_state_link = build_link(params, "") + update_async_uri + desired_state = module.params.get("desired_state") + state_link = resource.build_link("update") if desired_state != existing_obj.get("runtimeState"): - gcp.debug(module, msg=f"Changing runtime state to {desired_state}") + gcp_v2.debug(module, msg=f"Changing runtime state to {desired_state}") new_obj = change_state( state_link, - async_state_link, + op_configs.update.async_uri, resource, op_configs.update.timeout, "start" if desired_state == "RUNNING" else "stop", ) # wait for runtime to reach desired state while True: - new_obj = resource.get(read_url) - gcp.debug(module, polling=new_obj, action="custom_update") - if new_obj.get("runtimeState") == params.get("desired_state"): + new_obj = resource.get(read_link) + gcp_v2.debug(module, polling=new_obj, action="custom_update") + if new_obj.get("runtimeState") == module.params.get("desired_state"): break time.sleep(1) - auto_upgrade = params.get("auto_upgrade", False) + auto_upgrade = module.params.get("auto_upgrade", False) is_upgradable = existing_obj.get("isUpgradable", False) if auto_upgrade and is_upgradable: - gcp.debug(module, msg="Upgrading runtime") + gcp_v2.debug(module, msg="Upgrading runtime") new_obj = change_state( - state_link, async_state_link, resource, op_configs.update.timeout, "upgrade" + state_link, op_configs.update.async_uri, resource, op_configs.update.timeout, "upgrade" ) + # --------- END custom update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -521,9 +485,8 @@ def change_state(state_link, async_state_link, resource, retries, verb): else: new_obj = existing_obj - new_obj = resource.from_response(resource.get(read_url, allow_not_found=True) or {}) new_obj.update({"changed": changed}) - gcp.debug(module, final_obj=new_obj, changed=changed) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_colab_runtime_template.py b/plugins/modules/gcp_colab_runtime_template.py index 8345ec549..54296cb66 100644 --- a/plugins/modules/gcp_colab_runtime_template.py +++ b/plugins/modules/gcp_colab_runtime_template.py @@ -99,6 +99,7 @@ labels: description: - Labels to identify and group the runtime template. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict location: description: @@ -249,71 +250,42 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports - # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{location}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class DataPersistentDiskSpec(gcp.Resource): +class DataPersistentDiskSpec(gcp_v2.Resource): def _request(self): return { "diskSizeGb": self.request.get("disk_size_gb"), "diskType": self.request.get("disk_type"), } - def _response(self): - return { - "diskSizeGb": self.response.get("diskSizeGb"), - "diskType": self.response.get("diskType"), - } - -class EncryptionSpec(gcp.Resource): +class EncryptionSpec(gcp_v2.Resource): def _request(self): return { "kmsKeyName": self.request.get("kms_key_name"), } - def _response(self): - return { - "kmsKeyName": self.response.get("kmsKeyName"), - } - -class EucConfig(gcp.Resource): +class EucConfig(gcp_v2.Resource): def _request(self): return { "eucDisabled": self.request.get("euc_disabled"), } - def _response(self): - return { - "eucDisabled": self.response.get("eucDisabled"), - } - -class IdleShutdownConfig(gcp.Resource): +class IdleShutdownConfig(gcp_v2.Resource): def _request(self): return { "idleTimeout": self.request.get("idle_timeout"), } - def _response(self): - return { - "idleTimeout": self.response.get("idleTimeout"), - } - -class MachineSpec(gcp.Resource): +class MachineSpec(gcp_v2.Resource): def _request(self): return { "acceleratorCount": self.request.get("accelerator_count"), @@ -321,15 +293,8 @@ def _request(self): "machineType": self.request.get("machine_type"), } - def _response(self): - return { - "acceleratorCount": self.response.get("acceleratorCount"), - "acceleratorType": self.response.get("acceleratorType"), - "machineType": self.response.get("machineType"), - } - -class NetworkSpec(gcp.Resource): +class NetworkSpec(gcp_v2.Resource): def _request(self): return { "enableInternetAccess": self.request.get("enable_internet_access"), @@ -337,59 +302,33 @@ def _request(self): "subnetwork": self.request.get("subnetwork"), } - def _response(self): - return { - "enableInternetAccess": self.response.get("enableInternetAccess"), - "network": self.response.get("network"), - "subnetwork": self.response.get("subnetwork"), - } - -class ShieldedVmConfig(gcp.Resource): +class ShieldedVmConfig(gcp_v2.Resource): def _request(self): return { "enableSecureBoot": self.request.get("enable_secure_boot"), } - def _response(self): - return { - "enableSecureBoot": self.response.get("enableSecureBoot"), - } - -class SoftwareConfig(gcp.Resource): +class SoftwareConfig(gcp_v2.Resource): def _request(self): return { "env": [SoftwareConfigEnv(item).to_request() for item in (self.request.get("env") or [])], - "postStartupScriptConfig": gcp.remove_empties( + "postStartupScriptConfig": gcp_v2.remove_empties( SoftwareConfigPostStartupScriptConfig(self.request.get("post_startup_script_config", {})).to_request() ), # remove empty values } - def _response(self): - return { - "env": [SoftwareConfigEnv().from_response(item) for item in (self.response.get("env") or [])], - "postStartupScriptConfig": SoftwareConfigPostStartupScriptConfig().from_response( - self.response.get("postStartupScriptConfig", {}) - ), - } - -class SoftwareConfigEnv(gcp.Resource): +class SoftwareConfigEnv(gcp_v2.Resource): def _request(self): return { "name": self.request.get("name"), "value": self.request.get("value"), } - def _response(self): - return { - "name": self.response.get("name"), - "value": self.response.get("value"), - } - -class SoftwareConfigPostStartupScriptConfig(gcp.Resource): +class SoftwareConfigPostStartupScriptConfig(gcp_v2.Resource): def _request(self): return { "postStartupScript": self.request.get("post_startup_script"), @@ -397,66 +336,43 @@ def _request(self): "postStartupScriptUrl": self.request.get("post_startup_script_url"), } - def _response(self): - return { - "postStartupScript": self.response.get("postStartupScript"), - "postStartupScriptBehavior": self.response.get("postStartupScriptBehavior"), - "postStartupScriptUrl": self.response.get("postStartupScriptUrl"), - } - -class Colab(gcp.Resource): +class Colab(gcp_v2.Resource): def _request(self): return { - "dataPersistentDiskSpec": gcp.remove_empties( + "dataPersistentDiskSpec": gcp_v2.remove_empties( DataPersistentDiskSpec(self.request.get("data_persistent_disk_spec", {})).to_request() ), # remove empty values "description": self.request.get("description"), "displayName": self.request.get("display_name"), - "encryptionSpec": gcp.remove_empties( + "encryptionSpec": gcp_v2.remove_empties( EncryptionSpec(self.request.get("encryption_spec", {})).to_request() ), # remove empty values - "eucConfig": gcp.remove_empties( + "eucConfig": gcp_v2.remove_empties( EucConfig(self.request.get("euc_config", {})).to_request() ), # remove empty values - "idleShutdownConfig": gcp.remove_empties( + "idleShutdownConfig": gcp_v2.remove_empties( IdleShutdownConfig(self.request.get("idle_shutdown_config", {})).to_request() ), # remove empty values "labels": self.request.get("labels"), - "machineSpec": gcp.remove_empties( + "machineSpec": gcp_v2.remove_empties( MachineSpec(self.request.get("machine_spec", {})).to_request() ), # remove empty values "name": self.request.get("name"), - "networkSpec": gcp.remove_empties( + "networkSpec": gcp_v2.remove_empties( NetworkSpec(self.request.get("network_spec", {})).to_request() ), # remove empty values "networkTags": [str(item) for item in (self.request.get("network_tags") or [])], - "shieldedVmConfig": gcp.remove_empties( + "shieldedVmConfig": gcp_v2.remove_empties( ShieldedVmConfig(self.request.get("shielded_vm_config", {})).to_request() ), # remove empty values - "softwareConfig": gcp.remove_empties( + "softwareConfig": gcp_v2.remove_empties( SoftwareConfig(self.request.get("software_config", {})).to_request() ), # remove empty values } def _response(self): - return { - "dataPersistentDiskSpec": DataPersistentDiskSpec().from_response( - self.response.get("dataPersistentDiskSpec", {}) - ), - "description": self.response.get("description"), - "displayName": self.response.get("displayName"), - "encryptionSpec": EncryptionSpec().from_response(self.response.get("encryptionSpec", {})), - "eucConfig": EucConfig().from_response(self.response.get("eucConfig", {})), - "idleShutdownConfig": IdleShutdownConfig().from_response(self.response.get("idleShutdownConfig", {})), - "labels": self.response.get("labels"), - "machineSpec": MachineSpec().from_response(self.response.get("machineSpec", {})), - "name": self.response.get("name"), - "networkSpec": NetworkSpec().from_response(self.response.get("networkSpec", {})), - "networkTags": [str(item) for item in (self.response.get("networkTags") or [])], - "shieldedVmConfig": ShieldedVmConfig().from_response(self.response.get("shieldedVmConfig", {})), - "softwareConfig": SoftwareConfig().from_response(self.response.get("softwareConfig", {})), - } + return {} ################################################################################ @@ -464,26 +380,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -624,17 +524,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{location}/notebookRuntimeTemplates", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{location}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{location}/notebookRuntimeTemplates", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/notebookRuntimeTemplates?notebook_runtime_template_id={name}", "async_uri": "{op_id}", @@ -642,7 +536,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/notebookRuntimeTemplates/{name}", "async_uri": "{op_id}", @@ -650,7 +544,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/notebookRuntimeTemplates/{name}", "async_uri": "", @@ -658,7 +552,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/notebookRuntimeTemplates/{name}", "async_uri": "{op_id}", @@ -666,64 +560,62 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = Colab(params, module=module, product="Colab", kind="colab#runtimeTemplate") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = Colab(request, module=module, product="Colab", kind="colab#runtimeTemplate", op_configs=op_configs) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload - read_url = build_link(params, read_uri) - existing_obj = resource.decode_func(resource.get(read_url, allow_not_found=True) or {}) + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - new_obj = resource.decode_func(new_obj) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -733,28 +625,30 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) - new_obj = resource.decode_func(new_obj) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -762,30 +656,29 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - new_obj = resource.decode_func(new_obj) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -794,9 +687,8 @@ def main(): else: new_obj = existing_obj - new_obj = resource.from_response(resource.get(read_url, allow_not_found=True) or {}) new_obj.update({"changed": changed}) - gcp.debug(module, final_obj=new_obj, changed=changed) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_colab_schedule.py b/plugins/modules/gcp_colab_schedule.py index 3e95dc19f..7a5c684e4 100644 --- a/plugins/modules/gcp_colab_schedule.py +++ b/plugins/modules/gcp_colab_schedule.py @@ -251,8 +251,7 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports import copy @@ -260,34 +259,21 @@ # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{location}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class CreateNotebookExecutionJobRequest(gcp.Resource): +class CreateNotebookExecutionJobRequest(gcp_v2.Resource): def _request(self): return { - "notebookExecutionJob": gcp.remove_empties( + "notebookExecutionJob": gcp_v2.remove_empties( CreateNotebookExecutionJobRequestNotebookExecutionJob( self.request.get("notebook_execution_job", {}) ).to_request() ), # remove empty values } - def _response(self): - return { - "notebookExecutionJob": CreateNotebookExecutionJobRequestNotebookExecutionJob().from_response( - self.response.get("notebookExecutionJob", {}) - ), - } - -class CreateNotebookExecutionJobRequestNotebookExecutionJob(gcp.Resource): +class CreateNotebookExecutionJobRequestNotebookExecutionJob(gcp_v2.Resource): def _request(self): return { - "dataformRepositorySource": gcp.remove_empties( + "dataformRepositorySource": gcp_v2.remove_empties( CreateNotebookExecutionJobRequestNotebookExecutionJobDataformRepositorySource( self.request.get("dataform_repository_source", {}) ).to_request() @@ -295,7 +281,7 @@ def _request(self): "displayName": self.request.get("display_name"), "executionTimeout": self.request.get("execution_timeout"), "executionUser": self.request.get("execution_user"), - "gcsNotebookSource": gcp.remove_empties( + "gcsNotebookSource": gcp_v2.remove_empties( CreateNotebookExecutionJobRequestNotebookExecutionJobGcsNotebookSource( self.request.get("gcs_notebook_source", {}) ).to_request() @@ -305,56 +291,28 @@ def _request(self): "serviceAccount": self.request.get("service_account"), } - def _response(self): - return { - "dataformRepositorySource": CreateNotebookExecutionJobRequestNotebookExecutionJobDataformRepositorySource().from_response( - self.response.get("dataformRepositorySource", {}) - ), - "displayName": self.response.get("displayName"), - "executionTimeout": self.response.get("executionTimeout"), - "executionUser": self.response.get("executionUser"), - "gcsNotebookSource": CreateNotebookExecutionJobRequestNotebookExecutionJobGcsNotebookSource().from_response( - self.response.get("gcsNotebookSource", {}) - ), - "gcsOutputUri": self.response.get("gcsOutputUri"), - "notebookRuntimeTemplateResourceName": self.response.get("notebookRuntimeTemplateResourceName"), - "serviceAccount": self.response.get("serviceAccount"), - } - -class CreateNotebookExecutionJobRequestNotebookExecutionJobDataformRepositorySource(gcp.Resource): +class CreateNotebookExecutionJobRequestNotebookExecutionJobDataformRepositorySource(gcp_v2.Resource): def _request(self): return { "commitSha": self.request.get("commit_sha"), "dataformRepositoryResourceName": self.request.get("dataform_repository_resource_name"), } - def _response(self): - return { - "commitSha": self.response.get("commitSha"), - "dataformRepositoryResourceName": self.response.get("dataformRepositoryResourceName"), - } - -class CreateNotebookExecutionJobRequestNotebookExecutionJobGcsNotebookSource(gcp.Resource): +class CreateNotebookExecutionJobRequestNotebookExecutionJobGcsNotebookSource(gcp_v2.Resource): def _request(self): return { "generation": self.request.get("generation"), "uri": self.request.get("uri"), } - def _response(self): - return { - "generation": self.response.get("generation"), - "uri": self.response.get("uri"), - } - -class Colab(gcp.Resource): +class Colab(gcp_v2.Resource): def _request(self): return { "allowQueueing": self.request.get("allow_queueing"), - "createNotebookExecutionJobRequest": gcp.remove_empties( + "createNotebookExecutionJobRequest": gcp_v2.remove_empties( CreateNotebookExecutionJobRequest( self.request.get("create_notebook_execution_job_request", {}) ).to_request() @@ -369,72 +327,56 @@ def _request(self): def _response(self): return { - "allowQueueing": self.response.get("allowQueueing"), - "createNotebookExecutionJobRequest": CreateNotebookExecutionJobRequest().from_response( - self.response.get("createNotebookExecutionJobRequest", {}) - ), - "cron": self.response.get("cron"), - "displayName": self.response.get("displayName"), - "endTime": self.response.get("endTime"), - "maxConcurrentRunCount": self.response.get("maxConcurrentRunCount"), - "maxRunCount": self.response.get("maxRunCount"), "name": self.response.get("name"), - "startTime": self.response.get("startTime"), } + def encode(self, request): + "Custom encoder function, mutates the request object before it is sent to the API." + + # --------- BEGIN custom encoder code --------- + # I am using "change" state to cheat and indicate the desired state is being changed + if self._state in ["absent", "change"]: + return {} + # createNotebookExecutionJobRequest does not exist in update requests + r = copy.deepcopy(request) + if self._state == "update": + r.pop("createNotebookExecutionJobRequest") + else: + # for create requests, we need to set the parent, idk why it is done this way + r["createNotebookExecutionJobRequest"]["parent"] = ( + "projects/" + self.module.params.get("project") + "/locations/" + self.module.params.get("location") + ) -################################################################################ -# Main -################################################################################ - + return r -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - # --------- BEGIN custom encoder code --------- - # I am using "change" state to cheat and indicate the desired state is being changed - if self._state in ["absent", "change"]: - return {} - # createNotebookExecutionJobRequest does not exist in update requests - r = copy.deepcopy(obj) - if self._state == "update": - r.pop("createNotebookExecutionJobRequest") - else: - # for create requests, we need to set the parent, idk why it is done this way - r["createNotebookExecutionJobRequest"]["parent"] = ( - "projects/" + self.module.params.get("project") + "/locations/" + self.module.params.get("location") - ) + # --------- END custom encoder code --------- - return r + def decode(self, response): + "Custom decoder function, mutates the response object before it is returned to the module caller." - # --------- END custom encoder code --------- + # --------- BEGIN custom decoder code --------- + # flatten the schedules list to a single object + r = copy.deepcopy(response) + if not gcp_v2.empty(response): + schedules = r.pop("schedules", []) + for schedule in schedules: + if schedule.get("displayName") == self.module.params.get("display_name"): + r.update(schedule) + break + return r + # --------- END custom decoder code --------- -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - # --------- BEGIN custom decoder code --------- - # flatten the schedules list to a single object - r = copy.deepcopy(obj) - if not gcp.empty(obj): - schedules = r.pop("schedules", []) - for schedule in schedules: - if schedule.get("displayName") == self.module.params.get("display_name"): - r.update(schedule) - break - return r - # --------- END custom decoder code --------- +################################################################################ +# Main +################################################################################ def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( state=dict( type="str", @@ -498,14 +440,6 @@ def main(): type="str", ), ), - mutually_exclusive=[ - ["dataform_repository_source", "gcs_notebook_source"], - ["execution_user", "service_account"], - ], - required_one_of=[ - ["dataform_repository_source", "gcs_notebook_source"], - ["execution_user", "service_account"], - ], ) ), ), @@ -547,17 +481,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{location}/schedules", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{location}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{location}/schedules", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/schedules", "async_uri": "", @@ -565,7 +493,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/schedules/{name}", "async_uri": "{op_id}", @@ -573,7 +501,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/schedules/{name}", "async_uri": "", @@ -581,27 +509,29 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ - "uri": "projects/{project}/locations/{location}/schedules/{name}", + "uri": "projects/{project}/locations/{location}/schedules/{name}?updateMask={update_mask}", "async_uri": "", "verb": "PATCH", "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = Colab(params, module=module, product="Colab", kind="colab#schedule") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = Colab(request, module=module, product="Colab", kind="colab#schedule", op_configs=op_configs) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload # --------- BEGIN pre-read custom code --------- def change_state(state_link, resource, verb): @@ -611,19 +541,20 @@ def change_state(state_link, resource, verb): state_func = getattr(resource, "post") return state_func(state_link + f":{verb}") - # for this module, we're hitting the list endpoint and filtering on display name - read_uri = op_configs.base_url.uri + "?filter=displayName=" + params.get("display_name") + # for this module, we're hitting the list endpoint and filtering on display name and rebuild the link + read_link = resource.build_link("list") + "?filter=displayName=" + module.params.get("display_name") # --------- END pre-read custom code --------- - read_url = build_link(params, read_uri) - existing_obj = resource.decode_func(resource.get(read_url, allow_not_found=True) or {}) + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) # --------- BEGIN post-read custom code --------- - if not gcp.empty(existing_obj): - if existing_obj.get("state") != params.get("desired_state"): + if not gcp_v2.empty(existing_obj): + if existing_obj.get("state") != module.params.get("desired_state"): custom_diff = True # force update code path # --------- END post-read custom code --------- @@ -631,51 +562,49 @@ def change_state(state_link, resource, verb): if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - new_obj = resource.decode_func(new_obj) - gcp.debug(module, new=new_obj, action="create", post=False) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) # --------- BEGIN post-create custom code --------- # pause schedule if desired_state is PAUSED - if params.get("desired_state") == "PAUSED": - params["name"] = new_obj["name"].split("/")[-1] - gcp.debug(module, msg="Changing schedule state to PAUSED") - state_link = build_link(params, op_configs.update.uri) - new_obj = change_state(state_link, resource, "pause") + if module.params.get("desired_state") == "PAUSED": + resource.url_params["name"] = new_obj["name"].split("/")[-1] + gcp_v2.debug(module, msg="Changing schedule state to PAUSED") + self_link = resource.build_link("read") + change_state(self_link, resource, "pause") + # pausing/resuming returns empty body, so we have to re-read the object + new_obj = resource.get(self_link) + new_obj = resource.from_response(new_obj) # --------- END post-create custom code --------- - gcp.debug(module, new=new_obj, action="create", post=True) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -685,31 +614,34 @@ def change_state(state_link, resource, verb): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload # --------- BEGIN pre-delete custom code --------- - params["name"] = existing_obj["name"].split("/")[-1] + resource.url_params["name"] = existing_obj["name"].split("/")[-1] + # --------- END pre-delete custom code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) - new_obj = resource.decode_func(new_obj) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -717,56 +649,57 @@ def change_state(state_link, resource, verb): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload # --------- BEGIN pre-update custom code --------- resource._state = "update" - params["name"] = existing_obj["name"].split("/")[-1] - update_mask = [ - "cron", - "startTime", - "endTime", - "maxRunCount", - "maxConcurrentRunCount", - "maxConcurrentActiveRunCount", - "allowQueueing", - ] - update_uri += "?updateMask=" + ",".join(update_mask) + resource.url_params["name"] = existing_obj["name"].split("/")[-1] + resource.url_params["update_mask"] = ",".join( + [ + "cron", + "startTime", + "endTime", + "maxRunCount", + "maxConcurrentRunCount", + "maxConcurrentActiveRunCount", + "allowQueueing", + ] + ) # --------- END pre-update custom code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - new_obj = resource.decode_func(new_obj) - gcp.debug(module, new=new_obj, action="update", post=False) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) # --------- BEGIN post-update custom code --------- - if params.get("desired_state") != existing_obj.get("state"): - gcp.debug(module, msg="Changing schedule state to {params.get('desired_state')}") - state_link = build_link(params, op_configs.update.uri) - new_obj = change_state( - state_link, resource, "pause" if params.get("desired_state") == "PAUSED" else "resume" + if module.params.get("desired_state") != existing_obj.get("state"): + self_link = resource.build_link("read") + change_state( + self_link, resource, "pause" if module.params.get("desired_state") == "PAUSED" else "resume" ) - new_obj = resource.decode_func(new_obj) + # pausing/resuming returns empty body, so we have to re-read the object + new_obj = resource.get(self_link) + new_obj = resource.from_response(new_obj) changed = True # --------- END post-update custom code --------- - gcp.debug(module, new=new_obj, action="update", post=True) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -775,9 +708,8 @@ def change_state(state_link, resource, verb): else: new_obj = existing_obj - new_obj = resource.from_response(resource.get(read_url, allow_not_found=True) or {}) new_obj.update({"changed": changed}) - gcp.debug(module, final_obj=new_obj, changed=changed) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_dataset.py b/plugins/modules/gcp_vertexai_dataset.py index 2286a0cd7..5197cf8e6 100644 --- a/plugins/modules/gcp_vertexai_dataset.py +++ b/plugins/modules/gcp_vertexai_dataset.py @@ -52,29 +52,34 @@ description: - Customer-managed encryption key spec for a Dataset. - If set, this Dataset and all sub-resources of this Dataset will be secured by this key. + - This property is immutable, to change it, you must delete and recreate the resource. suboptions: kms_key_name: description: - The Cloud KMS resource identifier of the customer managed encryption key used to protect a resource. - 'Has the form: projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key.' - The key needs to be in the same region as where the resource is created. + - This property is immutable, to change it, you must delete and recreate the resource. type: str type: dict labels: description: - A set of key/value label pairs to assign to this Workflow. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict metadata_schema_uri: description: - Points to a YAML file stored on Google Cloud Storage describing additional information about the Dataset. - The schema is defined as an OpenAPI 3.0.2 Schema Object. - The schema files that can be used here are found in gs://google-cloud-aiplatform/schema/dataset/metadata/. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str region: description: - The region of the dataset. - eg us-central1. + - This property is immutable, to change it, you must delete and recreate the resource. type: str state: choices: @@ -170,37 +175,24 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports - # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class EncryptionSpec(gcp.Resource): +class EncryptionSpec(gcp_v2.Resource): def _request(self): return { "kmsKeyName": self.request.get("kms_key_name"), } - def _response(self): - return { - "kmsKeyName": self.response.get("kmsKeyName"), - } - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { "displayName": self.request.get("display_name"), - "encryptionSpec": gcp.remove_empties( + "encryptionSpec": gcp_v2.remove_empties( EncryptionSpec(self.request.get("encryption_spec", {})).to_request() ), # remove empty values "labels": self.request.get("labels"), @@ -210,10 +202,6 @@ def _request(self): def _response(self): return { "createTime": self.response.get("createTime"), - "displayName": self.response.get("displayName"), - "encryptionSpec": EncryptionSpec().from_response(self.response.get("encryptionSpec", {})), - "labels": self.response.get("labels"), - "metadataSchemaUri": self.response.get("metadataSchemaUri"), "name": self.response.get("name"), "updateTime": self.response.get("updateTime"), } @@ -224,26 +212,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( state=dict( type="str", @@ -281,17 +253,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{region}/datasets", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{region}/datasets", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/datasets", "async_uri": "{op_id}", @@ -299,43 +265,46 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{"uri": "{name}", "async_uri": "{op_id}", "verb": "DELETE", "timeout_minutes": 20} ), - "read": gcp.ResourceOpConfig(**{"uri": "{name}", "async_uri": "", "verb": "GET", "timeout_minutes": 0}), - "update": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig(**{"uri": "{name}", "async_uri": "", "verb": "GET", "timeout_minutes": 0}), + "update": gcp_v2.ResourceOpConfig( **{"uri": "{name}", "async_uri": "", "verb": "PATCH", "timeout_minutes": 20} ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#dataset") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI(request, module=module, product="VertexAI", kind="vertexai#dataset", op_configs=op_configs) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload # --------- BEGIN pre-read custom code --------- # for this module, we're hitting the list endpoint and filtering on display name - read_uri = f"{op_configs.base_url.uri}?filter=displayName={params.get('display_name')}" + read_link = resource.build_link("list") + "?filter=displayName=" + request.get("display_name") # --------- END pre-read custom code --------- - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) # --------- BEGIN post-read custom code --------- # if there are existing datasets, the call would have returned a list - if not gcp.empty(existing_obj): + if not gcp_v2.empty(existing_obj): for ds in existing_obj.get("datasets", []): - if ds.get("displayName") == params.get("display_name"): + if ds.get("displayName") == request.get("display_name"): existing_obj = ds break @@ -344,41 +313,37 @@ def main(): if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -388,31 +353,35 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload # --------- BEGIN pre-delete custom code --------- # need to set to the required parameter "name" to the existing resource name - params["name"] = existing_obj["name"] + resource.url_params["name"] = existing_obj["name"] + # --------- END pre-delete custom code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -420,37 +389,38 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload # --------- BEGIN pre-update custom code --------- # need to set to the required parameter "name" to the existing resource name - params["name"] = existing_obj["name"] + resource.url_params["name"] = existing_obj["name"] - # need to build the updateMask for the fields in our module - update_uri += "?updateMask=labels," + ",".join(resource.dot_fields()) + # need to build the link with the updateMask + update_link = resource.build_link("update") + update_link += "?updateMask=" + ",".join(resource.dot_fields()) # --------- END pre-update custom code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -460,6 +430,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_deployment_resource_pool.py b/plugins/modules/gcp_vertexai_deployment_resource_pool.py index 5f818ea12..56f1dbd2a 100644 --- a/plugins/modules/gcp_vertexai_deployment_resource_pool.py +++ b/plugins/modules/gcp_vertexai_deployment_resource_pool.py @@ -49,6 +49,7 @@ autoscaling_metric_specs: description: - A list of the metric specifications that overrides a resource utilization metric. + - This property is immutable, to change it, you must delete and recreate the resource. elements: dict suboptions: metric_name: @@ -66,6 +67,7 @@ machine_spec: description: - The specification of a single machine used by the prediction. + - This property is immutable, to change it, you must delete and recreate the resource. required: true suboptions: accelerator_count: @@ -81,6 +83,7 @@ description: - The type of the machine. - See the [list of machine types supported for prediction](https://cloud.google.com/vertex-ai/docs/predictions/configure-compute#machine-types). + - This property is immutable, to change it, you must delete and recreate the resource. type: str type: dict max_replica_count: @@ -91,12 +94,14 @@ - If this value is not provided, will use min_replica_count as the default value. - The value of this field impacts the charge against Vertex CPU and GPU quotas. - Specifically, you will be charged for max_replica_count * number of cores in the selected machine type) and (max_replica_count * number of GPUs per replica in the selected machine type). + - This property is immutable, to change it, you must delete and recreate the resource. type: int min_replica_count: description: - The minimum number of machine replicas this DeployedModel will be always deployed on. - This value must be greater than or equal to 1. - If traffic against the DeployedModel increases, it may dynamically be deployed onto more replicas, and as traffic decreases, some of these extra replicas may be freed. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: int type: dict @@ -104,12 +109,14 @@ description: - The resource name of deployment resource pool. - The maximum length is 63 characters, and valid characters are `/^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$/`. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str region: description: - The region of deployment resource pool. - eg us-central1. + - This property is immutable, to change it, you must delete and recreate the resource. type: str state: choices: @@ -164,8 +171,7 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports import copy @@ -173,53 +179,30 @@ # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class DedicatedResources(gcp.Resource): +class DedicatedResources(gcp_v2.Resource): def _request(self): return { "autoscalingMetricSpecs": [ DedicatedResourcesAutoscalingMetricSpec(item).to_request() for item in (self.request.get("autoscaling_metric_specs") or []) ], - "machineSpec": gcp.remove_empties( + "machineSpec": gcp_v2.remove_empties( DedicatedResourcesMachineSpec(self.request.get("machine_spec", {})).to_request() ), # remove empty values "maxReplicaCount": self.request.get("max_replica_count"), "minReplicaCount": self.request.get("min_replica_count"), } - def _response(self): - return { - "autoscalingMetricSpecs": [ - DedicatedResourcesAutoscalingMetricSpec().from_response(item) - for item in (self.response.get("autoscalingMetricSpecs") or []) - ], - "machineSpec": DedicatedResourcesMachineSpec().from_response(self.response.get("machineSpec", {})), - "maxReplicaCount": self.response.get("maxReplicaCount"), - "minReplicaCount": self.response.get("minReplicaCount"), - } - -class DedicatedResourcesAutoscalingMetricSpec(gcp.Resource): +class DedicatedResourcesAutoscalingMetricSpec(gcp_v2.Resource): def _request(self): return { "metricName": self.request.get("metric_name"), "target": self.request.get("target"), } - def _response(self): - return { - "metricName": self.response.get("metricName"), - "target": self.response.get("target"), - } - -class DedicatedResourcesMachineSpec(gcp.Resource): +class DedicatedResourcesMachineSpec(gcp_v2.Resource): def _request(self): return { "acceleratorCount": self.request.get("accelerator_count"), @@ -227,18 +210,11 @@ def _request(self): "machineType": self.request.get("machine_type"), } - def _response(self): - return { - "acceleratorCount": self.response.get("acceleratorCount"), - "acceleratorType": self.response.get("acceleratorType"), - "machineType": self.response.get("machineType"), - } - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { - "dedicatedResources": gcp.remove_empties( + "dedicatedResources": gcp_v2.remove_empties( DedicatedResources(self.request.get("dedicated_resources", {})).to_request() ), # remove empty values "name": self.request.get("name"), @@ -247,53 +223,43 @@ def _request(self): def _response(self): return { "createTime": self.response.get("createTime"), - "dedicatedResources": DedicatedResources().from_response(self.response.get("dedicatedResources", {})), - "name": self.response.get("name"), } + def encode(self, request): + "Custom encoder function, mutates the request object before it is sent to the API." + + # --------- BEGIN custom encoder code --------- + self.debug(func="encoder", request=request, encoded=False) + action = getattr(self, "_action", "read") + r = {} + if action == "create": + r = { + "deploymentResourcePool": copy.deepcopy(request), + "deploymentResourcePoolId": request["name"], + } + elif action == "read": + r = copy.deepcopy(request) + elif action == "update": + r = copy.deepcopy(request) + else: + pass -################################################################################ -# Main -################################################################################ - + self.debug(func="encoder", request=request, encoded=True) -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - # --------- BEGIN custom encoder code --------- - self.debug(func="encoder", obj=obj) - action = getattr(self, "_action", "read") - r = {} - if action == "create": - r = { - "deploymentResourcePool": copy.deepcopy(obj), - "deploymentResourcePoolId": obj["name"], - } - elif action == "read": - r = copy.deepcopy(obj) - elif action == "update": - r = copy.deepcopy(obj) - else: - pass + return r - return r - # --------- END custom encoder code --------- + # --------- END custom encoder code --------- -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj +################################################################################ +# Main +################################################################################ def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -355,17 +321,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{region}/deploymentResourcePools", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{region}/deploymentResourcePools", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/deploymentResourcePools", "async_uri": "{op_id}", @@ -373,7 +333,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/deploymentResourcePools/{name}", "async_uri": "{op_id}", @@ -381,7 +341,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/deploymentResourcePools/{name}", "async_uri": "", @@ -389,7 +349,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/deploymentResourcePools/{name}", "async_uri": "", @@ -397,67 +357,68 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#deploymentResourcePool") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI( + request, module=module, product="VertexAI", kind="vertexai#deploymentResourcePool", op_configs=op_configs + ) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + read_link: str = "" # give it a chance for pre-read to overload + + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- + create_link: str = "" # give it a chance for pre-create to overload # --------- BEGIN pre-create custom code --------- resource._action = "create" # --------- END pre-create custom code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -467,31 +428,34 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload # --------- BEGIN pre-delete custom code --------- resource._action = "delete" # --------- END pre-delete custom code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -499,34 +463,34 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload # --------- BEGIN pre-update custom code --------- resource._action = "update" - update_uri += "?updateMask=" + ",".join(resource.dot_fields()) + update_link = resource.build_link("update") + "?updateMask=" + ",".join(resource.dot_fields()) # --------- END pre-update custom code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -536,6 +500,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_endpoint.py b/plugins/modules/gcp_vertexai_endpoint.py index 927076ae3..414c21952 100644 --- a/plugins/modules/gcp_vertexai_endpoint.py +++ b/plugins/modules/gcp_vertexai_endpoint.py @@ -63,12 +63,14 @@ description: - Customer-managed encryption key spec for an Endpoint. - If set, this Endpoint and all sub-resources of this Endpoint will be secured by this key. + - This property is immutable, to change it, you must delete and recreate the resource. suboptions: kms_key_name: description: - The Cloud KMS resource identifier of the customer managed encryption key used to protect a resource. - 'Has the form: `projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key`.' - The key needs to be in the same region as where the compute resource is created. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str type: dict @@ -78,16 +80,19 @@ - Label keys and values can be no longer than 64 characters (Unicode codepoints), can only contain lowercase letters, numeric characters, underscores and dashes. - International characters are allowed. - See https://goo.gl/xmQnxf for more information and examples of labels. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict location: description: - The location for the resource. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str name: description: - The resource name of the Endpoint. - The name must be numeric with no leading zeros and can be at most 10 digits. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str network: @@ -99,6 +104,7 @@ - '[Format](https://cloud.google.com/compute/docs/reference/rest/v1/networks/insert): `projects/{project}/global/networks/{network}`.' - Where `{project}` is a project number, as in `12345`, and `{network}` is network name. - Only one of the fields, `network` or `privateServiceConnectConfig`, can be set. + - This property is immutable, to change it, you must delete and recreate the resource. type: str predict_request_response_logging_config: description: @@ -137,6 +143,7 @@ enable_private_service_connect: description: - If true, expose the IndexEndpoint via private service connect. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: bool enable_secure_private_service_connect: @@ -191,6 +198,7 @@ region: description: - The region for the resource. + - This property is immutable, to change it, you must delete and recreate the resource. type: str state: choices: @@ -460,12 +468,6 @@ elements: dict returned: success type: list -etag: - description: - - Used to perform consistent read-modify-write updates. - - If not set, a blind "overwrite" update happens. - returned: success - type: str modelDeploymentMonitoringJob: description: - Output only. @@ -489,21 +491,13 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports - # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class DeployedModels(gcp.Resource): +class DeployedModels(gcp_v2.Resource): def _response(self): return { "automaticResources": DeployedModelsAutomaticResources().from_response( @@ -527,7 +521,7 @@ def _response(self): } -class DeployedModelsAutomaticResources(gcp.Resource): +class DeployedModelsAutomaticResources(gcp_v2.Resource): def _response(self): return { "maxReplicaCount": self.response.get("maxReplicaCount"), @@ -535,7 +529,7 @@ def _response(self): } -class DeployedModelsDedicatedResources(gcp.Resource): +class DeployedModelsDedicatedResources(gcp_v2.Resource): def _response(self): return { "autoscalingMetricSpecs": [ @@ -550,7 +544,7 @@ def _response(self): } -class DeployedModelsDedicatedResourcesAutoscalingMetricSpec(gcp.Resource): +class DeployedModelsDedicatedResourcesAutoscalingMetricSpec(gcp_v2.Resource): def _response(self): return { "metricName": self.response.get("metricName"), @@ -558,7 +552,7 @@ def _response(self): } -class DeployedModelsDedicatedResourcesMachineSpec(gcp.Resource): +class DeployedModelsDedicatedResourcesMachineSpec(gcp_v2.Resource): def _response(self): return { "acceleratorCount": self.response.get("acceleratorCount"), @@ -567,7 +561,7 @@ def _response(self): } -class DeployedModelsPrivateEndpoints(gcp.Resource): +class DeployedModelsPrivateEndpoints(gcp_v2.Resource): def _response(self): return { "explainHttpUri": self.response.get("explainHttpUri"), @@ -577,22 +571,17 @@ def _response(self): } -class EncryptionSpec(gcp.Resource): +class EncryptionSpec(gcp_v2.Resource): def _request(self): return { "kmsKeyName": self.request.get("kms_key_name"), } - def _response(self): - return { - "kmsKeyName": self.response.get("kmsKeyName"), - } - -class PredictRequestResponseLoggingConfig(gcp.Resource): +class PredictRequestResponseLoggingConfig(gcp_v2.Resource): def _request(self): return { - "bigqueryDestination": gcp.remove_empties( + "bigqueryDestination": gcp_v2.remove_empties( PredictRequestResponseLoggingConfigBigqueryDestination( self.request.get("bigquery_destination", {}) ).to_request() @@ -601,29 +590,15 @@ def _request(self): "samplingRate": self.request.get("sampling_rate"), } - def _response(self): - return { - "bigqueryDestination": PredictRequestResponseLoggingConfigBigqueryDestination().from_response( - self.response.get("bigqueryDestination", {}) - ), - "enabled": self.response.get("enabled"), - "samplingRate": self.response.get("samplingRate"), - } - -class PredictRequestResponseLoggingConfigBigqueryDestination(gcp.Resource): +class PredictRequestResponseLoggingConfigBigqueryDestination(gcp_v2.Resource): def _request(self): return { "outputUri": self.request.get("output_uri"), } - def _response(self): - return { - "outputUri": self.response.get("outputUri"), - } - -class PrivateServiceConnectConfig(gcp.Resource): +class PrivateServiceConnectConfig(gcp_v2.Resource): def _request(self): return { "enablePrivateServiceConnect": self.request.get("enable_private_service_connect"), @@ -635,19 +610,8 @@ def _request(self): ], } - def _response(self): - return { - "enablePrivateServiceConnect": self.response.get("enablePrivateServiceConnect"), - "enableSecurePrivateServiceConnect": self.response.get("enableSecurePrivateServiceConnect"), - "projectAllowlist": self.response.get("projectAllowlist"), - "pscAutomationConfigs": [ - PrivateServiceConnectConfigPscAutomationConfig().from_response(item) - for item in (self.response.get("pscAutomationConfigs") or []) - ], - } - -class PrivateServiceConnectConfigPscAutomationConfig(gcp.Resource): +class PrivateServiceConnectConfigPscAutomationConfig(gcp_v2.Resource): def _request(self): return { "network": self.request.get("network"), @@ -659,29 +623,27 @@ def _response(self): "errorMessage": self.response.get("errorMessage"), "forwardingRule": self.response.get("forwardingRule"), "ipAddress": self.response.get("ipAddress"), - "network": self.response.get("network"), - "projectId": self.response.get("projectId"), "state": self.response.get("state"), } -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { "dedicatedEndpointEnabled": self.request.get("dedicated_endpoint_enabled"), "description": self.request.get("description"), "displayName": self.request.get("display_name"), - "encryptionSpec": gcp.remove_empties( + "encryptionSpec": gcp_v2.remove_empties( EncryptionSpec(self.request.get("encryption_spec", {})).to_request() ), # remove empty values "labels": self.request.get("labels"), "network": self.request.get("network"), - "predictRequestResponseLoggingConfig": gcp.remove_empties( + "predictRequestResponseLoggingConfig": gcp_v2.remove_empties( PredictRequestResponseLoggingConfig( self.request.get("predict_request_response_logging_config", {}) ).to_request() ), # remove empty values - "privateServiceConnectConfig": gcp.remove_empties( + "privateServiceConnectConfig": gcp_v2.remove_empties( PrivateServiceConnectConfig(self.request.get("private_service_connect_config", {})).to_request() ), # remove empty values "trafficSplit": self.request.get("traffic_split"), @@ -691,24 +653,11 @@ def _response(self): return { "createTime": self.response.get("createTime"), "dedicatedEndpointDns": self.response.get("dedicatedEndpointDns"), - "dedicatedEndpointEnabled": self.response.get("dedicatedEndpointEnabled"), "deployedModels": [ DeployedModels().from_response(item) for item in (self.response.get("deployedModels") or []) ], - "description": self.response.get("description"), - "displayName": self.response.get("displayName"), - "encryptionSpec": EncryptionSpec().from_response(self.response.get("encryptionSpec", {})), "etag": self.response.get("etag"), - "labels": self.response.get("labels"), "modelDeploymentMonitoringJob": self.response.get("modelDeploymentMonitoringJob"), - "network": self.response.get("network"), - "predictRequestResponseLoggingConfig": PredictRequestResponseLoggingConfig().from_response( - self.response.get("predictRequestResponseLoggingConfig", {}) - ), - "privateServiceConnectConfig": PrivateServiceConnectConfig().from_response( - self.response.get("privateServiceConnectConfig", {}) - ), - "trafficSplit": self.response.get("trafficSplit"), "updateTime": self.response.get("updateTime"), } @@ -718,26 +667,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -848,9 +781,9 @@ def main(): ), ), mutually_exclusive=[ - ["dedicated_endpoint_enabled", "network", "private_service_connect_config"], - ["dedicated_endpoint_enabled", "private_service_connect_config"], - ["network", "private_service_connect_config"], + ("dedicated_endpoint_enabled", "network", "private_service_connect_config"), + ("dedicated_endpoint_enabled", "private_service_connect_config"), + ("network", "private_service_connect_config"), ], ) @@ -859,17 +792,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{location}/endpoints", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{location}/endpoints", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/endpoints?endpointId={name}", "async_uri": "{op_id}", @@ -877,7 +804,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/endpoints/{name}", "async_uri": "{op_id}", @@ -885,7 +812,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/endpoints/{name}", "async_uri": "", @@ -893,7 +820,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/endpoints/{name}", "async_uri": "", @@ -901,63 +828,62 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#endpoint") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI(request, module=module, product="VertexAI", kind="vertexai#endpoint", op_configs=op_configs) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + read_link: str = "" # give it a chance for pre-read to overload + + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -967,27 +893,30 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -995,29 +924,29 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -1027,6 +956,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_endpoint_with_model_garden_deployment.py b/plugins/modules/gcp_vertexai_endpoint_with_model_garden_deployment.py index 4d8e66901..001162aed 100644 --- a/plugins/modules/gcp_vertexai_endpoint_with_model_garden_deployment.py +++ b/plugins/modules/gcp_vertexai_endpoint_with_model_garden_deployment.py @@ -60,6 +60,7 @@ - The default target value is 60 for both metrics. - If machine_spec.accelerator_count is 0, the autoscaling will be based on CPU utilization metric only with default target value 60 if not explicitly set. - For example, in the case of Online Prediction, if you want to override target CPU utilization to 80, you should set autoscaling_metric_specs.metric_name to `aiplatform.googleapis.com/prediction/online/cpu/utilization` and autoscaling_metric_specs.target to `80`. + - This property is immutable, to change it, you must delete and recreate the resource. elements: dict suboptions: metric_name: @@ -93,10 +94,12 @@ - See the [list of machine types supported for prediction](https://cloud.google.com/vertex-ai/docs/predictions/configure-compute#machine-types) See the [list of machine types supported for custom training](https://cloud.google.com/vertex-ai/docs/training/configure-compute#machine-types). - For DeployedModel this field is optional, and the default value is `n1-standard-2`. - For BatchPredictionJob or as part of WorkerPoolSpec this field is required. + - This property is immutable, to change it, you must delete and recreate the resource. type: str multihost_gpu_node_count: description: - The number of nodes per replica for multihost GPU deployments. + - This property is immutable, to change it, you must delete and recreate the resource. type: int reservation_affinity: description: @@ -125,6 +128,7 @@ - The topology of the TPUs. - Corresponds to the TPU topologies available from GKE. - '(Example: tpu_topology: "2x2x1").' + - This property is immutable, to change it, you must delete and recreate the resource. type: str type: dict max_replica_count: @@ -135,12 +139,14 @@ - If this value is not provided, will use min_replica_count as the default value. - The value of this field impacts the charge against Vertex CPU and GPU quotas. - Specifically, you will be charged for (max_replica_count * number of cores in the selected machine type) and (max_replica_count * number of GPUs per replica in the selected machine type). + - This property is immutable, to change it, you must delete and recreate the resource. type: int min_replica_count: description: - The minimum number of machine replicas that will be always deployed on. - This value must be greater than or equal to 1. - If traffic increases, it may dynamically be deployed onto more replicas, and as traffic decreases, some of these extra replicas may be freed. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: int required_replica_count: @@ -190,20 +196,24 @@ private_service_connect_config: description: - The configuration for Private Service Connect (PSC). + - This property is immutable, to change it, you must delete and recreate the resource. suboptions: enable_private_service_connect: description: - If true, expose the IndexEndpoint via private service connect. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: bool project_allowlist: description: - A list of Projects from which the forwarding rule will target the service attachment. + - This property is immutable, to change it, you must delete and recreate the resource. elements: str type: list psc_automation_configs: description: - PSC config that is used to automatically create PSC endpoints in the user projects. + - This property is immutable, to change it, you must delete and recreate the resource. suboptions: error_message: description: @@ -224,11 +234,13 @@ description: - The full name of the Google Compute Engine network. - 'Format: projects/{project}/global/networks/{network}.' + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str project_id: description: - Project id used to create forwarding rule. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str state: @@ -258,6 +270,7 @@ description: - Resource ID segment making up resource `location`. - It identifies the resource within its parent collection as described in https://google.aip.dev/122. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str model_config: @@ -287,6 +300,7 @@ - In order for environment variables to be expanded, reference them by using the following syntax:$(VARIABLE_NAME) Note that this differs from Bash variable expansion, which does not use parentheses. - If a variable cannot be resolved, the reference in the input string is used unchanged. - To avoid variable expansion, you can escape this syntax with `$$`; for example:$$(VARIABLE_NAME) This field corresponds to the `args` field of the Kubernetes Containers [v1 core API](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#container-v1-core). + - This property is immutable, to change it, you must delete and recreate the resource. elements: str type: list command: @@ -304,12 +318,14 @@ - In order for environment variables to be expanded, reference them by using the following syntax:$(VARIABLE_NAME) Note that this differs from Bash variable expansion, which does not use parentheses. - If a variable cannot be resolved, the reference in the input string is used unchanged. - To avoid variable expansion, you can escape this syntax with `$$`; for example:$$(VARIABLE_NAME) This field corresponds to the `command` field of the Kubernetes Containers [v1 core API](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#container-v1-core). + - This property is immutable, to change it, you must delete and recreate the resource. elements: str type: list deployment_timeout: description: - Deployment timeout. - Limit for deployment timeout is 2 hours. + - This property is immutable, to change it, you must delete and recreate the resource. type: str env: description: @@ -319,6 +335,7 @@ - Later entries in this list can also reference earlier entries. - 'For example, the following example sets the variable `VAR_2` to have the value `foo bar`: ```json [ { "name": "VAR_1", "value": "foo" }, { "name": "VAR_2", "value": "$(VAR_1) bar" } ] ``` If you switch the order of the variables in the example, then the expansion does not occur.' - This field corresponds to the `env` field of the Kubernetes Containers [v1 core API](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#container-v1-core). + - This property is immutable, to change it, you must delete and recreate the resource. elements: dict suboptions: name: @@ -344,6 +361,7 @@ - If you do not specify this field, gRPC requests to the container will be disabled. - Vertex AI does not use ports other than the first one listed. - This field corresponds to the `ports` field of the Kubernetes Containers v1 core API. + - This property is immutable, to change it, you must delete and recreate the resource. elements: dict suboptions: container_port: @@ -487,6 +505,7 @@ - 'If you don''t specify this field, it defaults to the following value when you deploy this Model to an Endpoint:/v1/endpoints/ENDPOINT/deployedModels/DEPLOYED_MODEL:predict The placeholders in this value are replaced as follows: * ENDPOINT: The last segment (following `endpoints/`)of the Endpoint.name][] field of the Endpoint where this Model has been deployed.' - '(Vertex AI makes this value available to your container code as the [`AIP_ENDPOINT_ID` environment variable](https://cloud.google.com/vertex-ai/docs/predictions/custom-container-requirements#aip-variables).) * DEPLOYED_MODEL: DeployedModel.id of the `DeployedModel`.' - (Vertex AI makes this value available to your container code as the [`AIP_DEPLOYED_MODEL_ID` environment variable](https://cloud.google.com/vertex-ai/docs/predictions/custom-container-requirements#aip-variables).). + - This property is immutable, to change it, you must delete and recreate the resource. type: str image_uri: description: @@ -496,6 +515,7 @@ - The container image is ingested upon ModelService.UploadModel, stored internally, and this original path is afterwards not used. - To learn about the requirements for the Docker image itself, see [Custom container requirements](https://cloud.google.com/vertex-ai/docs/predictions/custom-container-requirements#). - You can use the URI to one of Vertex AI's [pre-built container images for prediction](https://cloud.google.com/vertex-ai/docs/predictions/pre-built-containers) in this field. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str liveness_probe: @@ -631,6 +651,7 @@ - Vertex AI also sends [liveness and health checks](https://cloud.google.com/vertex-ai/docs/predictions/custom-container-requirements#liveness) to this port. - 'If you do not specify this field, it defaults to following value: ```json [ { "containerPort": 8080 } ] ``` Vertex AI does not use ports other than the first one listed.' - This field corresponds to the `ports` field of the Kubernetes Containers [v1 core API](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#container-v1-core). + - This property is immutable, to change it, you must delete and recreate the resource. elements: dict suboptions: container_port: @@ -648,10 +669,12 @@ - 'If you don''t specify this field, it defaults to the following value when you deploy this Model to an Endpoint:/v1/endpoints/ENDPOINT/deployedModels/DEPLOYED_MODEL:predict The placeholders in this value are replaced as follows: * ENDPOINT: The last segment (following `endpoints/`)of the Endpoint.name][] field of the Endpoint where this Model has been deployed.' - '(Vertex AI makes this value available to your container code as the [`AIP_ENDPOINT_ID` environment variable](https://cloud.google.com/vertex-ai/docs/predictions/custom-container-requirements#aip-variables).) * DEPLOYED_MODEL: DeployedModel.id of the `DeployedModel`.' - (Vertex AI makes this value available to your container code as the [`AIP_DEPLOYED_MODEL_ID` environment variable](https://cloud.google.com/vertex-ai/docs/predictions/custom-container-requirements#aip-variables).). + - This property is immutable, to change it, you must delete and recreate the resource. type: str shared_memory_size_mb: description: - The amount of the VM memory to reserve as the shared memory for the model in megabytes. + - This property is immutable, to change it, you must delete and recreate the resource. type: str startup_probe: description: @@ -895,51 +918,34 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports -import copy import json import re # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class DeployConfig(gcp.Resource): +class DeployConfig(gcp_v2.Resource): def _request(self): return { - "dedicatedResources": gcp.remove_empties( + "dedicatedResources": gcp_v2.remove_empties( DeployConfigDedicatedResources(self.request.get("dedicated_resources", {})).to_request() ), # remove empty values "fastTryoutEnabled": self.request.get("fast_tryout_enabled"), "systemLabels": self.request.get("system_labels"), } - def _response(self): - return { - "dedicatedResources": DeployConfigDedicatedResources().from_response( - self.response.get("dedicatedResources", {}) - ), - "fastTryoutEnabled": self.response.get("fastTryoutEnabled"), - "systemLabels": self.response.get("systemLabels"), - } - -class DeployConfigDedicatedResources(gcp.Resource): +class DeployConfigDedicatedResources(gcp_v2.Resource): def _request(self): return { "autoscalingMetricSpecs": [ DeployConfigDedicatedResourcesAutoscalingMetricSpec(item).to_request() for item in (self.request.get("autoscaling_metric_specs") or []) ], - "machineSpec": gcp.remove_empties( + "machineSpec": gcp_v2.remove_empties( DeployConfigDedicatedResourcesMachineSpec(self.request.get("machine_spec", {})).to_request() ), # remove empty values "maxReplicaCount": self.request.get("max_replica_count"), @@ -948,44 +954,23 @@ def _request(self): "spot": self.request.get("spot"), } - def _response(self): - return { - "autoscalingMetricSpecs": [ - DeployConfigDedicatedResourcesAutoscalingMetricSpec().from_response(item) - for item in (self.response.get("autoscalingMetricSpecs") or []) - ], - "machineSpec": DeployConfigDedicatedResourcesMachineSpec().from_response( - self.response.get("machineSpec", {}) - ), - "maxReplicaCount": self.response.get("maxReplicaCount"), - "minReplicaCount": self.response.get("minReplicaCount"), - "requiredReplicaCount": self.response.get("requiredReplicaCount"), - "spot": self.response.get("spot"), - } - -class DeployConfigDedicatedResourcesAutoscalingMetricSpec(gcp.Resource): +class DeployConfigDedicatedResourcesAutoscalingMetricSpec(gcp_v2.Resource): def _request(self): return { "metricName": self.request.get("metric_name"), "target": self.request.get("target"), } - def _response(self): - return { - "metricName": self.response.get("metricName"), - "target": self.response.get("target"), - } - -class DeployConfigDedicatedResourcesMachineSpec(gcp.Resource): +class DeployConfigDedicatedResourcesMachineSpec(gcp_v2.Resource): def _request(self): return { "acceleratorCount": self.request.get("accelerator_count"), "acceleratorType": self.request.get("accelerator_type"), "machineType": self.request.get("machine_type"), "multihostGpuNodeCount": self.request.get("multihost_gpu_node_count"), - "reservationAffinity": gcp.remove_empties( + "reservationAffinity": gcp_v2.remove_empties( DeployConfigDedicatedResourcesMachineSpecReservationAffinity( self.request.get("reservation_affinity", {}) ).to_request() @@ -993,20 +978,8 @@ def _request(self): "tpuTopology": self.request.get("tpu_topology"), } - def _response(self): - return { - "acceleratorCount": self.response.get("acceleratorCount"), - "acceleratorType": self.response.get("acceleratorType"), - "machineType": self.response.get("machineType"), - "multihostGpuNodeCount": self.response.get("multihostGpuNodeCount"), - "reservationAffinity": DeployConfigDedicatedResourcesMachineSpecReservationAffinity().from_response( - self.response.get("reservationAffinity", {}) - ), - "tpuTopology": self.response.get("tpuTopology"), - } - -class DeployConfigDedicatedResourcesMachineSpecReservationAffinity(gcp.Resource): +class DeployConfigDedicatedResourcesMachineSpecReservationAffinity(gcp_v2.Resource): def _request(self): return { "key": self.request.get("key"), @@ -1014,42 +987,26 @@ def _request(self): "values": self.request.get("values"), } - def _response(self): - return { - "key": self.response.get("key"), - "reservationAffinityType": self.response.get("reservationAffinityType"), - "values": self.response.get("values"), - } - -class EndpointConfig(gcp.Resource): +class EndpointConfig(gcp_v2.Resource): def _request(self): return { "dedicatedEndpointEnabled": self.request.get("dedicated_endpoint_enabled"), "endpointDisplayName": self.request.get("endpoint_display_name"), - "privateServiceConnectConfig": gcp.remove_empties( + "privateServiceConnectConfig": gcp_v2.remove_empties( EndpointConfigPrivateServiceConnectConfig( self.request.get("private_service_connect_config", {}) ).to_request() ), # remove empty values } - def _response(self): - return { - "dedicatedEndpointEnabled": self.response.get("dedicatedEndpointEnabled"), - "endpointDisplayName": self.response.get("endpointDisplayName"), - "privateServiceConnectConfig": EndpointConfigPrivateServiceConnectConfig().from_response( - self.response.get("privateServiceConnectConfig", {}) - ), - } - -class EndpointConfigPrivateServiceConnectConfig(gcp.Resource): +class EndpointConfigPrivateServiceConnectConfig(gcp_v2.Resource): def _request(self): return { "enablePrivateServiceConnect": self.request.get("enable_private_service_connect"), "projectAllowlist": self.request.get("project_allowlist"), - "pscAutomationConfigs": gcp.remove_empties( + "pscAutomationConfigs": gcp_v2.remove_empties( EndpointConfigPrivateServiceConnectConfigPscAutomationConfigs( self.request.get("psc_automation_configs", {}) ).to_request() @@ -1058,16 +1015,11 @@ def _request(self): def _response(self): return { - "enablePrivateServiceConnect": self.response.get("enablePrivateServiceConnect"), - "projectAllowlist": self.response.get("projectAllowlist"), - "pscAutomationConfigs": EndpointConfigPrivateServiceConnectConfigPscAutomationConfigs().from_response( - self.response.get("pscAutomationConfigs", {}) - ), "serviceAttachment": self.response.get("serviceAttachment"), } -class EndpointConfigPrivateServiceConnectConfigPscAutomationConfigs(gcp.Resource): +class EndpointConfigPrivateServiceConnectConfigPscAutomationConfigs(gcp_v2.Resource): def _request(self): return { "network": self.request.get("network"), @@ -1079,17 +1031,15 @@ def _response(self): "errorMessage": self.response.get("errorMessage"), "forwardingRule": self.response.get("forwardingRule"), "ipAddress": self.response.get("ipAddress"), - "network": self.response.get("network"), - "projectId": self.response.get("projectId"), "state": self.response.get("state"), } -class ModelConfig(gcp.Resource): +class ModelConfig(gcp_v2.Resource): def _request(self): return { "acceptEula": self.request.get("accept_eula"), - "containerSpec": gcp.remove_empties( + "containerSpec": gcp_v2.remove_empties( ModelConfigContainerSpec(self.request.get("container_spec", {})).to_request() ), # remove empty values "huggingFaceAccessToken": self.request.get("hugging_face_access_token"), @@ -1097,17 +1047,8 @@ def _request(self): "modelDisplayName": self.request.get("model_display_name"), } - def _response(self): - return { - "acceptEula": self.response.get("acceptEula"), - "containerSpec": ModelConfigContainerSpec().from_response(self.response.get("containerSpec", {})), - "huggingFaceAccessToken": self.response.get("huggingFaceAccessToken"), - "huggingFaceCacheEnabled": self.response.get("huggingFaceCacheEnabled"), - "modelDisplayName": self.response.get("modelDisplayName"), - } - -class ModelConfigContainerSpec(gcp.Resource): +class ModelConfigContainerSpec(gcp_v2.Resource): def _request(self): return { "args": self.request.get("args"), @@ -1117,138 +1058,77 @@ def _request(self): "grpcPorts": [ ModelConfigContainerSpecGrpcPort(item).to_request() for item in (self.request.get("grpc_ports") or []) ], - "healthProbe": gcp.remove_empties( + "healthProbe": gcp_v2.remove_empties( ModelConfigContainerSpecHealthProbe(self.request.get("health_probe", {})).to_request() ), # remove empty values "healthRoute": self.request.get("health_route"), "imageUri": self.request.get("image_uri"), - "livenessProbe": gcp.remove_empties( + "livenessProbe": gcp_v2.remove_empties( ModelConfigContainerSpecLivenessProbe(self.request.get("liveness_probe", {})).to_request() ), # remove empty values "ports": [ModelConfigContainerSpecPort(item).to_request() for item in (self.request.get("ports") or [])], "predictRoute": self.request.get("predict_route"), "sharedMemorySizeMb": self.request.get("shared_memory_size_mb"), - "startupProbe": gcp.remove_empties( + "startupProbe": gcp_v2.remove_empties( ModelConfigContainerSpecStartupProbe(self.request.get("startup_probe", {})).to_request() ), # remove empty values } - def _response(self): - return { - "args": self.response.get("args"), - "command": self.response.get("command"), - "deploymentTimeout": self.response.get("deploymentTimeout"), - "env": [ModelConfigContainerSpecEnv().from_response(item) for item in (self.response.get("env") or [])], - "grpcPorts": [ - ModelConfigContainerSpecGrpcPort().from_response(item) - for item in (self.response.get("grpcPorts") or []) - ], - "healthProbe": ModelConfigContainerSpecHealthProbe().from_response(self.response.get("healthProbe", {})), - "healthRoute": self.response.get("healthRoute"), - "imageUri": self.response.get("imageUri"), - "livenessProbe": ModelConfigContainerSpecLivenessProbe().from_response( - self.response.get("livenessProbe", {}) - ), - "ports": [ - ModelConfigContainerSpecPort().from_response(item) for item in (self.response.get("ports") or []) - ], - "predictRoute": self.response.get("predictRoute"), - "sharedMemorySizeMb": self.response.get("sharedMemorySizeMb"), - "startupProbe": ModelConfigContainerSpecStartupProbe().from_response(self.response.get("startupProbe", {})), - } - -class ModelConfigContainerSpecEnv(gcp.Resource): +class ModelConfigContainerSpecEnv(gcp_v2.Resource): def _request(self): return { "name": self.request.get("name"), "value": self.request.get("value"), } - def _response(self): - return { - "name": self.response.get("name"), - "value": self.response.get("value"), - } - -class ModelConfigContainerSpecGrpcPort(gcp.Resource): +class ModelConfigContainerSpecGrpcPort(gcp_v2.Resource): def _request(self): return { "containerPort": self.request.get("container_port"), } - def _response(self): - return { - "containerPort": self.response.get("containerPort"), - } - -class ModelConfigContainerSpecHealthProbe(gcp.Resource): +class ModelConfigContainerSpecHealthProbe(gcp_v2.Resource): def _request(self): return { - "exec": gcp.remove_empties( + "exec": gcp_v2.remove_empties( ModelConfigContainerSpecHealthProbeExec(self.request.get("exec", {})).to_request() ), # remove empty values "failureThreshold": self.request.get("failure_threshold"), - "grpc": gcp.remove_empties( + "grpc": gcp_v2.remove_empties( ModelConfigContainerSpecHealthProbeGrpc(self.request.get("grpc", {})).to_request() ), # remove empty values - "httpGet": gcp.remove_empties( + "httpGet": gcp_v2.remove_empties( ModelConfigContainerSpecHealthProbeHttpGet(self.request.get("http_get", {})).to_request() ), # remove empty values "initialDelaySeconds": self.request.get("initial_delay_seconds"), "periodSeconds": self.request.get("period_seconds"), "successThreshold": self.request.get("success_threshold"), - "tcpSocket": gcp.remove_empties( + "tcpSocket": gcp_v2.remove_empties( ModelConfigContainerSpecHealthProbeTcpSocket(self.request.get("tcp_socket", {})).to_request() ), # remove empty values "timeoutSeconds": self.request.get("timeout_seconds"), } - def _response(self): - return { - "exec": ModelConfigContainerSpecHealthProbeExec().from_response(self.response.get("exec", {})), - "failureThreshold": self.response.get("failureThreshold"), - "grpc": ModelConfigContainerSpecHealthProbeGrpc().from_response(self.response.get("grpc", {})), - "httpGet": ModelConfigContainerSpecHealthProbeHttpGet().from_response(self.response.get("httpGet", {})), - "initialDelaySeconds": self.response.get("initialDelaySeconds"), - "periodSeconds": self.response.get("periodSeconds"), - "successThreshold": self.response.get("successThreshold"), - "tcpSocket": ModelConfigContainerSpecHealthProbeTcpSocket().from_response( - self.response.get("tcpSocket", {}) - ), - "timeoutSeconds": self.response.get("timeoutSeconds"), - } - -class ModelConfigContainerSpecHealthProbeExec(gcp.Resource): +class ModelConfigContainerSpecHealthProbeExec(gcp_v2.Resource): def _request(self): return { "command": self.request.get("command"), } - def _response(self): - return { - "command": self.response.get("command"), - } - -class ModelConfigContainerSpecHealthProbeGrpc(gcp.Resource): +class ModelConfigContainerSpecHealthProbeGrpc(gcp_v2.Resource): def _request(self): return { "port": self.request.get("port"), "service": self.request.get("service"), } - def _response(self): - return { - "port": self.response.get("port"), - "service": self.response.get("service"), - } - -class ModelConfigContainerSpecHealthProbeHttpGet(gcp.Resource): +class ModelConfigContainerSpecHealthProbeHttpGet(gcp_v2.Resource): def _request(self): return { "host": self.request.get("host"), @@ -1261,112 +1141,62 @@ def _request(self): "scheme": self.request.get("scheme"), } - def _response(self): - return { - "host": self.response.get("host"), - "httpHeaders": [ - ModelConfigContainerSpecHealthProbeHttpGetHttpHeader().from_response(item) - for item in (self.response.get("httpHeaders") or []) - ], - "path": self.response.get("path"), - "port": self.response.get("port"), - "scheme": self.response.get("scheme"), - } - -class ModelConfigContainerSpecHealthProbeHttpGetHttpHeader(gcp.Resource): +class ModelConfigContainerSpecHealthProbeHttpGetHttpHeader(gcp_v2.Resource): def _request(self): return { "name": self.request.get("name"), "value": self.request.get("value"), } - def _response(self): - return { - "name": self.response.get("name"), - "value": self.response.get("value"), - } - -class ModelConfigContainerSpecHealthProbeTcpSocket(gcp.Resource): +class ModelConfigContainerSpecHealthProbeTcpSocket(gcp_v2.Resource): def _request(self): return { "host": self.request.get("host"), "port": self.request.get("port"), } - def _response(self): - return { - "host": self.response.get("host"), - "port": self.response.get("port"), - } - -class ModelConfigContainerSpecLivenessProbe(gcp.Resource): +class ModelConfigContainerSpecLivenessProbe(gcp_v2.Resource): def _request(self): return { - "exec": gcp.remove_empties( + "exec": gcp_v2.remove_empties( ModelConfigContainerSpecLivenessProbeExec(self.request.get("exec", {})).to_request() ), # remove empty values "failureThreshold": self.request.get("failure_threshold"), - "grpc": gcp.remove_empties( + "grpc": gcp_v2.remove_empties( ModelConfigContainerSpecLivenessProbeGrpc(self.request.get("grpc", {})).to_request() ), # remove empty values - "httpGet": gcp.remove_empties( + "httpGet": gcp_v2.remove_empties( ModelConfigContainerSpecLivenessProbeHttpGet(self.request.get("http_get", {})).to_request() ), # remove empty values "initialDelaySeconds": self.request.get("initial_delay_seconds"), "periodSeconds": self.request.get("period_seconds"), "successThreshold": self.request.get("success_threshold"), - "tcpSocket": gcp.remove_empties( + "tcpSocket": gcp_v2.remove_empties( ModelConfigContainerSpecLivenessProbeTcpSocket(self.request.get("tcp_socket", {})).to_request() ), # remove empty values "timeoutSeconds": self.request.get("timeout_seconds"), } - def _response(self): - return { - "exec": ModelConfigContainerSpecLivenessProbeExec().from_response(self.response.get("exec", {})), - "failureThreshold": self.response.get("failureThreshold"), - "grpc": ModelConfigContainerSpecLivenessProbeGrpc().from_response(self.response.get("grpc", {})), - "httpGet": ModelConfigContainerSpecLivenessProbeHttpGet().from_response(self.response.get("httpGet", {})), - "initialDelaySeconds": self.response.get("initialDelaySeconds"), - "periodSeconds": self.response.get("periodSeconds"), - "successThreshold": self.response.get("successThreshold"), - "tcpSocket": ModelConfigContainerSpecLivenessProbeTcpSocket().from_response( - self.response.get("tcpSocket", {}) - ), - "timeoutSeconds": self.response.get("timeoutSeconds"), - } - -class ModelConfigContainerSpecLivenessProbeExec(gcp.Resource): +class ModelConfigContainerSpecLivenessProbeExec(gcp_v2.Resource): def _request(self): return { "command": self.request.get("command"), } - def _response(self): - return { - "command": self.response.get("command"), - } - -class ModelConfigContainerSpecLivenessProbeGrpc(gcp.Resource): +class ModelConfigContainerSpecLivenessProbeGrpc(gcp_v2.Resource): def _request(self): return { "port": self.request.get("port"), "service": self.request.get("service"), } - def _response(self): - return { - "port": self.response.get("port"), - "service": self.response.get("service"), - } - -class ModelConfigContainerSpecLivenessProbeHttpGet(gcp.Resource): +class ModelConfigContainerSpecLivenessProbeHttpGet(gcp_v2.Resource): def _request(self): return { "host": self.request.get("host"), @@ -1379,124 +1209,69 @@ def _request(self): "scheme": self.request.get("scheme"), } - def _response(self): - return { - "host": self.response.get("host"), - "httpHeaders": [ - ModelConfigContainerSpecLivenessProbeHttpGetHttpHeader().from_response(item) - for item in (self.response.get("httpHeaders") or []) - ], - "path": self.response.get("path"), - "port": self.response.get("port"), - "scheme": self.response.get("scheme"), - } - -class ModelConfigContainerSpecLivenessProbeHttpGetHttpHeader(gcp.Resource): +class ModelConfigContainerSpecLivenessProbeHttpGetHttpHeader(gcp_v2.Resource): def _request(self): return { "name": self.request.get("name"), "value": self.request.get("value"), } - def _response(self): - return { - "name": self.response.get("name"), - "value": self.response.get("value"), - } - -class ModelConfigContainerSpecLivenessProbeTcpSocket(gcp.Resource): +class ModelConfigContainerSpecLivenessProbeTcpSocket(gcp_v2.Resource): def _request(self): return { "host": self.request.get("host"), "port": self.request.get("port"), } - def _response(self): - return { - "host": self.response.get("host"), - "port": self.response.get("port"), - } - -class ModelConfigContainerSpecPort(gcp.Resource): +class ModelConfigContainerSpecPort(gcp_v2.Resource): def _request(self): return { "containerPort": self.request.get("container_port"), } - def _response(self): - return { - "containerPort": self.response.get("containerPort"), - } - -class ModelConfigContainerSpecStartupProbe(gcp.Resource): +class ModelConfigContainerSpecStartupProbe(gcp_v2.Resource): def _request(self): return { - "exec": gcp.remove_empties( + "exec": gcp_v2.remove_empties( ModelConfigContainerSpecStartupProbeExec(self.request.get("exec", {})).to_request() ), # remove empty values "failureThreshold": self.request.get("failure_threshold"), - "grpc": gcp.remove_empties( + "grpc": gcp_v2.remove_empties( ModelConfigContainerSpecStartupProbeGrpc(self.request.get("grpc", {})).to_request() ), # remove empty values - "httpGet": gcp.remove_empties( + "httpGet": gcp_v2.remove_empties( ModelConfigContainerSpecStartupProbeHttpGet(self.request.get("http_get", {})).to_request() ), # remove empty values "initialDelaySeconds": self.request.get("initial_delay_seconds"), "periodSeconds": self.request.get("period_seconds"), "successThreshold": self.request.get("success_threshold"), - "tcpSocket": gcp.remove_empties( + "tcpSocket": gcp_v2.remove_empties( ModelConfigContainerSpecStartupProbeTcpSocket(self.request.get("tcp_socket", {})).to_request() ), # remove empty values "timeoutSeconds": self.request.get("timeout_seconds"), } - def _response(self): - return { - "exec": ModelConfigContainerSpecStartupProbeExec().from_response(self.response.get("exec", {})), - "failureThreshold": self.response.get("failureThreshold"), - "grpc": ModelConfigContainerSpecStartupProbeGrpc().from_response(self.response.get("grpc", {})), - "httpGet": ModelConfigContainerSpecStartupProbeHttpGet().from_response(self.response.get("httpGet", {})), - "initialDelaySeconds": self.response.get("initialDelaySeconds"), - "periodSeconds": self.response.get("periodSeconds"), - "successThreshold": self.response.get("successThreshold"), - "tcpSocket": ModelConfigContainerSpecStartupProbeTcpSocket().from_response( - self.response.get("tcpSocket", {}) - ), - "timeoutSeconds": self.response.get("timeoutSeconds"), - } - -class ModelConfigContainerSpecStartupProbeExec(gcp.Resource): +class ModelConfigContainerSpecStartupProbeExec(gcp_v2.Resource): def _request(self): return { "command": self.request.get("command"), } - def _response(self): - return { - "command": self.response.get("command"), - } - -class ModelConfigContainerSpecStartupProbeGrpc(gcp.Resource): +class ModelConfigContainerSpecStartupProbeGrpc(gcp_v2.Resource): def _request(self): return { "port": self.request.get("port"), "service": self.request.get("service"), } - def _response(self): - return { - "port": self.response.get("port"), - "service": self.response.get("service"), - } - -class ModelConfigContainerSpecStartupProbeHttpGet(gcp.Resource): +class ModelConfigContainerSpecStartupProbeHttpGet(gcp_v2.Resource): def _request(self): return { "host": self.request.get("host"), @@ -1509,58 +1284,34 @@ def _request(self): "scheme": self.request.get("scheme"), } - def _response(self): - return { - "host": self.response.get("host"), - "httpHeaders": [ - ModelConfigContainerSpecStartupProbeHttpGetHttpHeader().from_response(item) - for item in (self.response.get("httpHeaders") or []) - ], - "path": self.response.get("path"), - "port": self.response.get("port"), - "scheme": self.response.get("scheme"), - } - -class ModelConfigContainerSpecStartupProbeHttpGetHttpHeader(gcp.Resource): +class ModelConfigContainerSpecStartupProbeHttpGetHttpHeader(gcp_v2.Resource): def _request(self): return { "name": self.request.get("name"), "value": self.request.get("value"), } - def _response(self): - return { - "name": self.response.get("name"), - "value": self.response.get("value"), - } - -class ModelConfigContainerSpecStartupProbeTcpSocket(gcp.Resource): +class ModelConfigContainerSpecStartupProbeTcpSocket(gcp_v2.Resource): def _request(self): return { "host": self.request.get("host"), "port": self.request.get("port"), } - def _response(self): - return { - "host": self.response.get("host"), - "port": self.response.get("port"), - } - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { - "deployConfig": gcp.remove_empties( + "deployConfig": gcp_v2.remove_empties( DeployConfig(self.request.get("deploy_config", {})).to_request() ), # remove empty values - "endpointConfig": gcp.remove_empties( + "endpointConfig": gcp_v2.remove_empties( EndpointConfig(self.request.get("endpoint_config", {})).to_request() ), # remove empty values "huggingFaceModelId": self.request.get("hugging_face_model_id"), - "modelConfig": gcp.remove_empties( + "modelConfig": gcp_v2.remove_empties( ModelConfig(self.request.get("model_config", {})).to_request() ), # remove empty values "publisherModelName": self.request.get("publisher_model_name"), @@ -1568,13 +1319,9 @@ def _request(self): def _response(self): return { - "deployConfig": DeployConfig().from_response(self.response.get("deployConfig", {})), "deployedModelDisplayName": self.response.get("deployedModelDisplayName"), "deployedModelId": self.response.get("deployedModelId"), - "endpointConfig": EndpointConfig().from_response(self.response.get("endpointConfig", {})), - "huggingFaceModelId": self.response.get("huggingFaceModelId"), - "modelConfig": ModelConfig().from_response(self.response.get("modelConfig", {})), - "publisherModelName": self.response.get("publisherModelName"), + "endpoint": self.response.get("endpoint"), } @@ -1583,26 +1330,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( state=dict( type="str", @@ -2079,9 +1810,7 @@ def main(): publisher_model_name=dict( type="str", ), - ), - mutually_exclusive=[["hugging_face_model_id", "publisher_model_name"]], - required_one_of=[["hugging_face_model_id", "publisher_model_name"]], + ) ) if not module.params["scopes"]: @@ -2089,17 +1818,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{location}:deploy", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{location}:deploy", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}:deploy", "async_uri": "{op_id}", @@ -2107,7 +1830,7 @@ def main(): "timeout_minutes": 180, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/endpoints/{endpoint}", "async_uri": "", @@ -2115,7 +1838,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/endpoints/{endpoint}", "async_uri": "", @@ -2123,7 +1846,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{location}/endpoints/{endpoint}", "async_uri": "", @@ -2131,33 +1854,42 @@ def main(): "timeout_minutes": 0, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#endpointWithModelGardenDeployment") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI( + request, + module=module, + product="VertexAI", + kind="vertexai#endpointWithModelGardenDeployment", + op_configs=op_configs, + ) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None - # --------- BEGIN pre-read custom code --------- - # region and location are the same - params["region"] = params["location"] + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties - # need to strip the last part of the read_uri to hit the list endpoint with a filter - read_uri = "/".join(read_uri.split("/")[:-1]) + read_link: str = "" # give it a chance for pre-read to overload - display_name = params["display_name"] - endpoint_display_name = params.get("endpoint_config", {}).get("endpoint_display_name") or display_name - model_display_name = params.get("model_config", {}).get("model_display_name") or display_name + # --------- BEGIN pre-read custom code --------- + # region and location are the same, but region is used in the URL + resource.url_params["region"] = request.get("location") + + display_name: str = request.get("display_name") + endpoint_display_name: str = display_name + if request.get("endpoint_config", {}).get("endpoint_display_name"): + endpoint_display_name = request.get("endpoint_config")["endpoint_display_name"] + model_display_name: str = display_name + if request.get("model_config", {}).get("model_display_name"): + model_display_name = request.get("model_config")["model_display_name"] # filter by the endpoint display name - read_uri += f"?filter=displayName={endpoint_display_name}" + read_link = resource.build_link("list") + "?filter=displayName=" + endpoint_display_name def publisher_model_to_deployed_model(region, path: str) -> str: pattern = r"^publishers/(?P[^/]+)/models/(?P[^@/]+)(?:@(?P.+))?$" @@ -2175,20 +1907,17 @@ def publisher_model_to_deployed_model(region, path: str) -> str: # --------- END pre-read custom code --------- - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) # --------- BEGIN post-read custom code --------- - outgoing = resource.to_request() - # if there are existing endpoints, the call would have returned a list - if not gcp.empty(existing_obj): + if not gcp_v2.empty(existing_obj): for endpoint in existing_obj.get("endpoints", []): - if ( - endpoint.get("displayName") == endpoint_display_name - ): # filtering should take care of this, but just in case + if endpoint.get("displayName") == endpoint_display_name: existing_obj = endpoint existing_obj["endpointDisplayName"] = endpoint.pop("displayName") existing_obj["endpoint"] = endpoint.get("name") @@ -2208,91 +1937,86 @@ def publisher_model_to_deployed_model(region, path: str) -> str: if existing_obj["deployedModel"]: # finally, update the params to build the url - params["endpoint"] = existing_obj["name"] + resource.url_params["endpoint"] = existing_obj["name"] - gcp.debug( + gcp_v2.debug( module, - publisher_model_name=params.get("publisher_model_name"), - hugging_face_model_id=params.get("hugging_face_model_id"), + publisher_model_name=request.get("publisher_model_name"), + hugging_face_model_id=request.get("hugging_face_model_id"), ) - model_to_deploy = params.get("publisher_model_name") # read publisher model name first + model_to_deploy = request.get("publisher_model_name") # read publisher model name first if model_to_deploy is None: # if not provided, use the hugging face model id - model_to_deploy = params.get("hugging_face_model_id") + model_to_deploy = request.get("hugging_face_model_id") hfParts = model_to_deploy.split("/") model_to_deploy = ( f"publishers/hf-{hfParts[0]}/models/{hfParts[1]}@001" # put HF model into publisher model format ) model_to_deploy = publisher_model_to_deployed_model( - params["region"], model_to_deploy + request.get("region"), model_to_deploy ) # convert the model id to the deployed model id existing_model = existing_obj["deployedModel"].get("model", "") # requested model's final name is a substring of the actual deployed model - gcp.debug(module, model_to_deploy=model_to_deploy, existing_model=existing_model) + gcp_v2.debug(module, model_to_deploy=model_to_deploy, existing_model=existing_model) if model_to_deploy in existing_model: # our outgoing model is a substring the existing model custom_diff = False # deploy model is the same, mark as such else: custom_diff = True # deploy model changed, mark as such else: pass - # module.fail_json(msg="The endpoint exists but the deployed model does not, delete the endpoint and try again") # --------- END post-read custom code --------- if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- + create_link: str = "" # give it a chance for pre-create to overload # --------- BEGIN pre-create custom code --------- - if not params.get("endpoint_config"): - params["endpoint_config"] = {} - if not params["endpoint_config"].get("endpoint_display_name"): - params["endpoint_config"]["endpoint_display_name"] = params["display_name"] + if not request.get("endpoint_config"): + resource.request["endpoint_config"] = {} + if not request.get("endpoint_config", {}).get("endpoint_display_name"): + resource.request["endpoint_config"]["endpoint_display_name"] = request.get("display_name") - if not params.get("model_config"): - params["model_config"] = {} - if not params["model_config"].get("model_display_name"): - params["model_config"]["model_display_name"] = params["display_name"] + if not request.get("model_config"): + resource.request["model_config"] = {} + if not request.get("model_config", {}).get("model_display_name"): + resource.request["model_config"]["model_display_name"] = request.get("display_name") # --------- END pre-create custom code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) # --------- BEGIN post-create custom code --------- new_obj["name"] = new_obj.pop("endpoint") # --------- END post-create custom code --------- - gcp.debug(module, new=new_obj, action="create", post=True) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -2302,22 +2026,19 @@ def publisher_model_to_deployed_model(region, path: str) -> str: pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload # --------- BEGIN pre-delete custom code --------- - params = copy.deepcopy(module.params) - params["region"] = params["location"] - params["endpoint"] = existing_obj["name"] + resource.url_params["endpoint"] = existing_obj["name"] # First, undeploy the model (if any) deployed_model_id = existing_obj.get("deployedModelId") - # module.exit_json(msg="Deployed model ID: " + deployed_model_id) if deployed_model_id: - undeploy_url = build_link(params, f"{op_configs.delete.uri}:undeployModel") + undeploy_url = resource.build_link("delete") + ":undeployModel" undeploy_body = {"deployedModelId": deployed_model_id} - gcp.debug(module, undeploy_url=undeploy_url, undeploy_body=undeploy_body) + gcp_v2.debug(module, undeploy_url=undeploy_url, undeploy_body=undeploy_body) # perform an async post to undeploy undeploy_response = resource.session().post(undeploy_url, undeploy_body) try: @@ -2325,28 +2046,32 @@ def publisher_model_to_deployed_model(region, path: str) -> str: undeploy_async_result = undeploy_response.json() except getattr(json.decoder, "JSONDecodeError", ValueError): module.fail_json(msg=f"Invalid JSON response with error: {undeploy_response.text}") - params["op_id"] = undeploy_async_result.get("name") - async_undeploy_op_link = build_link(params, "{op_id}") - gcp.debug(module, params=params, async_undeploy_op_link=async_undeploy_op_link) + resource.url_params["op_id"] = undeploy_async_result.get("name") + async_undeploy_op_link = (resource.build_link("async") + "{op_id}").format(**resource.url_params) + gcp_v2.debug(module, url_params=resource.url_params, async_undeploy_op_link=async_undeploy_op_link) resource.wait_for_op(async_undeploy_op_link, retries=op_configs.delete.timeout) + # --------- END pre-delete custom code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -2354,33 +2079,33 @@ def publisher_model_to_deployed_model(region, path: str) -> str: changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload # --------- BEGIN pre-update custom code --------- module.fail_json(msg="Updating an endpoint with model garden deployment is not supported") # --------- END pre-update custom code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -2390,6 +2115,7 @@ def publisher_model_to_deployed_model(region, path: str) -> str: new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_feature_group.py b/plugins/modules/gcp_vertexai_feature_group.py index 9bba8fb2a..1097c2881 100644 --- a/plugins/modules/gcp_vertexai_feature_group.py +++ b/plugins/modules/gcp_vertexai_feature_group.py @@ -50,6 +50,7 @@ big_query_source: description: - The BigQuery source URI that points to either a BigQuery Table or View. + - This property is immutable, to change it, you must delete and recreate the resource. required: true suboptions: input_uri: @@ -73,6 +74,7 @@ labels: description: - The labels with user-defined metadata to organize your FeatureGroup. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict name: description: @@ -82,6 +84,7 @@ description: - The region of feature group. - eg us-central1. + - This property is immutable, to change it, you must delete and recreate the resource. type: str state: choices: @@ -123,11 +126,6 @@ - The timestamp of when the FeatureGroup was created in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. returned: success type: str -etag: - description: - - Used to perform consistent read-modify-write updates. - returned: success - type: str state: description: The current state of the resource. returned: always @@ -143,8 +141,7 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports import copy @@ -152,44 +149,27 @@ # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class BigQuery(gcp.Resource): +class BigQuery(gcp_v2.Resource): def _request(self): return { - "bigQuerySource": gcp.remove_empties( + "bigQuerySource": gcp_v2.remove_empties( BigQueryBigQuerySource(self.request.get("big_query_source", {})).to_request() ), # remove empty values "entityIdColumns": self.request.get("entity_id_columns"), } - def _response(self): - return { - "bigQuerySource": BigQueryBigQuerySource().from_response(self.response.get("bigQuerySource", {})), - "entityIdColumns": self.response.get("entityIdColumns"), - } - -class BigQueryBigQuerySource(gcp.Resource): +class BigQueryBigQuerySource(gcp_v2.Resource): def _request(self): return { "inputUri": self.request.get("input_uri"), } - def _response(self): - return { - "inputUri": self.response.get("inputUri"), - } - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { - "bigQuery": gcp.remove_empties( + "bigQuery": gcp_v2.remove_empties( BigQuery(self.request.get("big_query", {})).to_request() ), # remove empty values "description": self.request.get("description"), @@ -199,46 +179,32 @@ def _request(self): def _response(self): return { - "bigQuery": BigQuery().from_response(self.response.get("bigQuery", {})), "createTime": self.response.get("createTime"), - "description": self.response.get("description"), "etag": self.response.get("etag"), - "labels": self.response.get("labels"), - "name": self.response.get("name"), "updateTime": self.response.get("updateTime"), } + def decode(self, response): + "Custom decoder function, mutates the response object before it is returned to the module caller." -################################################################################ -# Main -################################################################################ - + # --------- BEGIN custom decoder code --------- + r = copy.deepcopy(response) + if "name" in r: + r["name"] = r.pop("name").split("/")[-1] + return r -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj + # --------- END custom decoder code --------- -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - # --------- BEGIN custom decoder code --------- - r = copy.deepcopy(obj) - if "name" in r: - r["name"] = r.pop("name").split("/")[-1] - return r - # --------- END custom decoder code --------- +################################################################################ +# Main +################################################################################ def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -284,17 +250,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{region}/featureGroups", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{region}/featureGroups", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureGroups?featureGroupId={name}", "async_uri": "{op_id}", @@ -302,7 +262,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureGroups/{name}", "async_uri": "{op_id}", @@ -310,7 +270,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureGroups/{name}", "async_uri": "", @@ -318,7 +278,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureGroups/{name}", "async_uri": "{op_id}", @@ -326,63 +286,62 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#featureGroup") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI(request, module=module, product="VertexAI", kind="vertexai#featureGroup", op_configs=op_configs) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -392,27 +351,30 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -420,29 +382,29 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -452,6 +414,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_feature_group_feature.py b/plugins/modules/gcp_vertexai_feature_group_feature.py index a91851036..4f98d0f14 100644 --- a/plugins/modules/gcp_vertexai_feature_group_feature.py +++ b/plugins/modules/gcp_vertexai_feature_group_feature.py @@ -49,21 +49,25 @@ feature_group: description: - The name of the Feature Group. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str labels: description: - The labels with user-defined metadata to organize your FeatureGroup. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict name: description: - The resource name of the Feature Group Feature. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str region: description: - The region for the resource. - It should be the same as the feature group's region. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str state: @@ -124,8 +128,7 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports import copy @@ -133,13 +136,7 @@ # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { "description": self.request.get("description"), @@ -150,43 +147,30 @@ def _request(self): def _response(self): return { "createTime": self.response.get("createTime"), - "description": self.response.get("description"), - "labels": self.response.get("labels"), "updateTime": self.response.get("updateTime"), - "versionColumnName": self.response.get("versionColumnName"), } + def decode(self, response): + "Custom decoder function, mutates the response object before it is returned to the module caller." -################################################################################ -# Main -################################################################################ - + # --------- BEGIN custom decoder code --------- + r = copy.deepcopy(response) + if "name" in r: + r["name"] = r.pop("name").split("/")[-1] + return r -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj + # --------- END custom decoder code --------- -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - # --------- BEGIN custom decoder code --------- - r = copy.deepcopy(obj) - if "name" in r: - r["name"] = r.pop("name").split("/")[-1] - return r - # --------- END custom decoder code --------- +################################################################################ +# Main +################################################################################ def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -222,17 +206,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{region}/featureGroups/{feature_group}/features", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{region}/featureGroups/{feature_group}/features", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureGroups/{feature_group}/features?featureId={name}", "async_uri": "{op_id}", @@ -240,7 +218,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureGroups/{feature_group}/features/{name}", "async_uri": "{op_id}", @@ -248,7 +226,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureGroups/{feature_group}/features/{name}", "async_uri": "", @@ -256,7 +234,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureGroups/{feature_group}/features/{name}", "async_uri": "{op_id}", @@ -264,63 +242,64 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#featureGroupFeature") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI( + request, module=module, product="VertexAI", kind="vertexai#featureGroupFeature", op_configs=op_configs + ) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload + + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -330,27 +309,30 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -358,29 +340,29 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -390,6 +372,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_feature_online_store.py b/plugins/modules/gcp_vertexai_feature_online_store.py index 0527c7db4..22f53629f 100644 --- a/plugins/modules/gcp_vertexai_feature_online_store.py +++ b/plugins/modules/gcp_vertexai_feature_online_store.py @@ -122,6 +122,7 @@ enabled: description: - Enable embedding management. + - This property is immutable, to change it, you must delete and recreate the resource. type: bool type: dict encryption_spec: @@ -144,12 +145,14 @@ labels: description: - The labels with user-defined metadata to organize your feature online stores. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict name: description: - The resource name of the Feature Online Store. - This value may be up to 60 characters, and valid characters are [a-z0-9_]. - The first character cannot be a number. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str optimized: @@ -160,6 +163,7 @@ description: - The region of feature online store. - eg us-central1. + - This property is immutable, to change it, you must delete and recreate the resource. type: str state: choices: @@ -202,11 +206,6 @@ - The timestamp of when the feature online store was created in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. returned: success type: str -etag: - description: - - Used to perform consistent read-modify-write updates. - returned: success - type: str state: description: - The state of the Feature Online Store. @@ -224,39 +223,24 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports - # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class Bigtable(gcp.Resource): +class Bigtable(gcp_v2.Resource): def _request(self): return { - "autoScaling": gcp.remove_empties( + "autoScaling": gcp_v2.remove_empties( BigtableAutoScaling(self.request.get("auto_scaling", {})).to_request() ), # remove empty values "enableDirectBigtableAccess": self.request.get("enable_direct_bigtable_access"), "zone": self.request.get("zone"), } - def _response(self): - return { - "autoScaling": BigtableAutoScaling().from_response(self.response.get("autoScaling", {})), - "enableDirectBigtableAccess": self.response.get("enableDirectBigtableAccess"), - "zone": self.response.get("zone"), - } - -class BigtableAutoScaling(gcp.Resource): +class BigtableAutoScaling(gcp_v2.Resource): def _request(self): return { "cpuUtilizationTarget": self.request.get("cpu_utilization_target"), @@ -264,18 +248,11 @@ def _request(self): "minNodeCount": self.request.get("min_node_count"), } - def _response(self): - return { - "cpuUtilizationTarget": self.response.get("cpuUtilizationTarget"), - "maxNodeCount": self.response.get("maxNodeCount"), - "minNodeCount": self.response.get("minNodeCount"), - } - -class DedicatedServingEndpoint(gcp.Resource): +class DedicatedServingEndpoint(gcp_v2.Resource): def _request(self): return { - "privateServiceConnectConfig": gcp.remove_empties( + "privateServiceConnectConfig": gcp_v2.remove_empties( DedicatedServingEndpointPrivateServiceConnectConfig( self.request.get("private_service_connect_config", {}) ).to_request() @@ -284,53 +261,34 @@ def _request(self): def _response(self): return { - "privateServiceConnectConfig": DedicatedServingEndpointPrivateServiceConnectConfig().from_response( - self.response.get("privateServiceConnectConfig", {}) - ), "publicEndpointDomainName": self.response.get("publicEndpointDomainName"), "serviceAttachment": self.response.get("serviceAttachment"), } -class DedicatedServingEndpointPrivateServiceConnectConfig(gcp.Resource): +class DedicatedServingEndpointPrivateServiceConnectConfig(gcp_v2.Resource): def _request(self): return { "enablePrivateServiceConnect": self.request.get("enable_private_service_connect"), "projectAllowlist": self.request.get("project_allowlist"), } - def _response(self): - return { - "enablePrivateServiceConnect": self.response.get("enablePrivateServiceConnect"), - "projectAllowlist": self.response.get("projectAllowlist"), - } - -class EmbeddingManagement(gcp.Resource): +class EmbeddingManagement(gcp_v2.Resource): def _request(self): return { "enabled": self.request.get("enabled"), } - def _response(self): - return { - "enabled": self.response.get("enabled"), - } - -class EncryptionSpec(gcp.Resource): +class EncryptionSpec(gcp_v2.Resource): def _request(self): return { "kmsKeyName": self.request.get("kms_key_name"), } - def _response(self): - return { - "kmsKeyName": self.response.get("kmsKeyName"), - } - -class Optimized(gcp.Resource): +class Optimized(gcp_v2.Resource): def _request(self): return self.request.get("optimized", dict()) @@ -338,39 +296,31 @@ def _response(self): return self.response.get("optimized", dict()) -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { - "bigtable": gcp.remove_empties( + "bigtable": gcp_v2.remove_empties( Bigtable(self.request.get("bigtable", {})).to_request() ), # remove empty values - "dedicatedServingEndpoint": gcp.remove_empties( + "dedicatedServingEndpoint": gcp_v2.remove_empties( DedicatedServingEndpoint(self.request.get("dedicated_serving_endpoint", {})).to_request() ), # remove empty values - "embeddingManagement": gcp.remove_empties( + "embeddingManagement": gcp_v2.remove_empties( EmbeddingManagement(self.request.get("embedding_management", {})).to_request() ), # remove empty values - "encryptionSpec": gcp.remove_empties( + "encryptionSpec": gcp_v2.remove_empties( EncryptionSpec(self.request.get("encryption_spec", {})).to_request() ), # remove empty values "labels": self.request.get("labels"), - "optimized": gcp.remove_nones( + "optimized": gcp_v2.remove_nones( Optimized(self.request.get("optimized", {})).to_request() ), # allow empty values } def _response(self): return { - "bigtable": Bigtable().from_response(self.response.get("bigtable", {})), "createTime": self.response.get("createTime"), - "dedicatedServingEndpoint": DedicatedServingEndpoint().from_response( - self.response.get("dedicatedServingEndpoint", {}) - ), - "embeddingManagement": EmbeddingManagement().from_response(self.response.get("embeddingManagement", {})), - "encryptionSpec": EncryptionSpec().from_response(self.response.get("encryptionSpec", {})), "etag": self.response.get("etag"), - "labels": self.response.get("labels"), - "optimized": Optimized().from_response(self.response.get("optimized", {})), "updateTime": self.response.get("updateTime"), } @@ -380,26 +330,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -494,8 +428,7 @@ def main(): type="str", ), ), - mutually_exclusive=[["bigtable", "optimized"], ["embedding_management", "optimized"]], - required_one_of=[["bigtable", "optimized"]], + mutually_exclusive=[("embedding_management", "optimized")], ) if not module.params["scopes"]: @@ -503,17 +436,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{region}/featureOnlineStores", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{region}/featureOnlineStores", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureOnlineStores?featureOnlineStoreId={name}", "async_uri": "{op_id}", @@ -521,7 +448,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureOnlineStores/{name}", "async_uri": "{op_id}", @@ -529,7 +456,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureOnlineStores/{name}", "async_uri": "", @@ -537,7 +464,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureOnlineStores/{name}", "async_uri": "{op_id}", @@ -545,63 +472,64 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#featureOnlineStore") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI( + request, module=module, product="VertexAI", kind="vertexai#featureOnlineStore", op_configs=op_configs + ) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + read_link: str = "" # give it a chance for pre-read to overload + + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -611,31 +539,35 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload # --------- BEGIN pre-delete custom code --------- - if params.get("force_destroy"): - delete_uri += "?force=true" + if request.get("force_destroy", False): + delete_link = resource.build_link("delete") + "?force=true" + # --------- END pre-delete custom code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -643,29 +575,29 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -675,6 +607,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_feature_online_store_featureview.py b/plugins/modules/gcp_vertexai_feature_online_store_featureview.py index f90d6af7e..00be7068f 100644 --- a/plugins/modules/gcp_vertexai_feature_online_store_featureview.py +++ b/plugins/modules/gcp_vertexai_feature_online_store_featureview.py @@ -62,6 +62,7 @@ feature_online_store: description: - The name of the FeatureOnlineStore to use for the featureview. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str feature_registry_source: @@ -94,17 +95,20 @@ labels: description: - A set of key/value label pairs to assign to this FeatureView. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict name: description: - Name of the FeatureView. - This value may be up to 60 characters, and valid characters are [a-z0-9_]. - The first character cannot be a number. + - This property is immutable, to change it, you must delete and recreate the resource. type: str region: description: - The region for the resource. - It should be the same as the featureonlinestore region. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str state: @@ -134,6 +138,7 @@ description: - Configuration for vector search. - It contains the required configurations to create an index from source data, so that approximate nearest neighbor (a.k.a ANN) algorithms search can be performed during online serving. + - This property is immutable, to change it, you must delete and recreate the resource. suboptions: brute_force_config: description: @@ -229,35 +234,21 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports - # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class BigQuerySource(gcp.Resource): +class BigQuerySource(gcp_v2.Resource): def _request(self): return { "entityIdColumns": self.request.get("entity_id_columns"), "uri": self.request.get("uri"), } - def _response(self): - return { - "entityIdColumns": self.response.get("entityIdColumns"), - "uri": self.response.get("uri"), - } - -class FeatureRegistrySource(gcp.Resource): +class FeatureRegistrySource(gcp_v2.Resource): def _request(self): return { "featureGroups": [ @@ -267,48 +258,27 @@ def _request(self): "projectNumber": self.request.get("project_number"), } - def _response(self): - return { - "featureGroups": [ - FeatureRegistrySourceFeatureGroup().from_response(item) - for item in (self.response.get("featureGroups") or []) - ], - "projectNumber": self.response.get("projectNumber"), - } - -class FeatureRegistrySourceFeatureGroup(gcp.Resource): +class FeatureRegistrySourceFeatureGroup(gcp_v2.Resource): def _request(self): return { "featureGroupId": self.request.get("feature_group_id"), "featureIds": self.request.get("feature_ids"), } - def _response(self): - return { - "featureGroupId": self.response.get("featureGroupId"), - "featureIds": self.response.get("featureIds"), - } - -class SyncConfig(gcp.Resource): +class SyncConfig(gcp_v2.Resource): def _request(self): return { "continuous": self.request.get("continuous"), "cron": self.request.get("cron"), } - def _response(self): - return { - "continuous": self.response.get("continuous"), - "cron": self.response.get("cron"), - } - -class VectorSearchConfig(gcp.Resource): +class VectorSearchConfig(gcp_v2.Resource): def _request(self): return { - "bruteForceConfig": gcp.remove_nones( + "bruteForceConfig": gcp_v2.remove_nones( VectorSearchConfigBruteForceConfig(self.request.get("brute_force_config", {})).to_request() ), # allow empty values "crowdingColumn": self.request.get("crowding_column"), @@ -316,26 +286,13 @@ def _request(self): "embeddingColumn": self.request.get("embedding_column"), "embeddingDimension": self.request.get("embedding_dimension"), "filterColumns": self.request.get("filter_columns"), - "treeAhConfig": gcp.remove_empties( + "treeAhConfig": gcp_v2.remove_empties( VectorSearchConfigTreeAhConfig(self.request.get("tree_ah_config", {})).to_request() ), # remove empty values } - def _response(self): - return { - "bruteForceConfig": VectorSearchConfigBruteForceConfig().from_response( - self.response.get("bruteForceConfig", {}) - ), - "crowdingColumn": self.response.get("crowdingColumn"), - "distanceMeasureType": self.response.get("distanceMeasureType"), - "embeddingColumn": self.response.get("embeddingColumn"), - "embeddingDimension": self.response.get("embeddingDimension"), - "filterColumns": self.response.get("filterColumns"), - "treeAhConfig": VectorSearchConfigTreeAhConfig().from_response(self.response.get("treeAhConfig", {})), - } - -class VectorSearchConfigBruteForceConfig(gcp.Resource): +class VectorSearchConfigBruteForceConfig(gcp_v2.Resource): def _request(self): return self.request.get("brute_force_config", dict()) @@ -343,47 +300,35 @@ def _response(self): return self.response.get("brute_force_config", dict()) -class VectorSearchConfigTreeAhConfig(gcp.Resource): +class VectorSearchConfigTreeAhConfig(gcp_v2.Resource): def _request(self): return { "leafNodeEmbeddingCount": self.request.get("leaf_node_embedding_count"), } - def _response(self): - return { - "leafNodeEmbeddingCount": self.response.get("leafNodeEmbeddingCount"), - } - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { - "bigQuerySource": gcp.remove_empties( + "bigQuerySource": gcp_v2.remove_empties( BigQuerySource(self.request.get("big_query_source", {})).to_request() ), # remove empty values - "featureRegistrySource": gcp.remove_empties( + "featureRegistrySource": gcp_v2.remove_empties( FeatureRegistrySource(self.request.get("feature_registry_source", {})).to_request() ), # remove empty values "labels": self.request.get("labels"), - "syncConfig": gcp.remove_empties( + "syncConfig": gcp_v2.remove_empties( SyncConfig(self.request.get("sync_config", {})).to_request() ), # remove empty values - "vectorSearchConfig": gcp.remove_empties( + "vectorSearchConfig": gcp_v2.remove_empties( VectorSearchConfig(self.request.get("vector_search_config", {})).to_request() ), # remove empty values } def _response(self): return { - "bigQuerySource": BigQuerySource().from_response(self.response.get("bigQuerySource", {})), "createTime": self.response.get("createTime"), - "featureRegistrySource": FeatureRegistrySource().from_response( - self.response.get("featureRegistrySource", {}) - ), - "labels": self.response.get("labels"), - "syncConfig": SyncConfig().from_response(self.response.get("syncConfig", {})), "updateTime": self.response.get("updateTime"), - "vectorSearchConfig": VectorSearchConfig().from_response(self.response.get("vectorSearchConfig", {})), } @@ -392,26 +337,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -480,7 +409,7 @@ def main(): type="str", ), ), - mutually_exclusive=[["continuous", "cron"]], + mutually_exclusive=[("continuous", "cron")], ), vector_search_config=dict( type="dict", @@ -515,15 +444,9 @@ def main(): ), ), ), - mutually_exclusive=[["brute_force_config", "tree_ah_config"]], - required_one_of=[["brute_force_config", "tree_ah_config"]], ), ), - mutually_exclusive=[ - ["big_query_source", "feature_registry_source"], - ["feature_registry_source", "vector_search_config"], - ], - required_one_of=[["big_query_source", "feature_registry_source"]], + mutually_exclusive=[("feature_registry_source", "vector_search_config")], ) if not module.params["scopes"]: @@ -531,17 +454,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{region}/featureOnlineStores/{feature_online_store}/featureViews", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{region}/featureOnlineStores/{feature_online_store}/featureViews", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureOnlineStores/{feature_online_store}/featureViews?featureViewId={name}", "async_uri": "{op_id}", @@ -549,7 +466,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureOnlineStores/{feature_online_store}/featureViews/{name}", "async_uri": "{op_id}", @@ -557,7 +474,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureOnlineStores/{feature_online_store}/featureViews/{name}", "async_uri": "", @@ -565,7 +482,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featureOnlineStores/{feature_online_store}/featureViews/{name}", "async_uri": "", @@ -573,63 +490,64 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#featureOnlineStoreFeatureview") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI( + request, module=module, product="VertexAI", kind="vertexai#featureOnlineStoreFeatureview", op_configs=op_configs + ) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload + + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -639,27 +557,30 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -667,29 +588,29 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -699,6 +620,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_featurestore.py b/plugins/modules/gcp_vertexai_featurestore.py index cc1ebb2fc..902a95a8e 100644 --- a/plugins/modules/gcp_vertexai_featurestore.py +++ b/plugins/modules/gcp_vertexai_featurestore.py @@ -62,12 +62,14 @@ labels: description: - A set of key/value label pairs to assign to this Featurestore. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict name: description: - The name of the Featurestore. - This value may be up to 60 characters, and valid characters are [a-z0-9_]. - The first character cannot be a number. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str online_serving_config: @@ -111,6 +113,7 @@ description: - The region of the dataset. - eg us-central1. + - This property is immutable, to change it, you must delete and recreate the resource. type: str state: choices: @@ -175,11 +178,6 @@ - The timestamp of when the featurestore was created in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. returned: success type: str -etag: - description: - - Used to perform consistent read-modify-write updates. - returned: success - type: str state: description: The current state of the resource. returned: always @@ -195,70 +193,45 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports - # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class EncryptionSpec(gcp.Resource): +class EncryptionSpec(gcp_v2.Resource): def _request(self): return { "kmsKeyName": self.request.get("kms_key_name"), } - def _response(self): - return { - "kmsKeyName": self.response.get("kmsKeyName"), - } - -class OnlineServingConfig(gcp.Resource): +class OnlineServingConfig(gcp_v2.Resource): def _request(self): return { "fixedNodeCount": self.request.get("fixed_node_count"), - "scaling": gcp.remove_empties( + "scaling": gcp_v2.remove_empties( OnlineServingConfigScaling(self.request.get("scaling", {})).to_request() ), # remove empty values } - def _response(self): - return { - "fixedNodeCount": self.response.get("fixedNodeCount"), - "scaling": OnlineServingConfigScaling().from_response(self.response.get("scaling", {})), - } - -class OnlineServingConfigScaling(gcp.Resource): +class OnlineServingConfigScaling(gcp_v2.Resource): def _request(self): return { "maxNodeCount": self.request.get("max_node_count"), "minNodeCount": self.request.get("min_node_count"), } - def _response(self): - return { - "maxNodeCount": self.response.get("maxNodeCount"), - "minNodeCount": self.response.get("minNodeCount"), - } - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { - "encryptionSpec": gcp.remove_empties( + "encryptionSpec": gcp_v2.remove_empties( EncryptionSpec(self.request.get("encryption_spec", {})).to_request() ), # remove empty values "labels": self.request.get("labels"), - "onlineServingConfig": gcp.remove_empties( + "onlineServingConfig": gcp_v2.remove_empties( OnlineServingConfig(self.request.get("online_serving_config", {})).to_request() ), # remove empty values "onlineStorageTtlDays": self.request.get("online_storage_ttl_days"), @@ -267,11 +240,7 @@ def _request(self): def _response(self): return { "createTime": self.response.get("createTime"), - "encryptionSpec": EncryptionSpec().from_response(self.response.get("encryptionSpec", {})), "etag": self.response.get("etag"), - "labels": self.response.get("labels"), - "onlineServingConfig": OnlineServingConfig().from_response(self.response.get("onlineServingConfig", {})), - "onlineStorageTtlDays": self.response.get("onlineStorageTtlDays"), "updateTime": self.response.get("updateTime"), } @@ -281,26 +250,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -348,8 +301,6 @@ def main(): ), ), ), - mutually_exclusive=[["fixed_node_count", "scaling"]], - required_one_of=[["fixed_node_count", "scaling"]], ), online_storage_ttl_days=dict( type="int", @@ -366,17 +317,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{region}/featurestores", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{region}/featurestores", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featurestores?featurestoreId={name}", "async_uri": "{op_id}", @@ -384,7 +329,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featurestores/{name}", "async_uri": "{op_id}", @@ -392,7 +337,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featurestores/{name}", "async_uri": "", @@ -400,7 +345,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/featurestores/{name}", "async_uri": "{op_id}", @@ -408,69 +353,68 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#featurestore") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI(request, module=module, product="VertexAI", kind="vertexai#featurestore", op_configs=op_configs) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload # --------- BEGIN pre-read custom code --------- # if this comes from a registered variable, strip down to base name - params["name"] = params["name"].split("/")[-1] + request["name"] = request["name"].split("/")[-1] # --------- END pre-read custom code --------- - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -480,31 +424,35 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload # --------- BEGIN pre-delete custom code --------- - if params.get("force_destroy"): - delete_uri += "?force=true" + if request.get("force_destroy"): + delete_link = resource.build_link("delete") + "?force=true" + # --------- END pre-delete custom code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -512,29 +460,29 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -544,6 +492,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_featurestore_entitytype.py b/plugins/modules/gcp_vertexai_featurestore_entitytype.py index e536c3aee..bd5726546 100644 --- a/plugins/modules/gcp_vertexai_featurestore_entitytype.py +++ b/plugins/modules/gcp_vertexai_featurestore_entitytype.py @@ -49,11 +49,13 @@ featurestore: description: - The name of the Featurestore to use, in the format projects/{project}/locations/{location}/featurestores/{featurestore}. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str labels: description: - A set of key/value label pairs to assign to this EntityType. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict monitoring_config: description: @@ -157,6 +159,7 @@ - The name of the EntityType. - This value may be up to 60 characters, and valid characters are [a-z0-9_]. - The first character cannot be a number. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str offline_storage_ttl_days: @@ -209,11 +212,6 @@ - The timestamp of when the featurestore was created in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. returned: success type: str -etag: - description: - - Used to perform consistent read-modify-write updates. - returned: success - type: str state: description: The current state of the resource. returned: always @@ -229,8 +227,7 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports import re @@ -238,89 +235,51 @@ # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class MonitoringConfig(gcp.Resource): +class MonitoringConfig(gcp_v2.Resource): def _request(self): return { - "categoricalThresholdConfig": gcp.remove_empties( + "categoricalThresholdConfig": gcp_v2.remove_empties( MonitoringConfigCategoricalThresholdConfig( self.request.get("categorical_threshold_config", {}) ).to_request() ), # remove empty values - "importFeaturesAnalysis": gcp.remove_empties( + "importFeaturesAnalysis": gcp_v2.remove_empties( MonitoringConfigImportFeaturesAnalysis(self.request.get("import_features_analysis", {})).to_request() ), # remove empty values - "numericalThresholdConfig": gcp.remove_empties( + "numericalThresholdConfig": gcp_v2.remove_empties( MonitoringConfigNumericalThresholdConfig( self.request.get("numerical_threshold_config", {}) ).to_request() ), # remove empty values - "snapshotAnalysis": gcp.remove_empties( + "snapshotAnalysis": gcp_v2.remove_empties( MonitoringConfigSnapshotAnalysis(self.request.get("snapshot_analysis", {})).to_request() ), # remove empty values } - def _response(self): - return { - "categoricalThresholdConfig": MonitoringConfigCategoricalThresholdConfig().from_response( - self.response.get("categoricalThresholdConfig", {}) - ), - "importFeaturesAnalysis": MonitoringConfigImportFeaturesAnalysis().from_response( - self.response.get("importFeaturesAnalysis", {}) - ), - "numericalThresholdConfig": MonitoringConfigNumericalThresholdConfig().from_response( - self.response.get("numericalThresholdConfig", {}) - ), - "snapshotAnalysis": MonitoringConfigSnapshotAnalysis().from_response( - self.response.get("snapshotAnalysis", {}) - ), - } - -class MonitoringConfigCategoricalThresholdConfig(gcp.Resource): +class MonitoringConfigCategoricalThresholdConfig(gcp_v2.Resource): def _request(self): return { "value": self.request.get("value"), } - def _response(self): - return { - "value": self.response.get("value"), - } - -class MonitoringConfigImportFeaturesAnalysis(gcp.Resource): +class MonitoringConfigImportFeaturesAnalysis(gcp_v2.Resource): def _request(self): return { "anomalyDetectionBaseline": self.request.get("anomaly_detection_baseline"), "state": self.request.get("state"), } - def _response(self): - return { - "anomalyDetectionBaseline": self.response.get("anomalyDetectionBaseline"), - "state": self.response.get("state"), - } - -class MonitoringConfigNumericalThresholdConfig(gcp.Resource): +class MonitoringConfigNumericalThresholdConfig(gcp_v2.Resource): def _request(self): return { "value": self.request.get("value"), } - def _response(self): - return { - "value": self.response.get("value"), - } - -class MonitoringConfigSnapshotAnalysis(gcp.Resource): +class MonitoringConfigSnapshotAnalysis(gcp_v2.Resource): def _request(self): return { "disabled": self.request.get("disabled"), @@ -329,21 +288,13 @@ def _request(self): "stalenessDays": self.request.get("staleness_days"), } - def _response(self): - return { - "disabled": self.response.get("disabled"), - "monitoringInterval": self.response.get("monitoringInterval"), - "monitoringIntervalDays": self.response.get("monitoringIntervalDays"), - "stalenessDays": self.response.get("stalenessDays"), - } - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { "description": self.request.get("description"), "labels": self.request.get("labels"), - "monitoringConfig": gcp.remove_empties( + "monitoringConfig": gcp_v2.remove_empties( MonitoringConfig(self.request.get("monitoring_config", {})).to_request() ), # remove empty values "offlineStorageTtlDays": self.request.get("offline_storage_ttl_days"), @@ -352,11 +303,7 @@ def _request(self): def _response(self): return { "createTime": self.response.get("createTime"), - "description": self.response.get("description"), "etag": self.response.get("etag"), - "labels": self.response.get("labels"), - "monitoringConfig": MonitoringConfig().from_response(self.response.get("monitoringConfig", {})), - "offlineStorageTtlDays": self.response.get("offlineStorageTtlDays"), "updateTime": self.response.get("updateTime"), } @@ -366,26 +313,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -472,12 +403,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{"uri": "{featurestore}/entityTypes", "async_uri": "", "verb": "GET", "timeout_minutes": 0} - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="{featurestore}/entityTypes", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "{featurestore}/entityTypes?entityTypeId={name}", "async_uri": "{op_id}", @@ -485,7 +415,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "{featurestore}/entityTypes/{name}", "async_uri": "{op_id}", @@ -493,81 +423,82 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{"uri": "{featurestore}/entityTypes/{name}", "async_uri": "", "verb": "GET", "timeout_minutes": 0} ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{"uri": "{featurestore}/entityTypes/{name}", "async_uri": "", "verb": "PATCH", "timeout_minutes": 20} ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#featurestoreEntitytype") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI( + request, module=module, product="VertexAI", kind="vertexai#featurestoreEntitytype", op_configs=op_configs + ) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload # --------- BEGIN pre-read custom code --------- # if this comes from a registered variable, strip down to base name - params["name"] = params["name"].split("/")[-1] + resource.url_params["name"] = request.get("name").split("/")[-1] # extract region from featurestore pattern = r"projects/(.+)/locations/(.+)/featurestores/(.+)" - match = re.search(pattern, str(params["featurestore"])) + match = re.search(pattern, str(request.get("featurestore"))) if match: - params["region"] = match.group(2) + resource.url_params["region"] = match.group(2) # --------- END pre-read custom code --------- - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -577,27 +508,30 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -605,29 +539,29 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -637,6 +571,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_featurestore_entitytype_feature.py b/plugins/modules/gcp_vertexai_featurestore_entitytype_feature.py index 1dbc62096..dffa3e112 100644 --- a/plugins/modules/gcp_vertexai_featurestore_entitytype_feature.py +++ b/plugins/modules/gcp_vertexai_featurestore_entitytype_feature.py @@ -49,17 +49,20 @@ entitytype: description: - The name of the Featurestore to use, in the format projects/{project}/locations/{location}/featurestores/{featurestore}/entityTypes/{entitytype}. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str labels: description: - A set of key/value label pairs to assign to the feature. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict name: description: - The name of the feature. - The feature can be up to 64 characters long and can consist only of ASCII Latin letters A-Z and a-z, underscore(_), and ASCII digits 0-9 starting with a letter. - The value will be unique given an entity type. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str state: @@ -75,6 +78,7 @@ - Type of Feature value. - Immutable. - https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.featurestores.entityTypes.features#ValueType. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str requirements: @@ -107,11 +111,6 @@ - The timestamp of when the entity type was created in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. returned: success type: str -etag: - description: - - Used to perform consistent read-modify-write updates. - returned: success - type: str state: description: The current state of the resource. returned: always @@ -127,8 +126,7 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports import re @@ -136,13 +134,7 @@ # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { "description": self.request.get("description"), @@ -153,11 +145,8 @@ def _request(self): def _response(self): return { "createTime": self.response.get("createTime"), - "description": self.response.get("description"), "etag": self.response.get("etag"), - "labels": self.response.get("labels"), "updateTime": self.response.get("updateTime"), - "valueType": self.response.get("valueType"), } @@ -166,26 +155,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -218,12 +191,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{"uri": "{entitytype}/features", "async_uri": "", "verb": "GET", "timeout_minutes": 0} - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="{entitytype}/features", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "{entitytype}/features?featureId={name}", "async_uri": "{op_id}", @@ -231,7 +203,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "{entitytype}/features/{name}", "async_uri": "{op_id}", @@ -239,81 +211,82 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{"uri": "{entitytype}/features/{name}", "async_uri": "", "verb": "GET", "timeout_minutes": 0} ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{"uri": "{entitytype}/features/{name}", "async_uri": "", "verb": "PATCH", "timeout_minutes": 20} ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#featurestoreEntitytypeFeature") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI( + request, module=module, product="VertexAI", kind="vertexai#featurestoreEntitytypeFeature", op_configs=op_configs + ) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload # --------- BEGIN pre-read custom code --------- # if this comes from a registered variable, strip down to base name - params["name"] = params["name"].split("/")[-1] + resource.url_params["name"] = request.get("name").split("/")[-1] # extract region from entitytype pattern = r"projects/(.+)/locations/(.+)/featurestores/(.+)/entityTypes/(.+)" - match = re.search(pattern, str(params["entitytype"])) + match = re.search(pattern, str(request.get("entitytype"))) if match: - params["region"] = match.group(2) + resource.url_params["region"] = match.group(2) # --------- END pre-read custom code --------- - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -323,27 +296,30 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -351,29 +327,29 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -383,6 +359,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_index.py b/plugins/modules/gcp_vertexai_index.py index 0cf67ae2a..39be5aae8 100644 --- a/plugins/modules/gcp_vertexai_index.py +++ b/plugins/modules/gcp_vertexai_index.py @@ -55,12 +55,14 @@ description: - Customer-managed encryption key spec for an Index. - If set, this Index and all sub-resources of this Index will be secured by this key. + - This property is immutable, to change it, you must delete and recreate the resource. suboptions: kms_key_name: description: - The Cloud KMS resource identifier of the customer managed encryption key used to protect a resource. - 'Has the form: `projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key`.' - The key needs to be in the same region as where the compute resource is created. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str type: dict @@ -72,10 +74,12 @@ - If not set, BATCH_UPDATE will be used by default. - '* BATCH_UPDATE: user can call indexes.patch with files on Cloud Storage of datapoints to update.' - '* STREAM_UPDATE: user can call indexes.upsertDatapoints/DeleteDatapoints to update the Index and the updates will be applied in corresponding DeployedIndexes in nearly real-time.' + - This property is immutable, to change it, you must delete and recreate the resource. type: str labels: description: - The labels with user-defined metadata to organize your Indexes. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict metadata: description: @@ -87,6 +91,7 @@ config: description: - The configuration of the Matching Engine Index. + - This property is immutable, to change it, you must delete and recreate the resource. required: true suboptions: algorithm_config: @@ -150,6 +155,7 @@ - These are called "shards". - The shard size must be specified when creating an index. - 'The value must be one of the followings: * SHARD_SIZE_SMALL: Small (2GB) * SHARD_SIZE_MEDIUM: Medium (20GB) * SHARD_SIZE_LARGE: Large (50GB).' + - This property is immutable, to change it, you must delete and recreate the resource. type: str type: dict contents_delta_uri: @@ -169,6 +175,7 @@ description: - The region of the index. - eg us-central1. + - This property is immutable, to change it, you must delete and recreate the resource. type: str state: choices: @@ -256,11 +263,6 @@ elements: dict returned: success type: list -etag: - description: - - Used to perform consistent read-modify-write updates. - returned: success - type: str indexStats: contains: shardsCount: @@ -303,21 +305,13 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports - # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class DeployedIndexes(gcp.Resource): +class DeployedIndexes(gcp_v2.Resource): def _response(self): return { "deployedIndexId": self.response.get("deployedIndexId"), @@ -325,19 +319,14 @@ def _response(self): } -class EncryptionSpec(gcp.Resource): +class EncryptionSpec(gcp_v2.Resource): def _request(self): return { "kmsKeyName": self.request.get("kms_key_name"), } - def _response(self): - return { - "kmsKeyName": self.response.get("kmsKeyName"), - } - -class IndexStats(gcp.Resource): +class IndexStats(gcp_v2.Resource): def _response(self): return { "shardsCount": self.response.get("shardsCount"), @@ -345,28 +334,21 @@ def _response(self): } -class Metadata(gcp.Resource): +class Metadata(gcp_v2.Resource): def _request(self): return { - "config": gcp.remove_empties( + "config": gcp_v2.remove_empties( MetadataConfig(self.request.get("config", {})).to_request() ), # remove empty values "contentsDeltaUri": self.request.get("contents_delta_uri"), "isCompleteOverwrite": self.request.get("is_complete_overwrite"), } - def _response(self): - return { - "config": MetadataConfig().from_response(self.response.get("config", {})), - "contentsDeltaUri": self.response.get("contentsDeltaUri"), - "isCompleteOverwrite": self.response.get("isCompleteOverwrite"), - } - -class MetadataConfig(gcp.Resource): +class MetadataConfig(gcp_v2.Resource): def _request(self): return { - "algorithmConfig": gcp.remove_empties( + "algorithmConfig": gcp_v2.remove_empties( MetadataConfigAlgorithmConfig(self.request.get("algorithm_config", {})).to_request() ), # remove empty values "approximateNeighborsCount": self.request.get("approximate_neighbors_count"), @@ -376,40 +358,20 @@ def _request(self): "shardSize": self.request.get("shard_size"), } - def _response(self): - return { - "algorithmConfig": MetadataConfigAlgorithmConfig().from_response(self.response.get("algorithmConfig", {})), - "approximateNeighborsCount": self.response.get("approximateNeighborsCount"), - "dimensions": self.response.get("dimensions"), - "distanceMeasureType": self.response.get("distanceMeasureType"), - "featureNormType": self.response.get("featureNormType"), - "shardSize": self.response.get("shardSize"), - } - -class MetadataConfigAlgorithmConfig(gcp.Resource): +class MetadataConfigAlgorithmConfig(gcp_v2.Resource): def _request(self): return { - "bruteForceConfig": gcp.remove_nones( + "bruteForceConfig": gcp_v2.remove_nones( MetadataConfigAlgorithmConfigBruteForceConfig(self.request.get("brute_force_config", {})).to_request() ), # allow empty values - "treeAhConfig": gcp.remove_empties( + "treeAhConfig": gcp_v2.remove_empties( MetadataConfigAlgorithmConfigTreeAhConfig(self.request.get("tree_ah_config", {})).to_request() ), # remove empty values } - def _response(self): - return { - "bruteForceConfig": MetadataConfigAlgorithmConfigBruteForceConfig().from_response( - self.response.get("bruteForceConfig", {}) - ), - "treeAhConfig": MetadataConfigAlgorithmConfigTreeAhConfig().from_response( - self.response.get("treeAhConfig", {}) - ), - } - -class MetadataConfigAlgorithmConfigBruteForceConfig(gcp.Resource): +class MetadataConfigAlgorithmConfigBruteForceConfig(gcp_v2.Resource): def _request(self): return self.request.get("brute_force_config", dict()) @@ -417,31 +379,25 @@ def _response(self): return self.response.get("brute_force_config", dict()) -class MetadataConfigAlgorithmConfigTreeAhConfig(gcp.Resource): +class MetadataConfigAlgorithmConfigTreeAhConfig(gcp_v2.Resource): def _request(self): return { "leafNodeEmbeddingCount": self.request.get("leaf_node_embedding_count"), "leafNodesToSearchPercent": self.request.get("leaf_nodes_to_search_percent"), } - def _response(self): - return { - "leafNodeEmbeddingCount": self.response.get("leafNodeEmbeddingCount"), - "leafNodesToSearchPercent": self.response.get("leafNodesToSearchPercent"), - } - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { "description": self.request.get("description"), "displayName": self.request.get("display_name"), - "encryptionSpec": gcp.remove_empties( + "encryptionSpec": gcp_v2.remove_empties( EncryptionSpec(self.request.get("encryption_spec", {})).to_request() ), # remove empty values "indexUpdateMethod": self.request.get("index_update_method"), "labels": self.request.get("labels"), - "metadata": gcp.remove_empties( + "metadata": gcp_v2.remove_empties( Metadata(self.request.get("metadata", {})).to_request() ), # remove empty values } @@ -452,14 +408,8 @@ def _response(self): "deployedIndexes": [ DeployedIndexes().from_response(item) for item in (self.response.get("deployedIndexes") or []) ], - "description": self.response.get("description"), - "displayName": self.response.get("displayName"), - "encryptionSpec": EncryptionSpec().from_response(self.response.get("encryptionSpec", {})), "etag": self.response.get("etag"), "indexStats": IndexStats().from_response(self.response.get("indexStats", {})), - "indexUpdateMethod": self.response.get("indexUpdateMethod"), - "labels": self.response.get("labels"), - "metadata": Metadata().from_response(self.response.get("metadata", {})), "metadataSchemaUri": self.response.get("metadataSchemaUri"), "name": self.response.get("name"), "updateTime": self.response.get("updateTime"), @@ -471,26 +421,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( state=dict( type="str", @@ -549,8 +483,6 @@ def main(): ), ), ), - mutually_exclusive=[["brute_force_config", "tree_ah_config"]], - required_one_of=[["brute_force_config", "tree_ah_config"]], ), approximate_neighbors_count=dict( type="int", @@ -592,17 +524,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{region}/indexes", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{region}/indexes", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/indexes", "async_uri": "{op_id}", @@ -610,7 +536,7 @@ def main(): "timeout_minutes": 180, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/indexes/{name}", "async_uri": "{op_id}", @@ -618,7 +544,7 @@ def main(): "timeout_minutes": 180, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/indexes/{name}", "async_uri": "", @@ -626,7 +552,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/indexes/{name}", "async_uri": "{op_id}", @@ -634,36 +560,39 @@ def main(): "timeout_minutes": 180, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#index") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI(request, module=module, product="VertexAI", kind="vertexai#index", op_configs=op_configs) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload # --------- BEGIN pre-read custom code --------- - # for this module, we're hitting the list endpoint and filtering on display name - read_uri = op_configs.base_url.uri + "?filter=displayName=" + params.get("display_name") + # for this module, we're hitting the list endpoint and filtering on display name and rebuild the link + read_link = resource.build_link("list") + "?filter=displayName=" + request.get("display_name") # --------- END pre-read custom code --------- - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) # --------- BEGIN post-read custom code --------- # if there are existing indexes, the call would have returned a list - if not gcp.empty(existing_obj): + if not gcp_v2.empty(existing_obj): for idx in existing_obj.get("indexes", []): - if idx.get("displayName") == params.get("display_name"): + if idx.get("displayName") == request.get("display_name"): existing_obj = idx break @@ -672,41 +601,37 @@ def main(): if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -716,32 +641,35 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload # --------- BEGIN pre-delete custom code --------- # need to set to the required parameter "name" to the existing resource name - params["name"] = existing_obj["name"].split("/")[-1] + resource.url_params["name"] = existing_obj["name"].split("/")[-1] # --------- END pre-delete custom code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -749,31 +677,31 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload # --------- BEGIN pre-update custom code --------- # need to set to the required parameter "name" to the existing resource name - params["name"] = existing_obj["name"].split("/")[-1] + resource.url_params["name"] = existing_obj["name"].split("/")[-1] update_mask = [] metadata_mask = [] - req_metadata = gcp.remove_empties(resource.to_request().get("metadata")) - obj_metadata = gcp.remove_empties(existing_obj.get("metadata")) - gcp.debug(module, req_metadata=req_metadata, obj_metadata=obj_metadata) + req_metadata = gcp_v2.remove_empties(resource.to_request().get("metadata")) + obj_metadata = gcp_v2.remove_empties(existing_obj.get("metadata")) + gcp_v2.debug(module, req_metadata=req_metadata, obj_metadata=obj_metadata) if req_metadata.get("contentsDeltaUri") != obj_metadata.get("contentsDeltaUri"): metadata_mask.append("metadata.contentsDeltaUri") if req_metadata.get("isCompleteOverwrite", False) != obj_metadata.get("isCompleteOverwrite", False): metadata_mask.append("metadata.isCompleteOverwrite") - if not gcp.deep_equal(req_metadata["config"], obj_metadata["config"]): + if not gcp_v2.deep_equal(req_metadata["config"], obj_metadata["config"]): metadata_mask.append("metadata.config") # if description is set, we need to update it - if "description" in params: + if "description" in request: update_mask.append("description") - if "labels" in params: + if "labels" in request: update_mask.append("labels") # if we're updating metadata, we can't update other fields @@ -781,28 +709,28 @@ def main(): module.fail_json(msg="Cannot update index metadata and other fields at the same time") update_mask.extend(metadata_mask) - update_uri += "?updateMask=" + ",".join(update_mask) + update_link = resource.build_link("update") + "?updateMask=" + ",".join(update_mask) # --------- END pre-update custom code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -812,6 +740,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_index_endpoint.py b/plugins/modules/gcp_vertexai_index_endpoint.py index 50858e9a2..04ac16b88 100644 --- a/plugins/modules/gcp_vertexai_index_endpoint.py +++ b/plugins/modules/gcp_vertexai_index_endpoint.py @@ -55,18 +55,21 @@ description: - Customer-managed encryption key spec for an IndexEndpoint. - If set, this IndexEndpoint and all sub-resources of this IndexEndpoint will be secured by this key. + - This property is immutable, to change it, you must delete and recreate the resource. suboptions: kms_key_name: description: - The Cloud KMS resource identifier of the customer managed encryption key used to protect a resource. - 'Has the form: `projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key`.' - The key needs to be in the same region as where the compute resource is created. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str type: dict labels: description: - The labels with user-defined metadata to organize your Indexes. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict network: description: @@ -75,20 +78,24 @@ - If left unspecified, the index endpoint is not peered with any network. - '[Format](https://cloud.google.com/compute/docs/reference/rest/v1/networks/insert): `projects/{project}/global/networks/{network}`.' - Where `{project}` is a project number, as in `12345`, and `{network}` is network name. + - This property is immutable, to change it, you must delete and recreate the resource. type: str private_service_connect_config: description: - Configuration for private service connect. - '`network` and `privateServiceConnectConfig` are mutually exclusive.' + - This property is immutable, to change it, you must delete and recreate the resource. suboptions: enable_private_service_connect: description: - If set to true, the IndexEndpoint is created without private service access. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: bool project_allowlist: description: - A list of Projects from which the forwarding rule will target the service attachment. + - This property is immutable, to change it, you must delete and recreate the resource. elements: str type: list psc_automation_configs: @@ -113,11 +120,13 @@ public_endpoint_enabled: description: - If true, the deployed index will be accessible through public endpoint. + - This property is immutable, to change it, you must delete and recreate the resource. type: bool region: description: - The region of the index endpoint. - eg us-central1. + - This property is immutable, to change it, you must delete and recreate the resource. type: str state: choices: @@ -168,11 +177,6 @@ - The timestamp of when the Index was created in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. returned: success type: str -etag: - description: - - Used to perform consistent read-modify-write updates. - returned: success - type: str name: description: - The resource name of the Index. @@ -198,33 +202,20 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports - # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class EncryptionSpec(gcp.Resource): +class EncryptionSpec(gcp_v2.Resource): def _request(self): return { "kmsKeyName": self.request.get("kms_key_name"), } - def _response(self): - return { - "kmsKeyName": self.response.get("kmsKeyName"), - } - -class PrivateServiceConnectConfig(gcp.Resource): +class PrivateServiceConnectConfig(gcp_v2.Resource): def _request(self): return { "enablePrivateServiceConnect": self.request.get("enable_private_service_connect"), @@ -235,42 +226,26 @@ def _request(self): ], } - def _response(self): - return { - "enablePrivateServiceConnect": self.response.get("enablePrivateServiceConnect"), - "projectAllowlist": self.response.get("projectAllowlist"), - "pscAutomationConfigs": [ - PrivateServiceConnectConfigPscAutomationConfig().from_response(item) - for item in (self.response.get("pscAutomationConfigs") or []) - ], - } - -class PrivateServiceConnectConfigPscAutomationConfig(gcp.Resource): +class PrivateServiceConnectConfigPscAutomationConfig(gcp_v2.Resource): def _request(self): return { "network": self.request.get("network"), "projectId": self.request.get("project_id"), } - def _response(self): - return { - "network": self.response.get("network"), - "projectId": self.response.get("projectId"), - } - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { "description": self.request.get("description"), "displayName": self.request.get("display_name"), - "encryptionSpec": gcp.remove_empties( + "encryptionSpec": gcp_v2.remove_empties( EncryptionSpec(self.request.get("encryption_spec", {})).to_request() ), # remove empty values "labels": self.request.get("labels"), "network": self.request.get("network"), - "privateServiceConnectConfig": gcp.remove_empties( + "privateServiceConnectConfig": gcp_v2.remove_empties( PrivateServiceConnectConfig(self.request.get("private_service_connect_config", {})).to_request() ), # remove empty values "publicEndpointEnabled": self.request.get("public_endpoint_enabled"), @@ -279,18 +254,9 @@ def _request(self): def _response(self): return { "createTime": self.response.get("createTime"), - "description": self.response.get("description"), - "displayName": self.response.get("displayName"), - "encryptionSpec": EncryptionSpec().from_response(self.response.get("encryptionSpec", {})), "etag": self.response.get("etag"), - "labels": self.response.get("labels"), "name": self.response.get("name"), - "network": self.response.get("network"), - "privateServiceConnectConfig": PrivateServiceConnectConfig().from_response( - self.response.get("privateServiceConnectConfig", {}) - ), "publicEndpointDomainName": self.response.get("publicEndpointDomainName"), - "publicEndpointEnabled": self.response.get("publicEndpointEnabled"), "updateTime": self.response.get("updateTime"), } @@ -300,26 +266,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( state=dict( type="str", @@ -383,7 +333,7 @@ def main(): type="str", ), ), - mutually_exclusive=[["network", "private_service_connect_config"]], + mutually_exclusive=[("network", "private_service_connect_config")], ) if not module.params["scopes"]: @@ -391,17 +341,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{region}/indexEndpoints", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{region}/indexEndpoints", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/indexEndpoints", "async_uri": "{op_id}", @@ -409,7 +353,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/indexEndpoints/{name}", "async_uri": "{op_id}", @@ -417,7 +361,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/indexEndpoints/{name}", "async_uri": "", @@ -425,7 +369,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/indexEndpoints/{name}", "async_uri": "", @@ -433,36 +377,41 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#indexEndpoint") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI( + request, module=module, product="VertexAI", kind="vertexai#indexEndpoint", op_configs=op_configs + ) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload # --------- BEGIN pre-read custom code --------- - # for this module, we're hitting the list endpoint and filtering on display name - read_uri = op_configs.base_url.uri + "?filter=displayName=" + params.get("display_name") + # for this module, we're hitting the list endpoint and filtering on display name and rebuild the link + read_link = resource.build_link("list") + "?filter=displayName=" + request.get("display_name") # --------- END pre-read custom code --------- - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) # --------- BEGIN post-read custom code --------- # if there are existing indexes, the call would have returned a list - if not gcp.empty(existing_obj): + if not gcp_v2.empty(existing_obj): for idx in existing_obj.get("indexEndpoints", []): - if idx.get("displayName") == params.get("display_name"): + if idx.get("displayName") == request.get("display_name"): existing_obj = idx break @@ -471,41 +420,37 @@ def main(): if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -515,32 +460,35 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload # --------- BEGIN pre-delete custom code --------- # need to set to the required parameter "name" to the existing resource name - params["name"] = existing_obj["name"].split("/")[-1] + resource.url_params["name"] = existing_obj["name"].split("/")[-1] # --------- END pre-delete custom code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -548,37 +496,37 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload # --------- BEGIN pre-update custom code --------- # need to set to the required parameter "name" to the existing resource name - params["name"] = existing_obj["name"].split("/")[-1] + resource.url_params["name"] = existing_obj["name"].split("/")[-1] # finally, need to build the updateMask for the fields in our module - update_uri += f"?updateMask={','.join(resource.dot_fields())}" + update_link = resource.build_link("update") + "?updateMask=" + ",".join(resource.dot_fields()) # --------- END pre-update custom code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -588,6 +536,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_index_endpoint_deployed_index.py b/plugins/modules/gcp_vertexai_index_endpoint_deployed_index.py index 31cf80e39..e51a3d4d4 100644 --- a/plugins/modules/gcp_vertexai_index_endpoint_deployed_index.py +++ b/plugins/modules/gcp_vertexai_index_endpoint_deployed_index.py @@ -78,6 +78,7 @@ machine_spec: description: - The minimum number of replicas this DeployedModel will be always deployed on. + - This property is immutable, to change it, you must delete and recreate the resource. required: true suboptions: machine_type: @@ -86,6 +87,7 @@ - See the [list of machine types supported for prediction](https://cloud.google.com/vertex-ai/docs/predictions/configure-compute#machine-types) See the [list of machine types supported for custom training](https://cloud.google.com/vertex-ai/docs/training/configure-compute#machine-types). - For [DeployedModel](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.endpoints#DeployedModel) this field is optional, and the default value is n1-standard-2. - For [BatchPredictionJob](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.batchPredictionJobs#BatchPredictionJob) or as part of [WorkerPoolSpec](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/CustomJobSpec#WorkerPoolSpec) this field is required. + - This property is immutable, to change it, you must delete and recreate the resource. type: str type: dict max_replica_count: @@ -103,6 +105,7 @@ deployed_index_auth_config: description: - If set, the authentication is enabled for the private endpoint. + - This property is immutable, to change it, you must delete and recreate the resource. suboptions: auth_provider: description: @@ -112,6 +115,7 @@ description: - A list of allowed JWT issuers. - 'Each entry must be a valid Google service account, in the following format: service-account-name@project-id.iam.gserviceaccount.com.' + - This property is immutable, to change it, you must delete and recreate the resource. elements: str type: list audiences: @@ -119,6 +123,7 @@ - The list of JWT audiences. - that are allowed to access. - A JWT containing any of these audiences will be accepted. + - This property is immutable, to change it, you must delete and recreate the resource. elements: str type: list type: dict @@ -128,6 +133,7 @@ - The user specified ID of the DeployedIndex. - The ID can be up to 128 characters long and must start with a letter and only contain letters, numbers, and underscores. - The ID must be unique within the project it is created in. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str deployment_group: @@ -140,20 +146,24 @@ - 'Also, one deployment_group (except ''default'') can only be used with the same reserved_ip_ranges which means if the deployment_group has been used with reserved_ip_ranges: [a, b, c], using it with [a, b] or [d, e] is disallowed.' - '[See the official documentation here](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.indexEndpoints#DeployedIndex.FIELDS.deployment_group).' - 'Note: we only support up to 5 deployment groups (not including ''default'').' + - This property is immutable, to change it, you must delete and recreate the resource. type: str display_name: description: - The display name of the Index. - The name can be up to 128 characters long and can consist of any UTF-8 characters. + - This property is immutable, to change it, you must delete and recreate the resource. type: str enable_access_logging: default: false description: - If true, private endpoint's access logs are sent to Cloud Logging. + - This property is immutable, to change it, you must delete and recreate the resource. type: bool index: description: - The name of the Index this is the deployment of. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str index_endpoint: @@ -163,12 +173,14 @@ - This field is a reference to a IndexEndpoint resource in GCP. - 'It can be specified in two ways: First, you can place a dictionary with key ''name'' matching your resource.' - 'Alternatively, you can add `register: name-of-resource` to a IndexEndpoint task and then set this field to `{{ name-of-resource }}`.' + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: dict region: description: - The region of the index. - eg us-central1. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str reserved_ip_ranges: @@ -178,6 +190,7 @@ - Otherwise, the index might be deployed to any ip ranges under the provided VPC network. - 'The value should be the name of the address (https://cloud.google.com/compute/docs/reference/rest/v1/addresses) Example: [''vertex-ai-ip-range''].' - For more information about subnets and network IP ranges, please see https://cloud.google.com/vpc/docs/subnets#manually_created_subnet_ip_ranges. + - This property is immutable, to change it, you must delete and recreate the resource. elements: str type: list state: @@ -214,55 +227,6 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" - -################################################################################ - -- name: Create index endpoint deployed index with dedicated resources - google.cloud.gcp_vertexai_index_endpoint_deployed_index: - state: present - display_name: "{{ resource_name }}" - deployed_index_id: "{{ resource_name | regex_replace('-', '_') }}" - region: us-central1 - index: "{{ _myidx.name }}" - index_endpoint: "{{ _myidxep.name }}" - enable_access_logging: false - deployed_index_auth_config: - auth_provider: - audiences: - - 123-myapp - allowed_issuers: - - mysa@myproject.iam.gserviceaccount.com - dedicated_resources: - min_replica_count: 1 - max_replica_count: 3 - machine_spec: - machine_type: e2-standard-2 - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - -################################################################################ - -- name: Create index endpoint deployed index with automatic resources - google.cloud.gcp_vertexai_index_endpoint_deployed_index: - state: present - display_name: "{{ resource_name }}" - deployed_index_id: "{{ resource_name | regex_replace('-', '_') }}" - region: us-central1 - index: "{{ _myidx.name }}" - index_endpoint: "{{ _myidxep.name }}" - enable_access_logging: false - deployed_index_auth_config: - auth_provider: - audiences: - - 123-myapp - allowed_issuers: - - mysa@myproject.iam.gserviceaccount.com - automatic_resources: - max_replica_count: 3 - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" """ # noqa: E501 RETURN = r""" @@ -342,8 +306,7 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports import copy @@ -351,86 +314,50 @@ # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - params["index_endpoint"] = gcp.replace_resource_dict(module_params["index_endpoint"], "name") - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class AutomaticResources(gcp.Resource): +class AutomaticResources(gcp_v2.Resource): def _request(self): return { "maxReplicaCount": self.request.get("max_replica_count"), "minReplicaCount": self.request.get("min_replica_count"), } - def _response(self): - return { - "maxReplicaCount": self.response.get("maxReplicaCount"), - "minReplicaCount": self.response.get("minReplicaCount"), - } - -class DedicatedResources(gcp.Resource): +class DedicatedResources(gcp_v2.Resource): def _request(self): return { - "machineSpec": gcp.remove_empties( + "machineSpec": gcp_v2.remove_empties( DedicatedResourcesMachineSpec(self.request.get("machine_spec", {})).to_request() ), # remove empty values "maxReplicaCount": self.request.get("max_replica_count"), "minReplicaCount": self.request.get("min_replica_count"), } - def _response(self): - return { - "machineSpec": DedicatedResourcesMachineSpec().from_response(self.response.get("machineSpec", {})), - "maxReplicaCount": self.response.get("maxReplicaCount"), - "minReplicaCount": self.response.get("minReplicaCount"), - } - -class DedicatedResourcesMachineSpec(gcp.Resource): +class DedicatedResourcesMachineSpec(gcp_v2.Resource): def _request(self): return { "machineType": self.request.get("machine_type"), } - def _response(self): - return { - "machineType": self.response.get("machineType"), - } - -class DeployedIndexAuthConfig(gcp.Resource): +class DeployedIndexAuthConfig(gcp_v2.Resource): def _request(self): return { - "authProvider": gcp.remove_empties( + "authProvider": gcp_v2.remove_empties( DeployedIndexAuthConfigAuthProvider(self.request.get("auth_provider", {})).to_request() ), # remove empty values } - def _response(self): - return { - "authProvider": DeployedIndexAuthConfigAuthProvider().from_response(self.response.get("authProvider", {})), - } - -class DeployedIndexAuthConfigAuthProvider(gcp.Resource): +class DeployedIndexAuthConfigAuthProvider(gcp_v2.Resource): def _request(self): return { "allowedIssuers": self.request.get("allowed_issuers"), "audiences": self.request.get("audiences"), } - def _response(self): - return { - "allowedIssuers": self.response.get("allowedIssuers"), - "audiences": self.response.get("audiences"), - } - -class PrivateEndpoints(gcp.Resource): +class PrivateEndpoints(gcp_v2.Resource): def _response(self): return { "matchGrpcAddress": self.response.get("matchGrpcAddress"), @@ -442,7 +369,7 @@ def _response(self): } -class PrivateEndpointsPscAutomatedEndpoint(gcp.Resource): +class PrivateEndpointsPscAutomatedEndpoint(gcp_v2.Resource): def _response(self): return { "matchAddress": self.response.get("matchAddress"), @@ -451,16 +378,16 @@ def _response(self): } -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { - "automaticResources": gcp.remove_empties( + "automaticResources": gcp_v2.remove_empties( AutomaticResources(self.request.get("automatic_resources", {})).to_request() ), # remove empty values - "dedicatedResources": gcp.remove_empties( + "dedicatedResources": gcp_v2.remove_empties( DedicatedResources(self.request.get("dedicated_resources", {})).to_request() ), # remove empty values - "deployedIndexAuthConfig": gcp.remove_empties( + "deployedIndexAuthConfig": gcp_v2.remove_empties( DeployedIndexAuthConfig(self.request.get("deployed_index_auth_config", {})).to_request() ), # remove empty values "deployedIndexId": self.request.get("deployed_index_id"), @@ -473,84 +400,67 @@ def _request(self): def _response(self): return { - "automaticResources": AutomaticResources().from_response(self.response.get("automaticResources", {})), "createTime": self.response.get("createTime"), - "dedicatedResources": DedicatedResources().from_response(self.response.get("dedicatedResources", {})), - "deployedIndexAuthConfig": DeployedIndexAuthConfig().from_response( - self.response.get("deployedIndexAuthConfig", {}) - ), - "deployedIndexId": self.response.get("deployedIndexId"), - "deploymentGroup": self.response.get("deploymentGroup"), - "displayName": self.response.get("displayName"), - "enableAccessLogging": self.response.get("enableAccessLogging"), - "index": self.response.get("index"), "indexSyncTime": self.response.get("indexSyncTime"), "name": self.response.get("name"), "privateEndpoints": PrivateEndpoints().from_response(self.response.get("privateEndpoints", {})), - "reservedIpRanges": [str(item) for item in (self.response.get("reservedIpRanges") or [])], } + def encode(self, request): + "Custom encoder function, mutates the request object before it is sent to the API." + + # --------- BEGIN custom encoder code --------- + r = {} + action = getattr(self, "_action", "read") + self.debug(func="encoder", action=action, request=request) + if action == "read": # for read, we just return the original object + return request + elif action == "create": # encode for create + # deployIndex requires the deployedIndex in a nested object + # https://docs.cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.indexEndpoints/deployIndex#request-body + t = copy.deepcopy(request) + t["id"] = t.pop("deployedIndexId") + r = {"deployedIndex": t} + elif action == "update": # encode for update + # mutateDeployedIndex requires the deployedIndex definition at top-level + # https://docs.cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.indexEndpoints/mutateDeployedIndex#request-body + r = copy.deepcopy(request) + r["id"] = r.pop("deployedIndexId") + else: # encode for delete + # normally, deletes are empty but undeployIndex requires a single parameter, the deployedIndexId + # https://docs.cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.indexEndpoints/undeployIndex#request-body + r = {"deployedIndexId": self._request().get("deployedIndexId")} + + return r + + # --------- END custom encoder code --------- + + def decode(self, response): + "Custom decoder function, mutates the response object before it is returned to the module caller." + + # --------- BEGIN custom decoder code --------- + r = copy.deepcopy(response) + deployed_index = r.pop("deployedIndex", None) + if deployed_index is not None: + r["deployedIndexId"] = deployed_index.pop("id", None) + elif r.get("id") is not None: + r["deployedIndexId"] = r.pop("id") + else: + pass + return r + + # --------- END custom decoder code --------- + ################################################################################ # Main ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - # --------- BEGIN custom encoder code --------- - r = {} - action = getattr(self, "_action", "read") - self.debug(func="encoder", action=action, obj=obj) - if action == "read": # for read, we just return the original object - r = copy.deepcopy(obj) - elif action == "create": # encode for create - # deployIndex requires the deployedIndex in a nested object - # https://docs.cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.indexEndpoints/deployIndex#request-body - t = copy.deepcopy(obj) - t["id"] = t.pop("deployedIndexId") - r = {"deployedIndex": t} - elif action == "update": # encode for update - # mutateDeployedIndex requires the deployedIndex definition at top-level - # https://docs.cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.indexEndpoints/mutateDeployedIndex#request-body - r = copy.deepcopy(obj) - r["id"] = r.pop("deployedIndexId") - else: # encode for delete - # normally, deletes are empty but undeployIndex requires a single parameter, the deployedIndexId - # https://docs.cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.indexEndpoints/undeployIndex#request-body - r = {"deployedIndexId": self._request().get("deployedIndexId")} - - return r - - # --------- END custom encoder code --------- - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - # --------- BEGIN custom decoder code --------- - r = copy.deepcopy(obj) - deployed_index = r.pop("deployedIndex", None) - if deployed_index is not None: - r["deployedIndexId"] = deployed_index.pop("id", None) - elif r.get("id") is not None: - r["deployedIndexId"] = r.pop("id") - else: - pass - return r - - # --------- END custom decoder code --------- - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( state=dict( type="str", @@ -648,15 +558,14 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{"uri": "{index_endpoint}", "async_uri": "", "verb": "GET", "timeout_minutes": 0} - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="{index_endpoint}", + configs={ + "create": gcp_v2.ResourceOpConfig( **{"uri": "{index_endpoint}:deployIndex", "async_uri": "{op_id}", "verb": "POST", "timeout_minutes": 45} ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "{index_endpoint}:undeployIndex", "async_uri": "{op_id}", @@ -664,10 +573,10 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{"uri": "{index_endpoint}", "async_uri": "", "verb": "GET", "timeout_minutes": 0} ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "{index_endpoint}:mutateDeployedIndex", "async_uri": "{op_id}", @@ -675,85 +584,88 @@ def main(): "timeout_minutes": 45, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#indexEndpointDeployedIndex") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI( + request, module=module, product="VertexAI", kind="vertexai#indexEndpointDeployedIndex", op_configs=op_configs + ) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + resource.url_params["index_endpoint"] = gcp_v2.resource_ref(module.params["index_endpoint"], "name") + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload # --------- BEGIN pre-read custom code --------- resource._action = "read" # --------- END pre-read custom code --------- - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) # --------- BEGIN post-read custom code --------- # We need an existing index endpoint to work with - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): module.fail_json(msg="The referenced index endpoint was not found") else: for didx in existing_obj.get("deployedIndexes", []): - if didx.get("id") == params.get("deployed_index_id"): + if didx.get("id") == request.get("deployed_index_id"): existing_obj = didx break else: # deployedIndexes was empty or no match found existing_obj = {} + # --------- END post-read custom code --------- if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- + create_link: str = "" # give it a chance for pre-create to overload # --------- BEGIN pre-create custom code --------- resource._action = "create" # --------- END pre-create custom code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -763,31 +675,34 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload # --------- BEGIN pre-delete custom code --------- resource._action = "delete" # --------- END pre-delete custom code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -795,33 +710,33 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload # --------- BEGIN pre-update custom code --------- resource._action = "update" # --------- END pre-update custom code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -831,6 +746,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_metadata_store.py b/plugins/modules/gcp_vertexai_metadata_store.py index 059287fd1..619ba82da 100644 --- a/plugins/modules/gcp_vertexai_metadata_store.py +++ b/plugins/modules/gcp_vertexai_metadata_store.py @@ -45,17 +45,20 @@ description: description: - Description of the MetadataStore. + - This property is immutable, to change it, you must delete and recreate the resource. type: str encryption_spec: description: - Customer-managed encryption key spec for a MetadataStore. - If set, this MetadataStore and all sub-resources of this MetadataStore will be secured by this key. + - This property is immutable, to change it, you must delete and recreate the resource. suboptions: kms_key_name: description: - The Cloud KMS resource identifier of the customer managed encryption key used to protect a resource. - 'Has the form: projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key.' - The key needs to be in the same region as where the resource is created. + - This property is immutable, to change it, you must delete and recreate the resource. type: str type: dict name: @@ -63,11 +66,13 @@ - The name of the MetadataStore. - This value may be up to 60 characters, and valid characters are [a-z0-9_]. - The first character cannot be a number. + - This property is immutable, to change it, you must delete and recreate the resource. type: str region: description: - The region of the Metadata Store. - eg us-central1. + - This property is immutable, to change it, you must delete and recreate the resource. type: str state: choices: @@ -137,37 +142,24 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports - # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1beta1/" + uri).format(**params) - - -class EncryptionSpec(gcp.Resource): +class EncryptionSpec(gcp_v2.Resource): def _request(self): return { "kmsKeyName": self.request.get("kms_key_name"), } - def _response(self): - return { - "kmsKeyName": self.response.get("kmsKeyName"), - } - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { "description": self.request.get("description"), - "encryptionSpec": gcp.remove_empties( + "encryptionSpec": gcp_v2.remove_empties( EncryptionSpec(self.request.get("encryption_spec", {})).to_request() ), # remove empty values } @@ -175,8 +167,6 @@ def _request(self): def _response(self): return { "createTime": self.response.get("createTime"), - "description": self.response.get("description"), - "encryptionSpec": EncryptionSpec().from_response(self.response.get("encryptionSpec", {})), "updateTime": self.response.get("updateTime"), } @@ -186,26 +176,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( name=dict( type="str", @@ -238,17 +212,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{region}/metadataStores", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1beta1/", + base_uri="projects/{project}/locations/{region}/metadataStores", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/metadataStores?metadataStoreId={name}", "async_uri": "{op_id}", @@ -256,7 +224,7 @@ def main(): "timeout_minutes": 40, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/metadataStores/{name}", "async_uri": "{op_id}", @@ -264,7 +232,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/metadataStores/{name}", "async_uri": "", @@ -272,7 +240,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/metadataStores/{name}", "async_uri": "{op_id}", @@ -280,63 +248,64 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#metadataStore") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI( + request, module=module, product="VertexAI", kind="vertexai#metadataStore", op_configs=op_configs + ) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + read_link: str = "" # give it a chance for pre-read to overload + + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -346,27 +315,30 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -374,32 +346,33 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload # --------- BEGIN pre-update custom code --------- module.fail_json(msg="Metadata stores API does not support updates") + # --------- END pre-update custom code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -409,6 +382,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_rag_engine_config.py b/plugins/modules/gcp_vertexai_rag_engine_config.py index 657de039c..914f835c3 100644 --- a/plugins/modules/gcp_vertexai_rag_engine_config.py +++ b/plugins/modules/gcp_vertexai_rag_engine_config.py @@ -64,6 +64,7 @@ description: - The region of the RagEngineConfig. - eg us-central1. + - This property is immutable, to change it, you must delete and recreate the resource. type: str state: choices: @@ -134,21 +135,13 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports - # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { "ragManagedDbConfig": self.request.get("rag_managed_db_config"), @@ -157,48 +150,41 @@ def _request(self): def _response(self): return { "name": self.response.get("name"), - "ragManagedDbConfig": self.response.get("ragManagedDbConfig"), } + def encode(self, request): + "Custom encoder function, mutates the request object before it is sent to the API." -################################################################################ -# Main -################################################################################ + # --------- BEGIN custom encoder code --------- + tier = request.get("ragManagedDbConfig") + if getattr(self, "_state", "present") == "absent": + tier = "unprovisioned" + return { + "name": "projects/{project}/locations/{region}/ragEngineConfig".format(**self.url_params), + "ragManagedDbConfig": {tier.lower(): {}}, + } + # --------- END custom encoder code --------- -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - # --------- BEGIN custom encoder code --------- - tier = obj.get("ragManagedDbConfig") - if getattr(self, "_state", "present") == "absent": - tier = "unprovisioned" - return { - "name": "projects/{project}/locations/{region}/ragEngineConfig".format(**self.module.params), - "ragManagedDbConfig": {tier.lower(): {}}, - } + def decode(self, response): + "Custom decoder function, mutates the response object before it is returned to the module caller." - # --------- END custom encoder code --------- + # --------- BEGIN custom decoder code --------- + tier = next(iter(response["ragManagedDbConfig"])) + return {"name": response.get("name"), "ragManagedDbConfig": {tier.lower(): {}}} + # --------- END custom decoder code --------- -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - # --------- BEGIN custom decoder code --------- - tier = next(iter(obj["ragManagedDbConfig"])) - return {"name": obj.get("name"), "ragManagedDbConfig": {tier.lower(): {}}} - # --------- END custom decoder code --------- +################################################################################ +# Main +################################################################################ def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( state=dict( type="str", @@ -221,17 +207,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{region}/ragEngineConfig", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{region}/ragEngineConfig", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/ragEngineConfig", "async_uri": "{op_id}", @@ -239,7 +219,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/ragEngineConfig", "async_uri": "{op_id}", @@ -247,7 +227,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/ragEngineConfig", "async_uri": "", @@ -255,7 +235,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/ragEngineConfig", "async_uri": "{op_id}", @@ -263,63 +243,64 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#ragEngineConfig") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI( + request, module=module, product="VertexAI", kind="vertexai#ragEngineConfig", op_configs=op_configs + ) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload + + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -329,27 +310,30 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -357,29 +341,29 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -389,6 +373,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_reasoning_engine.py b/plugins/modules/gcp_vertexai_reasoning_engine.py index f72e704c3..979af56b0 100644 --- a/plugins/modules/gcp_vertexai_reasoning_engine.py +++ b/plugins/modules/gcp_vertexai_reasoning_engine.py @@ -55,6 +55,7 @@ description: - Customer-managed encryption key spec for a ReasoningEngine. - If set, this ReasoningEngine and all sub-resources of this ReasoningEngine will be secured by this key. + - This property is immutable, to change it, you must delete and recreate the resource. suboptions: kms_key_name: description: @@ -68,6 +69,7 @@ description: - The region of the reasoning engine. - eg us-central1. + - This property is immutable, to change it, you must delete and recreate the resource. type: str spec: description: @@ -236,6 +238,32 @@ description: - Specification for deploying from source code. suboptions: + developer_connect_source: + description: + - Specification for source code to be fetched from a Git repository managed through the Developer Connect service. + suboptions: + config: + description: + - The Developer Connect configuration that defines the specific repository, revision, and directory to use as the source code root. + required: true + suboptions: + dir: + description: + - Directory, relative to the source root, in which to run the build. + required: true + type: str + git_repository_link: + description: + - The Developer Connect Git repository link, formatted as projects/*/locations/*/connections/*/gitRepositoryLink/*. + required: true + type: str + revision: + description: + - The revision to fetch from the Git repository such as a branch, a tag, a commit SHA, or any Git ref. + required: true + type: str + type: dict + type: dict inline_source: description: - Source code is provided directly in the request. @@ -292,18 +320,6 @@ """ # noqa: E501 EXAMPLES = r""" -- name: Create Basic Reasoning Engine - google.cloud.gcp_vertexai_reasoning_engine: - state: present - display_name: basic-reasoning-engine - description: A Basic Reasoning Engine - region: us-central1 - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - -################################################################################ - - name: Create Source Based Deployment Reasoning Engine google.cloud.gcp_vertexai_reasoning_engine: state: present @@ -355,68 +371,45 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 # BEGIN Custom imports - # END Custom imports -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class EncryptionSpec(gcp.Resource): +class EncryptionSpec(gcp_v2.Resource): def _request(self): return { "kmsKeyName": self.request.get("kms_key_name"), } - def _response(self): - return { - "kmsKeyName": self.response.get("kmsKeyName"), - } - -class Spec(gcp.Resource): +class Spec(gcp_v2.Resource): def _request(self): return { "agentFramework": self.request.get("agent_framework"), "classMethods": self.request.get("class_methods"), - "deploymentSpec": gcp.remove_empties( + "deploymentSpec": gcp_v2.remove_empties( SpecDeploymentSpec(self.request.get("deployment_spec", {})).to_request() ), # remove empty values - "packageSpec": gcp.remove_empties( + "packageSpec": gcp_v2.remove_empties( SpecPackageSpec(self.request.get("package_spec", {})).to_request() ), # remove empty values "serviceAccount": self.request.get("service_account"), - "sourceCodeSpec": gcp.remove_empties( + "sourceCodeSpec": gcp_v2.remove_empties( SpecSourceCodeSpec(self.request.get("source_code_spec", {})).to_request() ), # remove empty values } - def _response(self): - return { - "agentFramework": self.response.get("agentFramework"), - "classMethods": self.response.get("classMethods"), - "deploymentSpec": SpecDeploymentSpec().from_response(self.response.get("deploymentSpec", {})), - "packageSpec": SpecPackageSpec().from_response(self.response.get("packageSpec", {})), - "serviceAccount": self.response.get("serviceAccount"), - "sourceCodeSpec": SpecSourceCodeSpec().from_response(self.response.get("sourceCodeSpec", {})), - } - -class SpecDeploymentSpec(gcp.Resource): +class SpecDeploymentSpec(gcp_v2.Resource): def _request(self): return { "containerConcurrency": self.request.get("container_concurrency"), "env": [SpecDeploymentSpecEnv(item).to_request() for item in (self.request.get("env") or [])], "maxInstances": self.request.get("max_instances"), "minInstances": self.request.get("min_instances"), - "pscInterfaceConfig": gcp.remove_empties( + "pscInterfaceConfig": gcp_v2.remove_empties( SpecDeploymentSpecPscInterfaceConfig(self.request.get("psc_interface_config", {})).to_request() ), # remove empty values "resourceLimits": self.request.get("resource_limits"), @@ -425,37 +418,16 @@ def _request(self): ], } - def _response(self): - return { - "containerConcurrency": self.response.get("containerConcurrency"), - "env": [SpecDeploymentSpecEnv().from_response(item) for item in (self.response.get("env") or [])], - "maxInstances": self.response.get("maxInstances"), - "minInstances": self.response.get("minInstances"), - "pscInterfaceConfig": SpecDeploymentSpecPscInterfaceConfig().from_response( - self.response.get("pscInterfaceConfig", {}) - ), - "resourceLimits": self.response.get("resourceLimits"), - "secretEnv": [ - SpecDeploymentSpecSecretEnv().from_response(item) for item in (self.response.get("secretEnv") or []) - ], - } - -class SpecDeploymentSpecEnv(gcp.Resource): +class SpecDeploymentSpecEnv(gcp_v2.Resource): def _request(self): return { "name": self.request.get("name"), "value": self.request.get("value"), } - def _response(self): - return { - "name": self.response.get("name"), - "value": self.response.get("value"), - } - -class SpecDeploymentSpecPscInterfaceConfig(gcp.Resource): +class SpecDeploymentSpecPscInterfaceConfig(gcp_v2.Resource): def _request(self): return { "dnsPeeringConfigs": [ @@ -465,17 +437,8 @@ def _request(self): "networkAttachment": self.request.get("network_attachment"), } - def _response(self): - return { - "dnsPeeringConfigs": [ - SpecDeploymentSpecPscInterfaceConfigDnsPeeringConfig().from_response(item) - for item in (self.response.get("dnsPeeringConfigs") or []) - ], - "networkAttachment": self.response.get("networkAttachment"), - } - -class SpecDeploymentSpecPscInterfaceConfigDnsPeeringConfig(gcp.Resource): +class SpecDeploymentSpecPscInterfaceConfigDnsPeeringConfig(gcp_v2.Resource): def _request(self): return { "domain": self.request.get("domain"), @@ -483,45 +446,26 @@ def _request(self): "targetProject": self.request.get("target_project"), } - def _response(self): - return { - "domain": self.response.get("domain"), - "targetNetwork": self.response.get("targetNetwork"), - "targetProject": self.response.get("targetProject"), - } - -class SpecDeploymentSpecSecretEnv(gcp.Resource): +class SpecDeploymentSpecSecretEnv(gcp_v2.Resource): def _request(self): return { "name": self.request.get("name"), - "secretRef": gcp.remove_empties( + "secretRef": gcp_v2.remove_empties( SpecDeploymentSpecSecretEnvSecretRef(self.request.get("secret_ref", {})).to_request() ), # remove empty values } - def _response(self): - return { - "name": self.response.get("name"), - "secretRef": SpecDeploymentSpecSecretEnvSecretRef().from_response(self.response.get("secretRef", {})), - } - -class SpecDeploymentSpecSecretEnvSecretRef(gcp.Resource): +class SpecDeploymentSpecSecretEnvSecretRef(gcp_v2.Resource): def _request(self): return { "secret": self.request.get("secret"), "version": self.request.get("version"), } - def _response(self): - return { - "secret": self.response.get("secret"), - "version": self.response.get("version"), - } - -class SpecPackageSpec(gcp.Resource): +class SpecPackageSpec(gcp_v2.Resource): def _request(self): return { "dependencyFilesGcsUri": self.request.get("dependency_files_gcs_uri"), @@ -530,46 +474,48 @@ def _request(self): "requirementsGcsUri": self.request.get("requirements_gcs_uri"), } - def _response(self): - return { - "dependencyFilesGcsUri": self.response.get("dependencyFilesGcsUri"), - "pickleObjectGcsUri": self.response.get("pickleObjectGcsUri"), - "pythonVersion": self.response.get("pythonVersion"), - "requirementsGcsUri": self.response.get("requirementsGcsUri"), - } - -class SpecSourceCodeSpec(gcp.Resource): +class SpecSourceCodeSpec(gcp_v2.Resource): def _request(self): return { - "inlineSource": gcp.remove_empties( + "developerConnectSource": gcp_v2.remove_empties( + SpecSourceCodeSpecDeveloperConnectSource(self.request.get("developer_connect_source", {})).to_request() + ), # remove empty values + "inlineSource": gcp_v2.remove_empties( SpecSourceCodeSpecInlineSource(self.request.get("inline_source", {})).to_request() ), # remove empty values - "pythonSpec": gcp.remove_empties( + "pythonSpec": gcp_v2.remove_empties( SpecSourceCodeSpecPythonSpec(self.request.get("python_spec", {})).to_request() ), # remove empty values } - def _response(self): + +class SpecSourceCodeSpecDeveloperConnectSource(gcp_v2.Resource): + def _request(self): return { - "inlineSource": SpecSourceCodeSpecInlineSource().from_response(self.response.get("inlineSource", {})), - "pythonSpec": SpecSourceCodeSpecPythonSpec().from_response(self.response.get("pythonSpec", {})), + "config": gcp_v2.remove_empties( + SpecSourceCodeSpecDeveloperConnectSourceConfig(self.request.get("config", {})).to_request() + ), # remove empty values } -class SpecSourceCodeSpecInlineSource(gcp.Resource): +class SpecSourceCodeSpecDeveloperConnectSourceConfig(gcp_v2.Resource): def _request(self): return { - "sourceArchive": self.request.get("source_archive"), + "dir": self.request.get("dir"), + "gitRepositoryLink": self.request.get("git_repository_link"), + "revision": self.request.get("revision"), } - def _response(self): + +class SpecSourceCodeSpecInlineSource(gcp_v2.Resource): + def _request(self): return { - "sourceArchive": self.response.get("sourceArchive"), + "sourceArchive": self.request.get("source_archive"), } -class SpecSourceCodeSpecPythonSpec(gcp.Resource): +class SpecSourceCodeSpecPythonSpec(gcp_v2.Resource): def _request(self): return { "entrypointModule": self.request.get("entrypoint_module"), @@ -578,34 +524,22 @@ def _request(self): "version": self.request.get("version"), } - def _response(self): - return { - "entrypointModule": self.response.get("entrypointModule"), - "entrypointObject": self.response.get("entrypointObject"), - "requirementsFile": self.response.get("requirementsFile"), - "version": self.response.get("version"), - } - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { "description": self.request.get("description"), "displayName": self.request.get("display_name"), - "encryptionSpec": gcp.remove_empties( + "encryptionSpec": gcp_v2.remove_empties( EncryptionSpec(self.request.get("encryption_spec", {})).to_request() ), # remove empty values - "spec": gcp.remove_empties(Spec(self.request.get("spec", {})).to_request()), # remove empty values + "spec": gcp_v2.remove_empties(Spec(self.request.get("spec", {})).to_request()), # remove empty values } def _response(self): return { "createTime": self.response.get("createTime"), - "description": self.response.get("description"), - "displayName": self.response.get("displayName"), - "encryptionSpec": EncryptionSpec().from_response(self.response.get("encryptionSpec", {})), "name": self.response.get("name"), - "spec": Spec().from_response(self.response.get("spec", {})), "updateTime": self.response.get("updateTime"), } @@ -615,26 +549,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( state=dict( type="str", @@ -776,6 +694,29 @@ def main(): source_code_spec=dict( type="dict", options=dict( + developer_connect_source=dict( + type="dict", + options=dict( + config=dict( + type="dict", + required=True, + options=dict( + dir=dict( + type="str", + required=True, + ), + git_repository_link=dict( + type="str", + required=True, + ), + revision=dict( + type="str", + required=True, + ), + ), + ) + ), + ), inline_source=dict( type="dict", options=dict( @@ -813,17 +754,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{region}/reasoningEngines", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{region}/reasoningEngines", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/reasoningEngines", "async_uri": "{op_id}", @@ -831,7 +766,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/reasoningEngines/{name}", "async_uri": "{op_id}", @@ -839,7 +774,7 @@ def main(): "timeout_minutes": 60, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/reasoningEngines/{name}", "async_uri": "", @@ -847,7 +782,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/reasoningEngines/{name}", "async_uri": "{op_id}", @@ -855,35 +790,40 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#reasoningEngine") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI( + request, module=module, product="VertexAI", kind="vertexai#reasoningEngine", op_configs=op_configs + ) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload # --------- BEGIN pre-read custom code --------- - # for this module, we're hitting the list endpoint and filtering on display name - read_uri = op_configs.base_url.uri + "?filter=displayName=" + params.get("display_name") + # for this module, we're hitting the list endpoint and filtering on display name and rebuild the link + read_link = resource.build_link("list") + "?filter=displayName=" + request.get("display_name") # --------- END pre-read custom code --------- - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) # --------- BEGIN post-read custom code --------- - if not gcp.empty(existing_obj): + if not gcp_v2.empty(existing_obj): for rengine in existing_obj.get("reasoningEngines", []): - if rengine.get("displayName") == params.get("display_name"): + if rengine.get("displayName") == request.get("display_name"): existing_obj = rengine break @@ -892,41 +832,37 @@ def main(): if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -936,32 +872,35 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- + delete_link: str = "" # give it a chance for pre-delete to overload # --------- BEGIN pre-delete custom code --------- # need to set to the required parameter "name" to the existing resource name - params["name"] = existing_obj["name"].split("/")[-1] + resource.url_params["name"] = existing_obj["name"].split("/")[-1] # --------- END pre-delete custom code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -969,37 +908,37 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- + update_link: str = "" # give it a chance for pre-update to overload # --------- BEGIN pre-update custom code --------- # need to set to the required parameter "name" to the existing resource name - params["name"] = existing_obj["name"].split("/")[-1] + resource.url_params["name"] = existing_obj["name"].split("/")[-1] # finally, need to build the updateMask for the fields in our module - update_uri += "?updateMask=" + ",".join(resource.dot_fields()) + update_link = resource.build_link("update") + "?updateMask=" + ",".join(resource.dot_fields()) # --------- END pre-update custom code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -1009,6 +948,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/plugins/modules/gcp_vertexai_tensorboard.py b/plugins/modules/gcp_vertexai_tensorboard.py index 4c75fcb60..810641e4e 100644 --- a/plugins/modules/gcp_vertexai_tensorboard.py +++ b/plugins/modules/gcp_vertexai_tensorboard.py @@ -55,23 +55,27 @@ description: - Customer-managed encryption key spec for a Tensorboard. - If set, this Tensorboard and all sub-resources of this Tensorboard will be secured by this key. + - This property is immutable, to change it, you must delete and recreate the resource. suboptions: kms_key_name: description: - The Cloud KMS resource identifier of the customer managed encryption key used to protect a resource. - 'Has the form: projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key.' - The key needs to be in the same region as where the resource is created. + - This property is immutable, to change it, you must delete and recreate the resource. required: true type: str type: dict labels: description: - The labels with user-defined metadata to organize your Tensorboards. + - '**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.' type: dict region: description: - The region of the tensorboard. - eg us-central1. + - This property is immutable, to change it, you must delete and recreate the resource. type: str state: choices: @@ -144,34 +148,22 @@ # Imports ################################################################################ -from ansible_collections.google.cloud.plugins.module_utils import gcp_utils as gcp -import types +from ansible_collections.google.cloud.plugins.module_utils import gcp_v2 -def build_link(module_params, uri): - params = module_params.copy() - - return ("https://{region}-aiplatform.googleapis.com/v1/" + uri).format(**params) - - -class EncryptionSpec(gcp.Resource): +class EncryptionSpec(gcp_v2.Resource): def _request(self): return { "kmsKeyName": self.request.get("kms_key_name"), } - def _response(self): - return { - "kmsKeyName": self.response.get("kmsKeyName"), - } - -class VertexAI(gcp.Resource): +class VertexAI(gcp_v2.Resource): def _request(self): return { "description": self.request.get("description"), "displayName": self.request.get("display_name"), - "encryptionSpec": gcp.remove_empties( + "encryptionSpec": gcp_v2.remove_empties( EncryptionSpec(self.request.get("encryption_spec", {})).to_request() ), # remove empty values "labels": self.request.get("labels"), @@ -181,10 +173,6 @@ def _response(self): return { "blobStoragePathPrefix": self.response.get("blobStoragePathPrefix"), "createTime": self.response.get("createTime"), - "description": self.response.get("description"), - "displayName": self.response.get("displayName"), - "encryptionSpec": EncryptionSpec().from_response(self.response.get("encryptionSpec", {})), - "labels": self.response.get("labels"), "name": self.response.get("name"), "runCount": self.response.get("runCount"), "updateTime": self.response.get("updateTime"), @@ -196,26 +184,10 @@ def _response(self): ################################################################################ -def encode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from to_request() - and it mutates it before it is sent to the API. - """ - return obj - - -def decode(self, obj): - """ - This is a function bound to the main resource object. Its input is the object returned from from_response() - and it mutates it before it is returned to the module caller. - """ - return obj - - def main(): """Main function""" - module = gcp.Module( + module = gcp_v2.Module( argument_spec=dict( state=dict( type="str", @@ -253,17 +225,11 @@ def main(): state = module.params["state"] changed = False - op_configs = gcp.ResourceOpConfigs( - { - "base_url": gcp.ResourceOpConfig( - **{ - "uri": "projects/{project}/locations/{region}/tensorboards", - "async_uri": "", - "verb": "GET", - "timeout_minutes": 0, - } - ), - "create": gcp.ResourceOpConfig( + op_configs = gcp_v2.ResourceOpConfigs( + base_url="https://{region}-aiplatform.googleapis.com/v1/", + base_uri="projects/{project}/locations/{region}/tensorboards", + configs={ + "create": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/tensorboards", "async_uri": "{op_id}", @@ -271,7 +237,7 @@ def main(): "timeout_minutes": 20, } ), - "delete": gcp.ResourceOpConfig( + "delete": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/tensorboards/{name}", "async_uri": "{op_id}", @@ -279,7 +245,7 @@ def main(): "timeout_minutes": 20, } ), - "read": gcp.ResourceOpConfig( + "read": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/tensorboards/{name}", "async_uri": "", @@ -287,7 +253,7 @@ def main(): "timeout_minutes": 0, } ), - "update": gcp.ResourceOpConfig( + "update": gcp_v2.ResourceOpConfig( **{ "uri": "projects/{project}/locations/{region}/tensorboards/{name}", "async_uri": "{op_id}", @@ -295,79 +261,79 @@ def main(): "timeout_minutes": 20, } ), - } + }, ) - params = gcp.remove_nones(module.params) - resource = VertexAI(params, module=module, product="VertexAI", kind="vertexai#tensorboard") - read_uri = op_configs.read.uri + request = gcp_v2.remove_nones(module.params) + resource = VertexAI(request, module=module, product="VertexAI", kind="vertexai#tensorboard", op_configs=op_configs) resource._state = state # store the state in the resource object - # Bind the encode and decode functions to the resource object - resource.encode_func = types.MethodType(encode, resource) - resource.decode_func = types.MethodType(decode, resource) - custom_diff = None # Set this variable if you want to implement custom diff logic + # Set this variable in one of the pre steps to implement custom diff logic + custom_diff = None + + # BEGIN massaging ResourceRef properties + # END massaging ResourceRef properties + + read_link: str = "" # give it a chance for pre-read to overload # --------- BEGIN pre-read custom code --------- - # for this module, we're hitting the list endpoint and filtering on display name - read_uri = op_configs.base_url.uri + "?filter=displayName=" + params.get("display_name") + # for this module, we're hitting the list endpoint and filtering on display name and rebuild the link + read_link = resource.build_link("list") + "?filter=displayName=" + request.get("display_name") # --------- END pre-read custom code --------- - read_url = build_link(params, read_uri) - existing_obj = resource.get(read_url, allow_not_found=True) or {} + if read_link == "": + read_link = resource.build_link("read") + existing_obj = resource.from_response(resource.get(read_link, allow_not_found=True) or {}) new_obj = {} - gcp.debug(module, existing=existing_obj, post=False) + gcp_v2.debug(module, request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=False) # --------- BEGIN post-read custom code --------- # if there are existing tensorboards, the call would have returned a list - if not gcp.empty(existing_obj): + if not gcp_v2.empty(existing_obj): for tb in existing_obj.get("tensorboards", []): - if tb.get("displayName") == params.get("display_name"): + if tb.get("displayName") == request.get("display_name"): existing_obj = tb break - params["name"] = existing_obj["name"].split("/")[-1] + resource.url_params["name"] = existing_obj["name"].split("/")[-1] + # --------- END post-read custom code --------- if custom_diff is not None: is_different = custom_diff else: - is_different = resource.diff(gcp.remove_empties(existing_obj)) - gcp.debug( + is_different = resource.diff(gcp_v2.remove_empties(existing_obj)) + + gcp_v2.debug( module, - request=gcp.remove_empties(resource.to_request()), + request=gcp_v2.remove_empties(resource.to_request()), existing=existing_obj, post=True, is_different=is_different, ) - if gcp.empty(existing_obj): + if gcp_v2.empty(existing_obj): if state == "present": - create_uri = op_configs.create.uri - create_async_uri = op_configs.create.async_uri + gcp_v2.debug(module, action="create") try: # --------- BEGIN create code --------- - is_async = create_async_uri != "" - create_link = build_link(params, create_uri) + create_link: str = "" # give it a chance for pre-create to overload + if create_link == "": + create_link = resource.build_link("create") create_retries = op_configs.create.timeout create_func = getattr(resource, op_configs.create.verb) - async_create_func = getattr(resource, op_configs.create.verb + "_async") - async_create_link = build_link(params, "") + create_async_uri - gcp.debug( - module, - msg="Creating resource", - create_link=create_link, - async_create_link=async_create_link, - is_async=is_async, - ) + create_async_uri = op_configs.create.async_uri + create_async_func = getattr(resource, op_configs.create.verb + "_async") + gcp_v2.debug(module, msg="Creating resource", create_link=create_link, async_uri=create_async_uri) - if is_async: - new_obj = async_create_func(create_link, async_link=async_create_link, retries=create_retries) + if create_async_uri != "": + new_obj = create_async_func(create_link, async_uri=create_async_uri, retries=create_retries) else: new_obj = create_func(create_link) - gcp.debug(module, new=new_obj, action="create", post=False) - gcp.debug(module, new=new_obj, action="create", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="create", post=False) + gcp_v2.debug(module, new=new_obj, action="create", post=True) # --------- END create code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -377,27 +343,30 @@ def main(): pass # nothing to do else: if state == "absent": - delete_uri = op_configs.delete.uri - delete_async_uri = op_configs.delete.async_uri + gcp_v2.debug(module, action="delete") try: # --------- BEGIN delete code --------- - is_async = delete_async_uri != "" - delete_link = build_link(params, delete_uri) + delete_link: str = "" # give it a chance for pre-delete to overload + if delete_link == "": + delete_link = resource.build_link("delete") delete_retries = op_configs.delete.timeout delete_func = getattr(resource, op_configs.delete.verb) - async_delete_func = getattr(resource, op_configs.delete.verb + "_async") - async_delete_link = build_link(params, "") + delete_async_uri - gcp.debug( + delete_async_uri = op_configs.delete.async_uri + delete_async_func = getattr(resource, op_configs.delete.verb + "_async") + gcp_v2.debug( module, msg="Destroying resource", delete_link=delete_link, - async_delete_link=async_delete_link, - is_async=is_async, + async_uri=delete_async_uri, ) - if is_async: - new_obj = async_delete_func(delete_link, async_link=async_delete_link, retries=delete_retries) + + if delete_async_uri != "": + new_obj = delete_async_func(delete_link, async_uri=delete_async_uri, retries=delete_retries) else: new_obj = delete_func(delete_link) + new_obj = resource.from_response(new_obj) + gcp_v2.debug(module, new=new_obj, action="delete", post=False) + gcp_v2.debug(module, new=new_obj, action="delete", post=True) # --------- END delete code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -405,29 +374,29 @@ def main(): changed = True else: if is_different: - update_uri = op_configs.update.uri - update_async_uri = op_configs.update.async_uri + gcp_v2.debug(module, action="update") try: # --------- BEGIN update code --------- - is_async = update_async_uri != "" - update_link = build_link(params, update_uri) + update_link: str = "" # give it a chance for pre-update to overload + if update_link == "": + update_link = resource.build_link("update") update_retries = op_configs.update.timeout update_func = getattr(resource, op_configs.update.verb) - async_update_func = getattr(resource, op_configs.update.verb + "_async") - async_update_link = build_link(params, "") + update_async_uri - gcp.debug( + update_async_uri = op_configs.update.async_uri + update_async_func = getattr(resource, op_configs.update.verb + "_async") + gcp_v2.debug( module, msg="Updating resource", update_link=update_link, - async_update_link=async_update_link, - is_async=is_async, + async_uri=update_async_uri, ) - if is_async: - new_obj = async_update_func(update_link, async_link=async_update_link, retries=update_retries) + if update_async_uri != "": + new_obj = update_async_func(update_link, async_uri=update_async_uri, retries=update_retries) else: new_obj = update_func(update_link) - gcp.debug(module, new=new_obj, action="update", post=False) - gcp.debug(module, new=new_obj, action="update", post=True) + new_obj = resource.with_kind(resource.from_response(new_obj)) + gcp_v2.debug(module, new=new_obj, action="update", post=False) + gcp_v2.debug(module, new=new_obj, action="update", post=True) # --------- END update code --------- except Exception as e: module.fail_json(msg=str(e)) @@ -437,6 +406,7 @@ def main(): new_obj = existing_obj new_obj.update({"changed": changed}) + gcp_v2.debug(module, final_obj=new_obj, changed=changed) module.exit_json(**new_obj) diff --git a/tests/integration/targets/gcp_alloydb_backup/aliases b/tests/integration/targets/gcp_alloydb_backup/aliases index 13cd0ec6f..c42bb1a0b 100644 --- a/tests/integration/targets/gcp_alloydb_backup/aliases +++ b/tests/integration/targets/gcp_alloydb_backup/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/alloydb \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/alloydb diff --git a/tests/integration/targets/gcp_alloydb_backup/tasks/autogen.yml b/tests/integration/targets/gcp_alloydb_backup/tasks/autogen.yml index 74118fcc2..e0ea37649 100644 --- a/tests/integration/targets/gcp_alloydb_backup/tasks/autogen.yml +++ b/tests/integration/targets/gcp_alloydb_backup/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -34,95 +38,119 @@ auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" - - name: Connect peering range - ansible.builtin.command: > - gcloud services vpc-peerings connect - --quiet - --service=servicenetworking.googleapis.com - --ranges="{{ resource_name }}-peering" - --network="{{ _network.name }}" - --project="{{ gcp_project }}" + - name: Create temporary directory + ansible.builtin.tempfile: + state: directory + register: _tempdir - - name: Create alloydb cluster - google.cloud.gcp_alloydb_cluster: - cluster_id: "{{ resource_name }}" - state: present - location: us-central1 - cluster_type: PRIMARY - initial_user: - user: pgroot - password: Test123Test - network_config: - network: "projects/{{ gcp_project_number }}/global/networks/{{ resource_name }}" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - register: _cluster + - name: Connect block + block: + - name: Connect peering range + environment: + CLOUDSDK_CONFIG: "{{ _tempdir.path }}" + GOOGLE_APPLICATION_CREDENTIALS: "{{ gcp_cred_file }}" + ansible.builtin.shell: | + cd $CLOUDSDK_CONFIG + gcloud auth activate-service-account --key-file="$GOOGLE_APPLICATION_CREDENTIALS" + gcloud services vpc-peerings connect \ + --quiet \ + --service=servicenetworking.googleapis.com \ + --ranges="{{ resource_name }}-peering" \ + --network="{{ _network.name }}" \ + --project="{{ gcp_project }}" + register: _cmd + failed_when: + - "'finished successfully' not in _cmd.stderr" + changed_when: true + always: + - name: Delete temporary directory + ansible.builtin.file: + path: "{{ _tempdir.path }}" + state: absent - - name: Create instance in cluster - google.cloud.gcp_alloydb_instance: - instance_id: "{{ resource_name }}" - state: present - instance_type: PRIMARY - cluster: "{{ _cluster }}" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - register: _instance + - name: AlloyDB block + block: + - name: Create alloydb cluster + google.cloud.gcp_alloydb_cluster: + cluster_id: "{{ resource_name }}" + state: present + location: us-central1 + cluster_type: PRIMARY + initial_user: + user: pgroot + password: Test123Test + network_config: + network: "projects/{{ gcp_project_number }}/global/networks/{{ resource_name }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + register: _cluster - - name: Create a backup for cluster - google.cloud.gcp_alloydb_backup: - backup_id: "{{ resource_name }}" - state: present - location: us-central1 - cluster_name: "{{ _cluster.name }}" - type: ON_DEMAND - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - register: _backup + - name: Create instance in cluster + google.cloud.gcp_alloydb_instance: + instance_id: "{{ resource_name }}" + state: present + instance_type: PRIMARY + cluster: "{{ _cluster }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + register: _instance - - name: Run assertions - ansible.builtin.assert: - that: - - _cluster.changed == true - - _instance.changed == true - - _backup.changed == true - - _backup.type == "ON_DEMAND" - - _backup.clusterName == _cluster.name + - name: Create a backup for cluster + google.cloud.gcp_alloydb_backup: + backup_id: "{{ resource_name }}" + state: present + location: us-central1 + cluster_name: "{{ _cluster.name }}" + type: ON_DEMAND + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + register: _backup - always: - - name: Delete backup - google.cloud.gcp_alloydb_backup: - backup_id: "{{ resource_name }}" - state: absent - location: us-central1 - cluster_name: "projects/{{ gcp_project }}/locations/us-central1/clusters/{{ resource_name }}" - type: ON_DEMAND - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" + - name: Run assertions + ansible.builtin.assert: + that: + - _cluster.changed == true + - _instance.changed == true + - _backup.changed == true + - _backup.type == "ON_DEMAND" + - _backup.clusterUid == _cluster.uid - - name: Delete instance - google.cloud.gcp_alloydb_instance: - instance_id: "{{ resource_name }}" - state: absent - instance_type: PRIMARY - cluster: - name: "projects/{{ gcp_project }}/locations/us-central1/clusters/{{ resource_name }}" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" + always: + - name: Delete backup + google.cloud.gcp_alloydb_backup: + backup_id: "{{ resource_name }}" + state: absent + location: us-central1 + cluster_name: "projects/{{ gcp_project }}/locations/us-central1/clusters/{{ resource_name }}" + type: ON_DEMAND + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" - - name: Delete cluster - google.cloud.gcp_alloydb_cluster: - cluster_id: "{{ resource_name }}" - state: absent - location: us-central1 - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" + - name: Delete instance + google.cloud.gcp_alloydb_instance: + instance_id: "{{ resource_name }}" + state: absent + instance_type: PRIMARY + cluster: + name: "projects/{{ gcp_project }}/locations/us-central1/clusters/{{ resource_name }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + - name: Delete cluster + google.cloud.gcp_alloydb_cluster: + cluster_id: "{{ resource_name }}" + state: absent + location: us-central1 + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + + always: - name: Delete peering range google.cloud.gcp_compute_global_address: name: "{{ resource_name }}-peering" @@ -143,3 +171,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_alloydb_cluster/aliases b/tests/integration/targets/gcp_alloydb_cluster/aliases index 13cd0ec6f..c42bb1a0b 100644 --- a/tests/integration/targets/gcp_alloydb_cluster/aliases +++ b/tests/integration/targets/gcp_alloydb_cluster/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/alloydb \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/alloydb diff --git a/tests/integration/targets/gcp_alloydb_cluster/tasks/autogen.yml b/tests/integration/targets/gcp_alloydb_cluster/tasks/autogen.yml index b42fca9d2..beefc16f6 100644 --- a/tests/integration/targets/gcp_alloydb_cluster/tasks/autogen.yml +++ b/tests/integration/targets/gcp_alloydb_cluster/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -60,7 +64,7 @@ - _primary.clusterType == "PRIMARY" - _secondary.changed == true - _secondary.clusterType == "SECONDARY" - - _secondary.secondaryConfig.primaryClusterName == _primary.name + - _secondary.secondaryConfig.primaryClusterName.split('/')[-1] == _primary.name.split('/')[-1] always: - name: Delete secondary cluster @@ -89,3 +93,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_alloydb_instance/aliases b/tests/integration/targets/gcp_alloydb_instance/aliases index 13cd0ec6f..c42bb1a0b 100644 --- a/tests/integration/targets/gcp_alloydb_instance/aliases +++ b/tests/integration/targets/gcp_alloydb_instance/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/alloydb \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/alloydb diff --git a/tests/integration/targets/gcp_alloydb_instance/tasks/autogen.yml b/tests/integration/targets/gcp_alloydb_instance/tasks/autogen.yml index 36b25bae5..7e210fc5a 100644 --- a/tests/integration/targets/gcp_alloydb_instance/tasks/autogen.yml +++ b/tests/integration/targets/gcp_alloydb_instance/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -34,87 +38,105 @@ auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" - - name: Connect peering range - ansible.builtin.command: > - gcloud services vpc-peerings connect - --quiet - --service=servicenetworking.googleapis.com - --ranges="{{ resource_name }}-peering" - --network="{{ _network.name }}" - --project="{{ gcp_project }}" + - name: Create temporary directory + ansible.builtin.tempfile: + state: directory + register: _tempdir - - name: Create primary alloydb cluster - google.cloud.gcp_alloydb_cluster: - cluster_id: "{{ resource_name }}-primary" - state: present - location: us-central1 - cluster_type: PRIMARY - initial_user: - user: pgroot - password: Test123Test - network_config: - network: "projects/{{ gcp_project_number }}/global/networks/{{ resource_name }}" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - register: _primary + - name: Connect block + block: + - name: Connect peering range + environment: + CLOUDSDK_CONFIG: "{{ _tempdir.path }}" + GOOGLE_APPLICATION_CREDENTIALS: "{{ gcp_cred_file }}" + ansible.builtin.shell: | + cd $CLOUDSDK_CONFIG + gcloud auth activate-service-account --key-file="$GOOGLE_APPLICATION_CREDENTIALS" + gcloud services vpc-peerings connect \ + --quiet \ + --service=servicenetworking.googleapis.com \ + --ranges="{{ resource_name }}-peering" \ + --network="{{ _network.name }}" \ + --project="{{ gcp_project }}" + register: _cmd + failed_when: + - "'finished successfully' not in _cmd.stderr" + changed_when: true + always: + - name: Delete temporary directory + ansible.builtin.file: + path: "{{ _tempdir.path }}" + state: absent - - name: Create an instance in primary cluster - google.cloud.gcp_alloydb_instance: - instance_id: "{{ resource_name }}" - state: present - instance_type: PRIMARY - cluster: "{{ _primary }}" - database_flags: - password.enforce_complexity: "on" - password.min_uppercase_letters: "1" - password.min_numerical_chars: "1" - password.min_pass_length: "10" - password.enforce_expiration: "on" - password.expiration_in_days: "1" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - register: _instance + - name: AlloyDB block + block: + - name: Create primary alloydb cluster + google.cloud.gcp_alloydb_cluster: + cluster_id: "{{ resource_name }}-primary" + state: present + location: us-central1 + cluster_type: PRIMARY + initial_user: + user: pgroot + password: "test-alloydb-{{ resource_name }}" + network_config: + network: "projects/{{ gcp_project_number }}/global/networks/{{ resource_name }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + register: _primary - - name: Run assertions - ansible.builtin.assert: - that: - - _primary.changed == true - - _instance.changed == true + - name: Create an instance in primary cluster + google.cloud.gcp_alloydb_instance: + instance_id: "{{ resource_name }}" + state: present + instance_type: PRIMARY + cluster: "{{ _primary }}" + database_flags: + password.enforce_complexity: "on" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + register: _instance - always: - - name: Delete instance - google.cloud.gcp_alloydb_instance: - instance_id: "{{ resource_name }}" - state: absent - instance_type: PRIMARY - cluster: - name: "projects/{{ gcp_project }}/locations/us-central1/clusters/{{ resource_name }}-primary" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" + - name: Run assertions + ansible.builtin.assert: + that: + - _primary.changed == true + - _instance.changed == true - - name: Delete primary cluster - google.cloud.gcp_alloydb_cluster: - cluster_id: "{{ resource_name }}-primary" - state: absent - location: us-central1 - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" + always: + - name: Delete instance + google.cloud.gcp_alloydb_instance: + instance_id: "{{ resource_name }}" + state: absent + instance_type: PRIMARY + cluster: + name: "projects/{{ gcp_project }}/locations/us-central1/clusters/{{ resource_name }}-primary" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" - - name: Delete peering range - google.cloud.gcp_compute_global_address: - name: "{{ resource_name }}-peering" - state: absent - address_type: INTERNAL - prefix_length: 16 - purpose: VPC_PEERING - network: "{{ _network }}" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" + - name: Delete primary cluster + google.cloud.gcp_alloydb_cluster: + cluster_id: "{{ resource_name }}-primary" + state: absent + location: us-central1 + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + + - name: Delete peering range + google.cloud.gcp_compute_global_address: + name: "{{ resource_name }}-peering" + state: absent + address_type: INTERNAL + prefix_length: 16 + purpose: VPC_PEERING + network: "{{ _network }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" always: - name: Destroy test network @@ -124,3 +146,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_alloydb_user/aliases b/tests/integration/targets/gcp_alloydb_user/aliases index 13cd0ec6f..c42bb1a0b 100644 --- a/tests/integration/targets/gcp_alloydb_user/aliases +++ b/tests/integration/targets/gcp_alloydb_user/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/alloydb \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/alloydb diff --git a/tests/integration/targets/gcp_alloydb_user/tasks/autogen.yml b/tests/integration/targets/gcp_alloydb_user/tasks/autogen.yml index 0174b879d..5007f7ce3 100644 --- a/tests/integration/targets/gcp_alloydb_user/tasks/autogen.yml +++ b/tests/integration/targets/gcp_alloydb_user/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -34,129 +38,134 @@ auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" - - name: Connect peering range - ansible.builtin.command: > - gcloud services vpc-peerings connect - --quiet - --service=servicenetworking.googleapis.com - --ranges="{{ resource_name }}-peering" - --network="{{ _network.name }}" - --project="{{ gcp_project }}" + - name: Create temporary directory + ansible.builtin.tempfile: + state: directory + register: _tempdir - - name: Create alloydb cluster - google.cloud.gcp_alloydb_cluster: - cluster_id: "{{ resource_name }}" - state: present - location: us-central1 - cluster_type: PRIMARY - initial_user: - user: pgroot - password: Test123Test - network_config: - network: "projects/{{ gcp_project_number }}/global/networks/{{ resource_name }}" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - register: _cluster + - name: Connect block + block: + - name: Connect peering range + environment: + CLOUDSDK_CONFIG: "{{ _tempdir.path }}" + GOOGLE_APPLICATION_CREDENTIALS: "{{ gcp_cred_file }}" + ansible.builtin.shell: | + cd $CLOUDSDK_CONFIG + gcloud auth activate-service-account --key-file="$GOOGLE_APPLICATION_CREDENTIALS" + gcloud services vpc-peerings connect \ + --quiet \ + --service=servicenetworking.googleapis.com \ + --ranges="{{ resource_name }}-peering" \ + --network="{{ _network.name }}" \ + --project="{{ gcp_project }}" + register: _cmd + failed_when: + - "'finished successfully' not in _cmd.stderr" + changed_when: true + always: + - name: Delete temporary directory + ansible.builtin.file: + path: "{{ _tempdir.path }}" + state: absent - - name: Create instance in cluster - google.cloud.gcp_alloydb_instance: - instance_id: "{{ resource_name }}" - state: present - instance_type: PRIMARY - cluster: "{{ _cluster }}" - database_flags: - password.enforce_complexity: "on" - password.min_uppercase_letters: "1" - password.min_numerical_chars: "1" - password.min_pass_length: "10" - password.enforce_expiration: "on" - password.expiration_in_days: "1" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - register: _instance + - name: AlloyDB block + block: + - name: Create primary alloydb cluster + google.cloud.gcp_alloydb_cluster: + cluster_id: "{{ resource_name }}-primary" + state: present + location: us-central1 + cluster_type: PRIMARY + initial_user: + user: pgroot + password: "test-alloydb-{{ resource_name }}" + network_config: + network: "projects/{{ gcp_project_number }}/global/networks/{{ resource_name }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + register: _cluster - - name: Create user with good password - google.cloud.gcp_alloydb_user: - user_id: success - state: present - user_type: ALLOYDB_BUILT_IN - password: Test123Test - database_roles: - - alloydbsuperuser - cluster: "{{ _cluster }}" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - register: _password_ok + - name: Create an instance in primary cluster + google.cloud.gcp_alloydb_instance: + instance_id: "{{ resource_name }}" + state: present + instance_type: PRIMARY + cluster: "{{ _cluster }}" + database_flags: + password.enforce_complexity: "on" + password.enforce_password_does_not_contain_username: "on" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + register: _instance - - name: Create user with bad password - google.cloud.gcp_alloydb_user: - user_id: success - state: present - user_type: ALLOYDB_BUILT_IN - password: abc123 - database_roles: - - alloydbsuperuser - cluster: "{{ _cluster }}" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - ignore_errors: true - register: _password_bad + - name: Create user with good password + google.cloud.gcp_alloydb_user: + user_id: okuser + state: present + user_type: ALLOYDB_BUILT_IN + password: "good-user-{{ resource_name }}" + database_roles: + - alloydbsuperuser + cluster: "{{ _cluster }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + register: _password_ok - - name: Run assertions - ansible.builtin.assert: - that: - - _cluster.changed - - _instance.changed - - _password_ok.changed - - _password_bad.failed + - name: Run assertions + ansible.builtin.assert: + that: + - _cluster.changed + - _instance.changed + - _password_ok.changed - always: - - name: Delete user - google.cloud.gcp_alloydb_user: - user_id: success - state: absent - user_type: ALLOYDB_BUILT_IN - cluster: - name: "projects/{{ gcp_project }}/locations/us-central1/clusters/{{ resource_name }}" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" + always: + - name: Delete user + google.cloud.gcp_alloydb_user: + user_id: okuser + state: absent + user_type: ALLOYDB_BUILT_IN + password: "good-user-{{ resource_name }}" + database_roles: + - alloydbsuperuser + cluster: "{{ _cluster }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" - - name: Delete instance - google.cloud.gcp_alloydb_instance: - instance_id: "{{ resource_name }}" - state: absent - instance_type: PRIMARY - cluster: - name: "projects/{{ gcp_project }}/locations/us-central1/clusters/{{ resource_name }}" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" + - name: Delete instance + google.cloud.gcp_alloydb_instance: + instance_id: "{{ resource_name }}" + state: absent + instance_type: PRIMARY + cluster: + name: "projects/{{ gcp_project }}/locations/us-central1/clusters/{{ resource_name }}-primary" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" - - name: Delete cluster - google.cloud.gcp_alloydb_cluster: - cluster_id: "{{ resource_name }}" - state: absent - location: us-central1 - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" + - name: Delete primary cluster + google.cloud.gcp_alloydb_cluster: + cluster_id: "{{ resource_name }}-primary" + state: absent + location: us-central1 + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" - - name: Delete peering range - google.cloud.gcp_compute_global_address: - name: "{{ resource_name }}-peering" - state: absent - address_type: INTERNAL - prefix_length: 16 - purpose: VPC_PEERING - network: "{{ _network }}" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" + - name: Delete peering range + google.cloud.gcp_compute_global_address: + name: "{{ resource_name }}-peering" + state: absent + address_type: INTERNAL + prefix_length: 16 + purpose: VPC_PEERING + network: "{{ _network }}" + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" always: - name: Destroy test network @@ -166,3 +175,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_colab_notebook_execution/aliases b/tests/integration/targets/gcp_colab_notebook_execution/aliases index bd83600a5..ad922b268 100644 --- a/tests/integration/targets/gcp_colab_notebook_execution/aliases +++ b/tests/integration/targets/gcp_colab_notebook_execution/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/colab \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/colab diff --git a/tests/integration/targets/gcp_colab_notebook_execution/tasks/autogen.yml b/tests/integration/targets/gcp_colab_notebook_execution/tasks/autogen.yml index 9d6d94d3c..870c7019c 100644 --- a/tests/integration/targets/gcp_colab_notebook_execution/tasks/autogen.yml +++ b/tests/integration/targets/gcp_colab_notebook_execution/tasks/autogen.yml @@ -171,32 +171,22 @@ state: directory register: _tempdir - - name: Empty bucket block + - name: Destroy bucket block block: - - name: Empty bucket + - name: Destroy bucket environment: CLOUDSDK_CONFIG: "{{ _tempdir.path }}" GOOGLE_APPLICATION_CREDENTIALS: "{{ gcp_cred_file }}" ansible.builtin.shell: | cd $CLOUDSDK_CONFIG gcloud auth activate-service-account --key-file="$GOOGLE_APPLICATION_CREDENTIALS" - gcloud storage rm --recursive gs://{{ resource_name }}-nb/output/ - ignore_errors: true + gcloud storage rm --recursive gs://{{ resource_name }}-nb/ always: - name: Delete temporary directory ansible.builtin.file: path: "{{ _tempdir.path }}" state: absent - - name: Destroy storage bucket - google.cloud.gcp_storage_bucket: - name: "{{ resource_name }}-nb" - state: absent - location: us-central1 - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - always: - name: Destroy test network google.cloud.gcp_compute_network: diff --git a/tests/integration/targets/gcp_colab_runtime/aliases b/tests/integration/targets/gcp_colab_runtime/aliases index bd83600a5..ad922b268 100644 --- a/tests/integration/targets/gcp_colab_runtime/aliases +++ b/tests/integration/targets/gcp_colab_runtime/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/colab \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/colab diff --git a/tests/integration/targets/gcp_colab_runtime_template/aliases b/tests/integration/targets/gcp_colab_runtime_template/aliases index bd83600a5..ad922b268 100644 --- a/tests/integration/targets/gcp_colab_runtime_template/aliases +++ b/tests/integration/targets/gcp_colab_runtime_template/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/colab \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/colab diff --git a/tests/integration/targets/gcp_colab_schedule/aliases b/tests/integration/targets/gcp_colab_schedule/aliases index bd83600a5..ad922b268 100644 --- a/tests/integration/targets/gcp_colab_schedule/aliases +++ b/tests/integration/targets/gcp_colab_schedule/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/colab \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/colab diff --git a/tests/integration/targets/gcp_colab_schedule/tasks/autogen.yml b/tests/integration/targets/gcp_colab_schedule/tasks/autogen.yml index 094dccbc9..efdf202a6 100644 --- a/tests/integration/targets/gcp_colab_schedule/tasks/autogen.yml +++ b/tests/integration/targets/gcp_colab_schedule/tasks/autogen.yml @@ -238,17 +238,16 @@ auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" - - name: Empty bucket block + - name: Destroy bucket block block: - - name: Empty bucket + - name: Destroy bucket environment: CLOUDSDK_CONFIG: "{{ _tempdir.path }}" GOOGLE_APPLICATION_CREDENTIALS: "{{ gcp_cred_file }}" ansible.builtin.shell: | cd $CLOUDSDK_CONFIG gcloud auth activate-service-account --key-file="$GOOGLE_APPLICATION_CREDENTIALS" - gcloud storage rm --recursive gs://{{ resource_name }}-sch/input/ - gcloud storage rm --recursive gs://{{ resource_name }}-sch/output/ + gcloud storage rm --recursive gs://{{ resource_name }}-sch/ ignore_errors: true always: - name: Delete temporary directory @@ -256,15 +255,6 @@ path: "{{ _tempdir.path }}" state: absent - - name: Destroy storage bucket - google.cloud.gcp_storage_bucket: - name: "{{ resource_name }}-sch" - state: absent - location: us-central1 - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - always: - name: Destroy test network google.cloud.gcp_compute_network: diff --git a/tests/integration/targets/gcp_vertexai_dataset/aliases b/tests/integration/targets/gcp_vertexai_dataset/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_dataset/aliases +++ b/tests/integration/targets/gcp_vertexai_dataset/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_dataset/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_dataset/tasks/autogen.yml index 5af74ca00..2612603ae 100644 --- a/tests/integration/targets/gcp_vertexai_dataset/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_dataset/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -74,3 +78,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_deployment_resource_pool/aliases b/tests/integration/targets/gcp_vertexai_deployment_resource_pool/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_deployment_resource_pool/aliases +++ b/tests/integration/targets/gcp_vertexai_deployment_resource_pool/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_deployment_resource_pool/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_deployment_resource_pool/tasks/autogen.yml index 863d321aa..2cd487b56 100644 --- a/tests/integration/targets/gcp_vertexai_deployment_resource_pool/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_deployment_resource_pool/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -84,3 +88,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_endpoint/aliases b/tests/integration/targets/gcp_vertexai_endpoint/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_endpoint/aliases +++ b/tests/integration/targets/gcp_vertexai_endpoint/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_endpoint/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_endpoint/tasks/autogen.yml index cd12e7137..3246a3de6 100644 --- a/tests/integration/targets/gcp_vertexai_endpoint/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_endpoint/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -115,3 +119,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_endpoint_with_model_garden_deployment/aliases b/tests/integration/targets/gcp_vertexai_endpoint_with_model_garden_deployment/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_endpoint_with_model_garden_deployment/aliases +++ b/tests/integration/targets/gcp_vertexai_endpoint_with_model_garden_deployment/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_endpoint_with_model_garden_deployment/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_endpoint_with_model_garden_deployment/tasks/autogen.yml index 3d9fc0f0b..432ecacc5 100644 --- a/tests/integration/targets/gcp_vertexai_endpoint_with_model_garden_deployment/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_endpoint_with_model_garden_deployment/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -59,3 +63,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_feature_group/aliases b/tests/integration/targets/gcp_vertexai_feature_group/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_feature_group/aliases +++ b/tests/integration/targets/gcp_vertexai_feature_group/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_feature_group/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_feature_group/tasks/autogen.yml index 9ceb294db..214ea8dcc 100644 --- a/tests/integration/targets/gcp_vertexai_feature_group/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_feature_group/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -135,3 +139,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_feature_group_feature/aliases b/tests/integration/targets/gcp_vertexai_feature_group_feature/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_feature_group_feature/aliases +++ b/tests/integration/targets/gcp_vertexai_feature_group_feature/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_feature_group_feature/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_feature_group_feature/tasks/autogen.yml index 43c2cb534..de4ff3082 100644 --- a/tests/integration/targets/gcp_vertexai_feature_group_feature/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_feature_group_feature/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -58,7 +62,7 @@ - name: Create feature group google.cloud.gcp_vertexai_feature_group: state: present - name: "{{ resource_name | regex_replace('-', '_') }}" + name: "{{ resource_name | regex_replace('-', '_') }}_fgf" description: "My feature group" region: us-central1 big_query: @@ -72,7 +76,7 @@ google.cloud.gcp_vertexai_feature_group_feature: state: present name: feature_a - feature_group: "{{ resource_name | regex_replace('-', '_') }}" + feature_group: "{{ resource_name | regex_replace('-', '_') }}_fgf" description: "A simple feature" region: us-central1 project: "{{ gcp_project }}" @@ -83,7 +87,7 @@ google.cloud.gcp_vertexai_feature_group_feature: state: absent name: feature_a - feature_group: "{{ resource_name | regex_replace('-', '_') }}" + feature_group: "{{ resource_name | regex_replace('-', '_') }}_fgf" description: "A simple feature" region: us-central1 project: "{{ gcp_project }}" @@ -94,7 +98,7 @@ - name: Delete feature group google.cloud.gcp_vertexai_feature_group: state: absent - name: "{{ resource_name | regex_replace('-', '_') }}" + name: "{{ resource_name | regex_replace('-', '_') }}_fgf" description: "My feature group" region: us-central1 big_query: @@ -131,3 +135,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_feature_online_store/aliases b/tests/integration/targets/gcp_vertexai_feature_online_store/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_feature_online_store/aliases +++ b/tests/integration/targets/gcp_vertexai_feature_online_store/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_feature_online_store/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_feature_online_store/tasks/autogen.yml index b8b37ede1..eb99e3883 100644 --- a/tests/integration/targets/gcp_vertexai_feature_online_store/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_feature_online_store/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -59,3 +63,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_feature_online_store_featureview/aliases b/tests/integration/targets/gcp_vertexai_feature_online_store_featureview/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_feature_online_store_featureview/aliases +++ b/tests/integration/targets/gcp_vertexai_feature_online_store_featureview/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_feature_online_store_featureview/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_feature_online_store_featureview/tasks/autogen.yml index 3bd4d77b8..8e60c816c 100644 --- a/tests/integration/targets/gcp_vertexai_feature_online_store_featureview/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_feature_online_store_featureview/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -59,7 +63,7 @@ - name: Create Feature Online Store google.cloud.gcp_vertexai_feature_online_store: state: present - name: "{{ resource_name | regex_replace('-', '_') }}" + name: "{{ resource_name | regex_replace('-', '_') }}_fosfv" bigtable: auto_scaling: min_node_count: 1 @@ -74,7 +78,7 @@ google.cloud.gcp_vertexai_feature_online_store_featureview: state: present name: "{{ resource_name | regex_replace('-', '_') }}" - feature_online_store: "{{ resource_name | regex_replace('-', '_') }}" + feature_online_store: "{{ resource_name | regex_replace('-', '_') }}_fosfv" sync_config: cron: "0 0 * * *" big_query_source: @@ -90,7 +94,7 @@ google.cloud.gcp_vertexai_feature_online_store_featureview: state: absent name: "{{ resource_name | regex_replace('-', '_') }}" - feature_online_store: "{{ resource_name | regex_replace('-', '_') }}" + feature_online_store: "{{ resource_name | regex_replace('-', '_') }}_fosfv" sync_config: cron: "0 0 * * *" big_query_source: @@ -106,7 +110,7 @@ - name: Delete Feature Online Store google.cloud.gcp_vertexai_feature_online_store: state: absent - name: "{{ resource_name | regex_replace('-', '_') }}" + name: "{{ resource_name | regex_replace('-', '_') }}_fosfv" bigtable: auto_scaling: min_node_count: 1 @@ -145,3 +149,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_featurestore/aliases b/tests/integration/targets/gcp_vertexai_featurestore/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_featurestore/aliases +++ b/tests/integration/targets/gcp_vertexai_featurestore/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_featurestore/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_featurestore/tasks/autogen.yml index f2b4901e7..4dd26b811 100644 --- a/tests/integration/targets/gcp_vertexai_featurestore/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_featurestore/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -64,3 +68,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_featurestore_entitytype/aliases b/tests/integration/targets/gcp_vertexai_featurestore_entitytype/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_featurestore_entitytype/aliases +++ b/tests/integration/targets/gcp_vertexai_featurestore_entitytype/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_featurestore_entitytype/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_featurestore_entitytype/tasks/autogen.yml index a1a1723de..b6c5ec2db 100644 --- a/tests/integration/targets/gcp_vertexai_featurestore_entitytype/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_featurestore_entitytype/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -70,3 +74,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_featurestore_entitytype_feature/aliases b/tests/integration/targets/gcp_vertexai_featurestore_entitytype_feature/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_featurestore_entitytype_feature/aliases +++ b/tests/integration/targets/gcp_vertexai_featurestore_entitytype_feature/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_featurestore_entitytype_feature/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_featurestore_entitytype_feature/tasks/autogen.yml index c7620784d..9fb4d90c2 100644 --- a/tests/integration/targets/gcp_vertexai_featurestore_entitytype_feature/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_featurestore_entitytype_feature/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -98,3 +102,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_index/aliases b/tests/integration/targets/gcp_vertexai_index/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_index/aliases +++ b/tests/integration/targets/gcp_vertexai_index/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_index/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_index/tasks/autogen.yml index 97eb3f723..010a71262 100644 --- a/tests/integration/targets/gcp_vertexai_index/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_index/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -163,26 +167,26 @@ - v1 - v2 - - name: Empty bucket - google.cloud.gcp_storage_object: - action: delete - bucket: "{{ resource_name }}" - src: "contents-{{ item }}/data.json" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - loop: - - v1 - - v2 - - - name: Destroy storage bucket - google.cloud.gcp_storage_bucket: - name: "{{ resource_name }}" - state: absent - location: us-central1 - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" + - name: Create temporary directory + ansible.builtin.tempfile: + state: directory + register: _tempdir + + - name: Destroy bucket block + block: + - name: Destroy bucket + environment: + CLOUDSDK_CONFIG: "{{ _tempdir.path }}" + GOOGLE_APPLICATION_CREDENTIALS: "{{ gcp_cred_file }}" + ansible.builtin.shell: | + cd $CLOUDSDK_CONFIG + gcloud auth activate-service-account --key-file="$GOOGLE_APPLICATION_CREDENTIALS" + gcloud storage rm --recursive gs://{{ resource_name }}/ + always: + - name: Delete temporary directory + ansible.builtin.file: + path: "{{ _tempdir.path }}" + state: absent always: - name: Destroy test network @@ -192,3 +196,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_index_endpoint/aliases b/tests/integration/targets/gcp_vertexai_index_endpoint/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_index_endpoint/aliases +++ b/tests/integration/targets/gcp_vertexai_index_endpoint/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_index_endpoint/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_index_endpoint/tasks/autogen.yml index fae2ec797..e301dfc3f 100644 --- a/tests/integration/targets/gcp_vertexai_index_endpoint/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_index_endpoint/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -129,3 +133,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_index_endpoint_deployed_index/aliases b/tests/integration/targets/gcp_vertexai_index_endpoint_deployed_index/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_index_endpoint_deployed_index/aliases +++ b/tests/integration/targets/gcp_vertexai_index_endpoint_deployed_index/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_index_endpoint_deployed_index/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_index_endpoint_deployed_index/tasks/autogen.yml index 5ecb14a5c..48d28d702 100644 --- a/tests/integration/targets/gcp_vertexai_index_endpoint_deployed_index/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_index_endpoint_deployed_index/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -190,23 +194,21 @@ path: "/tmp/{{ resource_name }}-data.json" state: absent - - name: Empty bucket - google.cloud.gcp_storage_object: - action: delete - bucket: "{{ resource_name }}" - src: "contents/data.json" - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" - - - name: Destroy storage bucket - google.cloud.gcp_storage_bucket: - name: "{{ resource_name }}" - state: absent - location: us-central1 - project: "{{ gcp_project }}" - auth_kind: "{{ gcp_cred_kind }}" - service_account_file: "{{ gcp_cred_file }}" + - name: Destroy bucket block + block: + - name: Destroy bucket + environment: + CLOUDSDK_CONFIG: "{{ _tempdir.path }}" + GOOGLE_APPLICATION_CREDENTIALS: "{{ gcp_cred_file }}" + ansible.builtin.shell: | + cd $CLOUDSDK_CONFIG + gcloud auth activate-service-account --key-file="$GOOGLE_APPLICATION_CREDENTIALS" + gcloud storage rm --recursive gs://{{ resource_name }}/ + always: + - name: Delete temporary directory + ansible.builtin.file: + path: "{{ _tempdir.path }}" + state: absent - name: Delete peering range google.cloud.gcp_compute_global_address: @@ -228,3 +230,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_metadata_store/aliases b/tests/integration/targets/gcp_vertexai_metadata_store/aliases index 6044b1f75..d33f94608 100644 --- a/tests/integration/targets/gcp_vertexai_metadata_store/aliases +++ b/tests/integration/targets/gcp_vertexai_metadata_store/aliases @@ -10,5 +10,6 @@ # ---------------------------------------------------------------------------- cloud/gcp +cloud/gcp/v2 cloud/gcp/vertex_ai -unsupported \ No newline at end of file +unsupported diff --git a/tests/integration/targets/gcp_vertexai_metadata_store/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_metadata_store/tasks/autogen.yml index e92630f30..baa464f47 100644 --- a/tests/integration/targets/gcp_vertexai_metadata_store/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_metadata_store/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -84,3 +88,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_rag_engine_config/aliases b/tests/integration/targets/gcp_vertexai_rag_engine_config/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_rag_engine_config/aliases +++ b/tests/integration/targets/gcp_vertexai_rag_engine_config/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_rag_engine_config/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_rag_engine_config/tasks/autogen.yml index 78150ce5d..607aab423 100644 --- a/tests/integration/targets/gcp_vertexai_rag_engine_config/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_rag_engine_config/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -28,3 +32,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_reasoning_engine/aliases b/tests/integration/targets/gcp_vertexai_reasoning_engine/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_reasoning_engine/aliases +++ b/tests/integration/targets/gcp_vertexai_reasoning_engine/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_reasoning_engine/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_reasoning_engine/tasks/autogen.yml index daa3a2e31..f78947d25 100644 --- a/tests/integration/targets/gcp_vertexai_reasoning_engine/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_reasoning_engine/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -95,3 +99,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown" diff --git a/tests/integration/targets/gcp_vertexai_tensorboard/aliases b/tests/integration/targets/gcp_vertexai_tensorboard/aliases index ee807574e..56822582e 100644 --- a/tests/integration/targets/gcp_vertexai_tensorboard/aliases +++ b/tests/integration/targets/gcp_vertexai_tensorboard/aliases @@ -10,4 +10,5 @@ # ---------------------------------------------------------------------------- cloud/gcp -cloud/gcp/vertex_ai \ No newline at end of file +cloud/gcp/v2 +cloud/gcp/vertex_ai diff --git a/tests/integration/targets/gcp_vertexai_tensorboard/tasks/autogen.yml b/tests/integration/targets/gcp_vertexai_tensorboard/tasks/autogen.yml index 4f1c11494..71df3ced7 100644 --- a/tests/integration/targets/gcp_vertexai_tensorboard/tasks/autogen.yml +++ b/tests/integration/targets/gcp_vertexai_tensorboard/tasks/autogen.yml @@ -10,6 +10,10 @@ # ---------------------------------------------------------------------------- - name: Main test block block: + - name: Setup + ansible.builtin.debug: + msg: "Setup" + - name: Create test network google.cloud.gcp_compute_network: name: "{{ resource_name }}" @@ -73,3 +77,7 @@ project: "{{ gcp_project }}" auth_kind: "{{ gcp_cred_kind }}" service_account_file: "{{ gcp_cred_file }}" + + - name: Teardown + ansible.builtin.debug: + msg: "Teardown"