Repository: apache/airflow

Issue:
Add `endpoint_id` arg to `vertex_ai.endpoint_service.CreateEndpointOperator`
### Description

Add the optional argument `endpoint_id` to `google.cloud.operators.vertex_ai.endpoint_service.CreateEndpointOperator` class and `google.cloud.hooks.vertex_ai.endpoint_service.EndpointServiceHook.create_endpoint` method.

### Use case/motivation

`google.cloud.operators.vertex_ai.endpoint_service.CreateEndpointOperator` class and `google.cloud.hooks.vertex_ai.endpoint_service.EndpointServiceHook.create_endpoint` method do not have `endpoint_id` argument. They internally use [`CreateEndpointRequest`](https://github.com/googleapis/python-aiplatform/blob/v1.11.0/google/cloud/aiplatform_v1/types/endpoint_service.py#L43), which accepts `endpoint_id`. Hence, I'd like them to accept `endpoint_id` argument and pass it to [`CreateEndpointRequest`](https://github.com/googleapis/python-aiplatform/blob/v1.11.0/google/cloud/aiplatform_v1/types/endpoint_service.py#L43).

If this is satisfied, we can create Vertex Endpoints with a specific Endpoint ID. Then, an Endpoint will be created with the specified Endpoint ID. Without it, an Endpoint will be created with an ID generated randomly.

### Related issues

_No response_

### Are you willing to submit a PR?

- [X] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)



Discussion:

Resolving patch:
diff --git a/airflow/providers/google/cloud/hooks/vertex_ai/endpoint_service.py b/airflow/providers/google/cloud/hooks/vertex_ai/endpoint_service.py
--- a/airflow/providers/google/cloud/hooks/vertex_ai/endpoint_service.py
+++ b/airflow/providers/google/cloud/hooks/vertex_ai/endpoint_service.py
@@ -81,6 +81,7 @@ def create_endpoint(
         project_id: str,
         region: str,
         endpoint: Union[Endpoint, Dict],
+        endpoint_id: Optional[str] = None,
         retry: Union[Retry, _MethodDefault] = DEFAULT,
         timeout: Optional[float] = None,
         metadata: Sequence[Tuple[str, str]] = (),
@@ -91,6 +92,8 @@ def create_endpoint(
         :param project_id: Required. The ID of the Google Cloud project that the service belongs to.
         :param region: Required. The ID of the Google Cloud region that the service belongs to.
         :param endpoint: Required. The Endpoint to create.
+        :param endpoint_id: The ID of Endpoint. This value should be 1-10 characters, and valid characters
+            are /[0-9]/. If not provided, Vertex AI will generate a value for this ID.
         :param retry: Designation of what errors, if any, should be retried.
         :param timeout: The timeout for this request.
         :param metadata: Strings which should be sent along with the request as metadata.
@@ -102,6 +105,7 @@ def create_endpoint(
             request={
                 'parent': parent,
                 'endpoint': endpoint,
+                'endpoint_id': endpoint_id,
             },
             retry=retry,
             timeout=timeout,
diff --git a/airflow/providers/google/cloud/operators/vertex_ai/endpoint_service.py b/airflow/providers/google/cloud/operators/vertex_ai/endpoint_service.py
--- a/airflow/providers/google/cloud/operators/vertex_ai/endpoint_service.py
+++ b/airflow/providers/google/cloud/operators/vertex_ai/endpoint_service.py
@@ -55,6 +55,8 @@ class CreateEndpointOperator(BaseOperator):
     :param project_id: Required. The ID of the Google Cloud project that the service belongs to.
     :param region: Required. The ID of the Google Cloud region that the service belongs to.
     :param endpoint: Required. The Endpoint to create.
+    :param endpoint_id: The ID of Endpoint. This value should be 1-10 characters, and valid characters
+        are /[0-9]/. If not provided, Vertex AI will generate a value for this ID.
     :param retry: Designation of what errors, if any, should be retried.
     :param timeout: The timeout for this request.
     :param metadata: Strings which should be sent along with the request as metadata.
@@ -81,6 +83,7 @@ def __init__(
         region: str,
         project_id: str,
         endpoint: Union[Endpoint, Dict],
+        endpoint_id: Optional[str] = None,
         retry: Union[Retry, _MethodDefault] = DEFAULT,
         timeout: Optional[float] = None,
         metadata: Sequence[Tuple[str, str]] = (),
@@ -93,6 +96,7 @@ def __init__(
         self.region = region
         self.project_id = project_id
         self.endpoint = endpoint
+        self.endpoint_id = endpoint_id
         self.retry = retry
         self.timeout = timeout
         self.metadata = metadata
@@ -112,6 +116,7 @@ def execute(self, context: 'Context'):
             project_id=self.project_id,
             region=self.region,
             endpoint=self.endpoint,
+            endpoint_id=self.endpoint_id,
             retry=self.retry,
             timeout=self.timeout,
             metadata=self.metadata,


Test patch:
diff --git a/tests/providers/google/cloud/hooks/vertex_ai/test_endpoint_service.py b/tests/providers/google/cloud/hooks/vertex_ai/test_endpoint_service.py
--- a/tests/providers/google/cloud/hooks/vertex_ai/test_endpoint_service.py
+++ b/tests/providers/google/cloud/hooks/vertex_ai/test_endpoint_service.py
@@ -31,6 +31,7 @@
 TEST_REGION: str = "test-region"
 TEST_PROJECT_ID: str = "test-project-id"
 TEST_ENDPOINT: dict = {}
+TEST_ENDPOINT_ID: str = "1234567890"
 TEST_ENDPOINT_NAME: str = "test_endpoint_name"
 TEST_DEPLOYED_MODEL: dict = {}
 TEST_DEPLOYED_MODEL_ID: str = "test-deployed-model-id"
@@ -54,12 +55,14 @@ def test_create_endpoint(self, mock_client) -> None:
             project_id=TEST_PROJECT_ID,
             region=TEST_REGION,
             endpoint=TEST_ENDPOINT,
+            endpoint_id=TEST_ENDPOINT_ID,
         )
         mock_client.assert_called_once_with(TEST_REGION)
         mock_client.return_value.create_endpoint.assert_called_once_with(
             request=dict(
                 parent=mock_client.return_value.common_location_path.return_value,
                 endpoint=TEST_ENDPOINT,
+                endpoint_id=TEST_ENDPOINT_ID,
             ),
             metadata=(),
             retry=DEFAULT,
@@ -223,12 +226,14 @@ def test_create_endpoint(self, mock_client) -> None:
             project_id=TEST_PROJECT_ID,
             region=TEST_REGION,
             endpoint=TEST_ENDPOINT,
+            endpoint_id=TEST_ENDPOINT_ID,
         )
         mock_client.assert_called_once_with(TEST_REGION)
         mock_client.return_value.create_endpoint.assert_called_once_with(
             request=dict(
                 parent=mock_client.return_value.common_location_path.return_value,
                 endpoint=TEST_ENDPOINT,
+                endpoint_id=TEST_ENDPOINT_ID,
             ),
             metadata=(),
             retry=DEFAULT,
diff --git a/tests/providers/google/cloud/operators/test_vertex_ai.py b/tests/providers/google/cloud/operators/test_vertex_ai.py
--- a/tests/providers/google/cloud/operators/test_vertex_ai.py
+++ b/tests/providers/google/cloud/operators/test_vertex_ai.py
@@ -148,7 +148,7 @@
 TEST_ENDPOINT = {
     "display_name": "endpoint_test",
 }
-TEST_ENDPOINT_ID = "test_endpoint_id"
+TEST_ENDPOINT_ID = "1234567890"
 TEST_DEPLOYED_MODEL = {
     # format: 'projects/{project}/locations/{location}/models/{model}'
     'model': f"projects/{GCP_PROJECT}/locations/{GCP_LOCATION}/models/test_model_id",
@@ -1137,6 +1137,7 @@ def test_execute(self, mock_hook, to_dict_mock):
             region=GCP_LOCATION,
             project_id=GCP_PROJECT,
             endpoint=TEST_ENDPOINT,
+            endpoint_id=TEST_ENDPOINT_ID,
             retry=RETRY,
             timeout=TIMEOUT,
             metadata=METADATA,
@@ -1149,6 +1150,7 @@ def test_execute(self, mock_hook, to_dict_mock):
             region=GCP_LOCATION,
             project_id=GCP_PROJECT,
             endpoint=TEST_ENDPOINT,
+            endpoint_id=TEST_ENDPOINT_ID,
             retry=RETRY,
             timeout=TIMEOUT,
             metadata=METADATA,