apache-airflow-providers-google 14.0.0__py3-none-any.whl → 19.1.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- airflow/providers/google/3rd-party-licenses/LICENSES.txt +14 -0
- airflow/providers/google/3rd-party-licenses/NOTICE +5 -0
- airflow/providers/google/__init__.py +3 -3
- airflow/providers/google/_vendor/__init__.py +0 -0
- airflow/providers/google/_vendor/json_merge_patch.py +91 -0
- airflow/providers/google/ads/hooks/ads.py +52 -43
- airflow/providers/google/ads/operators/ads.py +2 -2
- airflow/providers/google/ads/transfers/ads_to_gcs.py +3 -19
- airflow/providers/google/assets/gcs.py +1 -11
- airflow/providers/google/cloud/_internal_client/secret_manager_client.py +3 -2
- airflow/providers/google/cloud/bundles/gcs.py +161 -0
- airflow/providers/google/cloud/hooks/alloy_db.py +2 -3
- airflow/providers/google/cloud/hooks/bigquery.py +195 -318
- airflow/providers/google/cloud/hooks/bigquery_dts.py +8 -8
- airflow/providers/google/cloud/hooks/bigtable.py +3 -2
- airflow/providers/google/cloud/hooks/cloud_batch.py +8 -9
- airflow/providers/google/cloud/hooks/cloud_build.py +6 -65
- airflow/providers/google/cloud/hooks/cloud_composer.py +292 -24
- airflow/providers/google/cloud/hooks/cloud_logging.py +109 -0
- airflow/providers/google/cloud/hooks/cloud_memorystore.py +4 -3
- airflow/providers/google/cloud/hooks/cloud_run.py +20 -11
- airflow/providers/google/cloud/hooks/cloud_sql.py +136 -64
- airflow/providers/google/cloud/hooks/cloud_storage_transfer_service.py +35 -15
- airflow/providers/google/cloud/hooks/compute.py +7 -6
- airflow/providers/google/cloud/hooks/compute_ssh.py +7 -4
- airflow/providers/google/cloud/hooks/datacatalog.py +12 -3
- airflow/providers/google/cloud/hooks/dataflow.py +87 -242
- airflow/providers/google/cloud/hooks/dataform.py +9 -14
- airflow/providers/google/cloud/hooks/datafusion.py +7 -9
- airflow/providers/google/cloud/hooks/dataplex.py +13 -12
- airflow/providers/google/cloud/hooks/dataprep.py +2 -2
- airflow/providers/google/cloud/hooks/dataproc.py +76 -74
- airflow/providers/google/cloud/hooks/dataproc_metastore.py +4 -3
- airflow/providers/google/cloud/hooks/dlp.py +5 -4
- airflow/providers/google/cloud/hooks/gcs.py +144 -33
- airflow/providers/google/cloud/hooks/gen_ai.py +196 -0
- airflow/providers/google/cloud/hooks/kms.py +3 -2
- airflow/providers/google/cloud/hooks/kubernetes_engine.py +22 -17
- airflow/providers/google/cloud/hooks/looker.py +6 -1
- airflow/providers/google/cloud/hooks/managed_kafka.py +227 -3
- airflow/providers/google/cloud/hooks/mlengine.py +7 -8
- airflow/providers/google/cloud/hooks/natural_language.py +3 -2
- airflow/providers/google/cloud/hooks/os_login.py +3 -2
- airflow/providers/google/cloud/hooks/pubsub.py +6 -6
- airflow/providers/google/cloud/hooks/secret_manager.py +105 -12
- airflow/providers/google/cloud/hooks/spanner.py +75 -10
- airflow/providers/google/cloud/hooks/speech_to_text.py +3 -2
- airflow/providers/google/cloud/hooks/stackdriver.py +18 -18
- airflow/providers/google/cloud/hooks/tasks.py +4 -3
- airflow/providers/google/cloud/hooks/text_to_speech.py +3 -2
- airflow/providers/google/cloud/hooks/translate.py +8 -17
- airflow/providers/google/cloud/hooks/vertex_ai/auto_ml.py +8 -222
- airflow/providers/google/cloud/hooks/vertex_ai/batch_prediction_job.py +9 -15
- airflow/providers/google/cloud/hooks/vertex_ai/custom_job.py +33 -283
- airflow/providers/google/cloud/hooks/vertex_ai/dataset.py +5 -12
- airflow/providers/google/cloud/hooks/vertex_ai/endpoint_service.py +6 -12
- airflow/providers/google/cloud/hooks/vertex_ai/experiment_service.py +202 -0
- airflow/providers/google/cloud/hooks/vertex_ai/feature_store.py +311 -10
- airflow/providers/google/cloud/hooks/vertex_ai/generative_model.py +79 -75
- airflow/providers/google/cloud/hooks/vertex_ai/hyperparameter_tuning_job.py +7 -13
- airflow/providers/google/cloud/hooks/vertex_ai/model_service.py +8 -12
- airflow/providers/google/cloud/hooks/vertex_ai/pipeline_job.py +6 -12
- airflow/providers/google/cloud/hooks/vertex_ai/prediction_service.py +3 -2
- airflow/providers/google/cloud/hooks/vertex_ai/ray.py +223 -0
- airflow/providers/google/cloud/hooks/video_intelligence.py +3 -2
- airflow/providers/google/cloud/hooks/vision.py +7 -7
- airflow/providers/google/cloud/hooks/workflows.py +4 -3
- airflow/providers/google/cloud/links/alloy_db.py +0 -46
- airflow/providers/google/cloud/links/base.py +77 -7
- airflow/providers/google/cloud/links/bigquery.py +0 -47
- airflow/providers/google/cloud/links/bigquery_dts.py +0 -20
- airflow/providers/google/cloud/links/bigtable.py +0 -48
- airflow/providers/google/cloud/links/cloud_build.py +0 -73
- airflow/providers/google/cloud/links/cloud_functions.py +0 -33
- airflow/providers/google/cloud/links/cloud_memorystore.py +0 -58
- airflow/providers/google/cloud/links/{life_sciences.py → cloud_run.py} +5 -27
- airflow/providers/google/cloud/links/cloud_sql.py +0 -33
- airflow/providers/google/cloud/links/cloud_storage_transfer.py +17 -46
- airflow/providers/google/cloud/links/cloud_tasks.py +7 -26
- airflow/providers/google/cloud/links/compute.py +0 -58
- airflow/providers/google/cloud/links/data_loss_prevention.py +0 -169
- airflow/providers/google/cloud/links/datacatalog.py +23 -54
- airflow/providers/google/cloud/links/dataflow.py +0 -34
- airflow/providers/google/cloud/links/dataform.py +0 -64
- airflow/providers/google/cloud/links/datafusion.py +1 -90
- airflow/providers/google/cloud/links/dataplex.py +0 -154
- airflow/providers/google/cloud/links/dataprep.py +0 -24
- airflow/providers/google/cloud/links/dataproc.py +11 -89
- airflow/providers/google/cloud/links/datastore.py +0 -31
- airflow/providers/google/cloud/links/kubernetes_engine.py +11 -61
- airflow/providers/google/cloud/links/managed_kafka.py +11 -51
- airflow/providers/google/cloud/links/mlengine.py +0 -70
- airflow/providers/google/cloud/links/pubsub.py +0 -32
- airflow/providers/google/cloud/links/spanner.py +0 -33
- airflow/providers/google/cloud/links/stackdriver.py +0 -30
- airflow/providers/google/cloud/links/translate.py +17 -187
- airflow/providers/google/cloud/links/vertex_ai.py +28 -195
- airflow/providers/google/cloud/links/workflows.py +0 -52
- airflow/providers/google/cloud/log/gcs_task_handler.py +166 -118
- airflow/providers/google/cloud/log/stackdriver_task_handler.py +14 -9
- airflow/providers/google/cloud/openlineage/CloudStorageTransferJobFacet.json +68 -0
- airflow/providers/google/cloud/openlineage/CloudStorageTransferRunFacet.json +60 -0
- airflow/providers/google/cloud/openlineage/DataFusionRunFacet.json +32 -0
- airflow/providers/google/cloud/openlineage/facets.py +141 -40
- airflow/providers/google/cloud/openlineage/mixins.py +14 -13
- airflow/providers/google/cloud/openlineage/utils.py +19 -3
- airflow/providers/google/cloud/operators/alloy_db.py +76 -61
- airflow/providers/google/cloud/operators/bigquery.py +104 -667
- airflow/providers/google/cloud/operators/bigquery_dts.py +12 -12
- airflow/providers/google/cloud/operators/bigtable.py +38 -7
- airflow/providers/google/cloud/operators/cloud_base.py +22 -1
- airflow/providers/google/cloud/operators/cloud_batch.py +18 -18
- airflow/providers/google/cloud/operators/cloud_build.py +80 -36
- airflow/providers/google/cloud/operators/cloud_composer.py +157 -71
- airflow/providers/google/cloud/operators/cloud_logging_sink.py +341 -0
- airflow/providers/google/cloud/operators/cloud_memorystore.py +74 -46
- airflow/providers/google/cloud/operators/cloud_run.py +39 -20
- airflow/providers/google/cloud/operators/cloud_sql.py +46 -61
- airflow/providers/google/cloud/operators/cloud_storage_transfer_service.py +92 -14
- airflow/providers/google/cloud/operators/compute.py +18 -50
- airflow/providers/google/cloud/operators/datacatalog.py +167 -29
- airflow/providers/google/cloud/operators/dataflow.py +38 -15
- airflow/providers/google/cloud/operators/dataform.py +19 -7
- airflow/providers/google/cloud/operators/datafusion.py +43 -43
- airflow/providers/google/cloud/operators/dataplex.py +212 -126
- airflow/providers/google/cloud/operators/dataprep.py +1 -5
- airflow/providers/google/cloud/operators/dataproc.py +134 -207
- airflow/providers/google/cloud/operators/dataproc_metastore.py +102 -84
- airflow/providers/google/cloud/operators/datastore.py +22 -6
- airflow/providers/google/cloud/operators/dlp.py +24 -45
- airflow/providers/google/cloud/operators/functions.py +21 -14
- airflow/providers/google/cloud/operators/gcs.py +15 -12
- airflow/providers/google/cloud/operators/gen_ai.py +389 -0
- airflow/providers/google/cloud/operators/kubernetes_engine.py +115 -106
- airflow/providers/google/cloud/operators/looker.py +1 -1
- airflow/providers/google/cloud/operators/managed_kafka.py +362 -40
- airflow/providers/google/cloud/operators/natural_language.py +5 -3
- airflow/providers/google/cloud/operators/pubsub.py +69 -21
- airflow/providers/google/cloud/operators/spanner.py +53 -45
- airflow/providers/google/cloud/operators/speech_to_text.py +5 -4
- airflow/providers/google/cloud/operators/stackdriver.py +5 -11
- airflow/providers/google/cloud/operators/tasks.py +6 -15
- airflow/providers/google/cloud/operators/text_to_speech.py +4 -3
- airflow/providers/google/cloud/operators/translate.py +46 -20
- airflow/providers/google/cloud/operators/translate_speech.py +4 -3
- airflow/providers/google/cloud/operators/vertex_ai/auto_ml.py +44 -34
- airflow/providers/google/cloud/operators/vertex_ai/batch_prediction_job.py +34 -12
- airflow/providers/google/cloud/operators/vertex_ai/custom_job.py +62 -53
- airflow/providers/google/cloud/operators/vertex_ai/dataset.py +75 -11
- airflow/providers/google/cloud/operators/vertex_ai/endpoint_service.py +48 -12
- airflow/providers/google/cloud/operators/vertex_ai/experiment_service.py +435 -0
- airflow/providers/google/cloud/operators/vertex_ai/feature_store.py +532 -1
- airflow/providers/google/cloud/operators/vertex_ai/generative_model.py +135 -116
- airflow/providers/google/cloud/operators/vertex_ai/hyperparameter_tuning_job.py +16 -12
- airflow/providers/google/cloud/operators/vertex_ai/model_service.py +62 -14
- airflow/providers/google/cloud/operators/vertex_ai/pipeline_job.py +35 -10
- airflow/providers/google/cloud/operators/vertex_ai/ray.py +393 -0
- airflow/providers/google/cloud/operators/video_intelligence.py +5 -3
- airflow/providers/google/cloud/operators/vision.py +7 -5
- airflow/providers/google/cloud/operators/workflows.py +24 -19
- airflow/providers/google/cloud/secrets/secret_manager.py +2 -1
- airflow/providers/google/cloud/sensors/bigquery.py +2 -2
- airflow/providers/google/cloud/sensors/bigquery_dts.py +6 -4
- airflow/providers/google/cloud/sensors/bigtable.py +14 -6
- airflow/providers/google/cloud/sensors/cloud_composer.py +535 -33
- airflow/providers/google/cloud/sensors/cloud_storage_transfer_service.py +6 -5
- airflow/providers/google/cloud/sensors/dataflow.py +27 -10
- airflow/providers/google/cloud/sensors/dataform.py +2 -2
- airflow/providers/google/cloud/sensors/datafusion.py +4 -4
- airflow/providers/google/cloud/sensors/dataplex.py +7 -5
- airflow/providers/google/cloud/sensors/dataprep.py +2 -2
- airflow/providers/google/cloud/sensors/dataproc.py +10 -9
- airflow/providers/google/cloud/sensors/dataproc_metastore.py +4 -3
- airflow/providers/google/cloud/sensors/gcs.py +22 -21
- airflow/providers/google/cloud/sensors/looker.py +5 -5
- airflow/providers/google/cloud/sensors/pubsub.py +20 -20
- airflow/providers/google/cloud/sensors/tasks.py +2 -2
- airflow/providers/google/cloud/sensors/vertex_ai/feature_store.py +2 -2
- airflow/providers/google/cloud/sensors/workflows.py +6 -4
- airflow/providers/google/cloud/transfers/adls_to_gcs.py +1 -1
- airflow/providers/google/cloud/transfers/azure_blob_to_gcs.py +2 -2
- airflow/providers/google/cloud/transfers/azure_fileshare_to_gcs.py +2 -2
- airflow/providers/google/cloud/transfers/bigquery_to_bigquery.py +11 -8
- airflow/providers/google/cloud/transfers/bigquery_to_gcs.py +14 -13
- airflow/providers/google/cloud/transfers/bigquery_to_mssql.py +7 -3
- airflow/providers/google/cloud/transfers/bigquery_to_mysql.py +12 -1
- airflow/providers/google/cloud/transfers/bigquery_to_postgres.py +24 -10
- airflow/providers/google/cloud/transfers/bigquery_to_sql.py +104 -5
- airflow/providers/google/cloud/transfers/calendar_to_gcs.py +1 -1
- airflow/providers/google/cloud/transfers/cassandra_to_gcs.py +18 -22
- airflow/providers/google/cloud/transfers/facebook_ads_to_gcs.py +4 -5
- airflow/providers/google/cloud/transfers/gcs_to_bigquery.py +45 -38
- airflow/providers/google/cloud/transfers/gcs_to_gcs.py +2 -2
- airflow/providers/google/cloud/transfers/gcs_to_local.py +5 -3
- airflow/providers/google/cloud/transfers/gcs_to_sftp.py +10 -4
- airflow/providers/google/cloud/transfers/gdrive_to_gcs.py +6 -2
- airflow/providers/google/cloud/transfers/gdrive_to_local.py +2 -2
- airflow/providers/google/cloud/transfers/http_to_gcs.py +193 -0
- airflow/providers/google/cloud/transfers/local_to_gcs.py +2 -2
- airflow/providers/google/cloud/transfers/mssql_to_gcs.py +1 -1
- airflow/providers/google/cloud/transfers/oracle_to_gcs.py +36 -11
- airflow/providers/google/cloud/transfers/postgres_to_gcs.py +44 -12
- airflow/providers/google/cloud/transfers/s3_to_gcs.py +12 -6
- airflow/providers/google/cloud/transfers/salesforce_to_gcs.py +2 -2
- airflow/providers/google/cloud/transfers/sftp_to_gcs.py +36 -14
- airflow/providers/google/cloud/transfers/sheets_to_gcs.py +3 -3
- airflow/providers/google/cloud/transfers/sql_to_gcs.py +10 -10
- airflow/providers/google/cloud/triggers/bigquery.py +75 -34
- airflow/providers/google/cloud/triggers/bigquery_dts.py +2 -1
- airflow/providers/google/cloud/triggers/cloud_batch.py +2 -1
- airflow/providers/google/cloud/triggers/cloud_build.py +3 -2
- airflow/providers/google/cloud/triggers/cloud_composer.py +303 -47
- airflow/providers/google/cloud/triggers/cloud_run.py +2 -2
- airflow/providers/google/cloud/triggers/cloud_storage_transfer_service.py +96 -5
- airflow/providers/google/cloud/triggers/dataflow.py +125 -2
- airflow/providers/google/cloud/triggers/datafusion.py +1 -1
- airflow/providers/google/cloud/triggers/dataplex.py +16 -3
- airflow/providers/google/cloud/triggers/dataproc.py +124 -53
- airflow/providers/google/cloud/triggers/kubernetes_engine.py +46 -28
- airflow/providers/google/cloud/triggers/mlengine.py +1 -1
- airflow/providers/google/cloud/triggers/pubsub.py +17 -20
- airflow/providers/google/cloud/triggers/vertex_ai.py +8 -7
- airflow/providers/google/cloud/utils/bigquery.py +5 -7
- airflow/providers/google/cloud/utils/bigquery_get_data.py +1 -1
- airflow/providers/google/cloud/utils/credentials_provider.py +4 -3
- airflow/providers/google/cloud/utils/dataform.py +1 -1
- airflow/providers/google/cloud/utils/external_token_supplier.py +0 -1
- airflow/providers/google/cloud/utils/field_validator.py +1 -2
- airflow/providers/google/cloud/utils/validators.py +43 -0
- airflow/providers/google/common/auth_backend/google_openid.py +26 -9
- airflow/providers/google/common/consts.py +2 -1
- airflow/providers/google/common/deprecated.py +2 -1
- airflow/providers/google/common/hooks/base_google.py +40 -43
- airflow/providers/google/common/hooks/operation_helpers.py +78 -0
- airflow/providers/google/common/links/storage.py +0 -22
- airflow/providers/google/common/utils/get_secret.py +31 -0
- airflow/providers/google/common/utils/id_token_credentials.py +4 -5
- airflow/providers/google/firebase/operators/firestore.py +2 -2
- airflow/providers/google/get_provider_info.py +61 -216
- airflow/providers/google/go_module_utils.py +35 -3
- airflow/providers/google/leveldb/hooks/leveldb.py +30 -6
- airflow/providers/google/leveldb/operators/leveldb.py +2 -2
- airflow/providers/google/marketing_platform/hooks/analytics_admin.py +3 -2
- airflow/providers/google/marketing_platform/hooks/display_video.py +3 -109
- airflow/providers/google/marketing_platform/hooks/search_ads.py +1 -1
- airflow/providers/google/marketing_platform/links/analytics_admin.py +4 -5
- airflow/providers/google/marketing_platform/operators/analytics_admin.py +7 -6
- airflow/providers/google/marketing_platform/operators/campaign_manager.py +5 -5
- airflow/providers/google/marketing_platform/operators/display_video.py +28 -489
- airflow/providers/google/marketing_platform/operators/search_ads.py +2 -2
- airflow/providers/google/marketing_platform/sensors/campaign_manager.py +2 -2
- airflow/providers/google/marketing_platform/sensors/display_video.py +4 -64
- airflow/providers/google/suite/hooks/calendar.py +1 -1
- airflow/providers/google/suite/hooks/drive.py +2 -2
- airflow/providers/google/suite/hooks/sheets.py +15 -1
- airflow/providers/google/suite/operators/sheets.py +8 -3
- airflow/providers/google/suite/sensors/drive.py +2 -2
- airflow/providers/google/suite/transfers/gcs_to_gdrive.py +2 -2
- airflow/providers/google/suite/transfers/gcs_to_sheets.py +1 -1
- airflow/providers/google/suite/transfers/local_to_drive.py +3 -3
- airflow/providers/google/suite/transfers/sql_to_sheets.py +5 -4
- airflow/providers/google/version_compat.py +15 -1
- {apache_airflow_providers_google-14.0.0.dist-info → apache_airflow_providers_google-19.1.0rc1.dist-info}/METADATA +117 -72
- apache_airflow_providers_google-19.1.0rc1.dist-info/RECORD +331 -0
- {apache_airflow_providers_google-14.0.0.dist-info → apache_airflow_providers_google-19.1.0rc1.dist-info}/WHEEL +1 -1
- apache_airflow_providers_google-19.1.0rc1.dist-info/licenses/NOTICE +5 -0
- airflow/providers/google/cloud/example_dags/example_cloud_task.py +0 -54
- airflow/providers/google/cloud/hooks/automl.py +0 -679
- airflow/providers/google/cloud/hooks/life_sciences.py +0 -159
- airflow/providers/google/cloud/links/automl.py +0 -193
- airflow/providers/google/cloud/operators/automl.py +0 -1360
- airflow/providers/google/cloud/operators/life_sciences.py +0 -119
- airflow/providers/google/cloud/operators/mlengine.py +0 -1515
- airflow/providers/google/cloud/utils/mlengine_operator_utils.py +0 -273
- apache_airflow_providers_google-14.0.0.dist-info/RECORD +0 -318
- /airflow/providers/google/cloud/{example_dags → bundles}/__init__.py +0 -0
- {apache_airflow_providers_google-14.0.0.dist-info → apache_airflow_providers_google-19.1.0rc1.dist-info}/entry_points.txt +0 -0
- {airflow/providers/google → apache_airflow_providers_google-19.1.0rc1.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
# or more contributor license agreements. See the NOTICE file
|
|
4
|
+
# distributed with this work for additional information
|
|
5
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
# to you under the Apache License, Version 2.0 (the
|
|
7
|
+
# "License"); you may not use this file except in compliance
|
|
8
|
+
# with the License. You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing,
|
|
13
|
+
# software distributed under the License is distributed on an
|
|
14
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
# KIND, either express or implied. See the License for the
|
|
16
|
+
# specific language governing permissions and limitations
|
|
17
|
+
# under the License.
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from collections.abc import Sequence
|
|
21
|
+
from typing import TYPE_CHECKING
|
|
22
|
+
|
|
23
|
+
from google.cloud.logging_v2.services.config_service_v2 import ConfigServiceV2Client
|
|
24
|
+
from google.cloud.logging_v2.types import (
|
|
25
|
+
CreateSinkRequest,
|
|
26
|
+
DeleteSinkRequest,
|
|
27
|
+
GetSinkRequest,
|
|
28
|
+
ListSinksRequest,
|
|
29
|
+
LogSink,
|
|
30
|
+
UpdateSinkRequest,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from airflow.providers.google.common.consts import CLIENT_INFO
|
|
34
|
+
from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID, GoogleBaseHook
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from google.protobuf.field_mask_pb2 import FieldMask
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CloudLoggingHook(GoogleBaseHook):
|
|
41
|
+
"""
|
|
42
|
+
Hook for Google Cloud Logging Log Sinks API.
|
|
43
|
+
|
|
44
|
+
:param gcp_conn_id: The connection ID to use when fetching connection info.
|
|
45
|
+
:param impersonation_chain: Optional service account to impersonate.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
gcp_conn_id: str = "google_cloud_default",
|
|
51
|
+
impersonation_chain: str | Sequence[str] | None = None,
|
|
52
|
+
**kwargs,
|
|
53
|
+
) -> None:
|
|
54
|
+
super().__init__(gcp_conn_id=gcp_conn_id, impersonation_chain=impersonation_chain, **kwargs)
|
|
55
|
+
self._client: ConfigServiceV2Client | None = None
|
|
56
|
+
|
|
57
|
+
def get_conn(self) -> ConfigServiceV2Client:
|
|
58
|
+
"""Return the Google Cloud Logging Config client."""
|
|
59
|
+
if not self._client:
|
|
60
|
+
self._client = ConfigServiceV2Client(credentials=self.get_credentials(), client_info=CLIENT_INFO)
|
|
61
|
+
return self._client
|
|
62
|
+
|
|
63
|
+
def get_parent(self, project_id):
|
|
64
|
+
return f"projects/{project_id}"
|
|
65
|
+
|
|
66
|
+
@GoogleBaseHook.fallback_to_default_project_id
|
|
67
|
+
def create_sink(
|
|
68
|
+
self, sink: LogSink | dict, unique_writer_identity: bool = True, project_id: str = PROVIDE_PROJECT_ID
|
|
69
|
+
) -> LogSink:
|
|
70
|
+
if isinstance(sink, dict):
|
|
71
|
+
sink = LogSink(**sink)
|
|
72
|
+
request = CreateSinkRequest(
|
|
73
|
+
parent=self.get_parent(project_id), sink=sink, unique_writer_identity=unique_writer_identity
|
|
74
|
+
)
|
|
75
|
+
return self.get_conn().create_sink(request=request)
|
|
76
|
+
|
|
77
|
+
@GoogleBaseHook.fallback_to_default_project_id
|
|
78
|
+
def get_sink(self, sink_name: str, project_id: str = PROVIDE_PROJECT_ID) -> LogSink:
|
|
79
|
+
request = GetSinkRequest(sink_name=f"projects/{project_id}/sinks/{sink_name}")
|
|
80
|
+
return self.get_conn().get_sink(request=request)
|
|
81
|
+
|
|
82
|
+
@GoogleBaseHook.fallback_to_default_project_id
|
|
83
|
+
def list_sinks(self, page_size: int | None = None, project_id: str = PROVIDE_PROJECT_ID) -> list[LogSink]:
|
|
84
|
+
request = ListSinksRequest(parent=self.get_parent(project_id), page_size=page_size)
|
|
85
|
+
return list(self.get_conn().list_sinks(request=request))
|
|
86
|
+
|
|
87
|
+
@GoogleBaseHook.fallback_to_default_project_id
|
|
88
|
+
def delete_sink(self, sink_name: str, project_id: str = PROVIDE_PROJECT_ID) -> None:
|
|
89
|
+
request = DeleteSinkRequest(sink_name=f"projects/{project_id}/sinks/{sink_name}")
|
|
90
|
+
self.get_conn().delete_sink(request=request)
|
|
91
|
+
|
|
92
|
+
@GoogleBaseHook.fallback_to_default_project_id
|
|
93
|
+
def update_sink(
|
|
94
|
+
self,
|
|
95
|
+
sink_name: str,
|
|
96
|
+
sink: LogSink | dict,
|
|
97
|
+
unique_writer_identity: bool,
|
|
98
|
+
update_mask: FieldMask | dict,
|
|
99
|
+
project_id: str = PROVIDE_PROJECT_ID,
|
|
100
|
+
) -> LogSink:
|
|
101
|
+
if isinstance(sink, dict):
|
|
102
|
+
sink = LogSink(**sink)
|
|
103
|
+
request = UpdateSinkRequest(
|
|
104
|
+
sink_name=f"projects/{project_id}/sinks/{sink_name}",
|
|
105
|
+
sink=sink,
|
|
106
|
+
unique_writer_identity=unique_writer_identity,
|
|
107
|
+
update_mask=update_mask,
|
|
108
|
+
)
|
|
109
|
+
return self.get_conn().update_sink(request=request)
|
|
@@ -31,9 +31,6 @@ from __future__ import annotations
|
|
|
31
31
|
from collections.abc import Sequence
|
|
32
32
|
from typing import TYPE_CHECKING
|
|
33
33
|
|
|
34
|
-
from airflow import version
|
|
35
|
-
from airflow.exceptions import AirflowException
|
|
36
|
-
from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID, GoogleBaseHook
|
|
37
34
|
from google.api_core import path_template
|
|
38
35
|
from google.api_core.exceptions import NotFound
|
|
39
36
|
from google.api_core.gapic_v1.method import DEFAULT, _MethodDefault
|
|
@@ -47,6 +44,10 @@ from google.cloud.redis_v1 import (
|
|
|
47
44
|
OutputConfig,
|
|
48
45
|
)
|
|
49
46
|
|
|
47
|
+
from airflow import version
|
|
48
|
+
from airflow.exceptions import AirflowException
|
|
49
|
+
from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID, GoogleBaseHook
|
|
50
|
+
|
|
50
51
|
if TYPE_CHECKING:
|
|
51
52
|
from google.api_core.retry import Retry
|
|
52
53
|
from google.protobuf.field_mask_pb2 import FieldMask
|
|
@@ -21,9 +21,6 @@ import itertools
|
|
|
21
21
|
from collections.abc import Iterable, Sequence
|
|
22
22
|
from typing import TYPE_CHECKING, Any
|
|
23
23
|
|
|
24
|
-
from airflow.exceptions import AirflowException
|
|
25
|
-
from airflow.providers.google.common.consts import CLIENT_INFO
|
|
26
|
-
from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID, GoogleBaseHook
|
|
27
24
|
from google.cloud.run_v2 import (
|
|
28
25
|
CreateJobRequest,
|
|
29
26
|
CreateServiceRequest,
|
|
@@ -41,7 +38,15 @@ from google.cloud.run_v2 import (
|
|
|
41
38
|
ServicesClient,
|
|
42
39
|
UpdateJobRequest,
|
|
43
40
|
)
|
|
44
|
-
from google.longrunning import operations_pb2
|
|
41
|
+
from google.longrunning import operations_pb2
|
|
42
|
+
|
|
43
|
+
from airflow.exceptions import AirflowException
|
|
44
|
+
from airflow.providers.google.common.consts import CLIENT_INFO
|
|
45
|
+
from airflow.providers.google.common.hooks.base_google import (
|
|
46
|
+
PROVIDE_PROJECT_ID,
|
|
47
|
+
GoogleBaseAsyncHook,
|
|
48
|
+
GoogleBaseHook,
|
|
49
|
+
)
|
|
45
50
|
|
|
46
51
|
if TYPE_CHECKING:
|
|
47
52
|
from google.api_core import operation
|
|
@@ -158,7 +163,7 @@ class CloudRunHook(GoogleBaseHook):
|
|
|
158
163
|
return list(itertools.islice(jobs, limit))
|
|
159
164
|
|
|
160
165
|
|
|
161
|
-
class CloudRunAsyncHook(
|
|
166
|
+
class CloudRunAsyncHook(GoogleBaseAsyncHook):
|
|
162
167
|
"""
|
|
163
168
|
Async hook for the Google Cloud Run service.
|
|
164
169
|
|
|
@@ -173,6 +178,8 @@ class CloudRunAsyncHook(GoogleBaseHook):
|
|
|
173
178
|
account from the list granting this role to the originating account.
|
|
174
179
|
"""
|
|
175
180
|
|
|
181
|
+
sync_hook_class = CloudRunHook
|
|
182
|
+
|
|
176
183
|
def __init__(
|
|
177
184
|
self,
|
|
178
185
|
gcp_conn_id: str = "google_cloud_default",
|
|
@@ -182,16 +189,16 @@ class CloudRunAsyncHook(GoogleBaseHook):
|
|
|
182
189
|
self._client: JobsAsyncClient | None = None
|
|
183
190
|
super().__init__(gcp_conn_id=gcp_conn_id, impersonation_chain=impersonation_chain, **kwargs)
|
|
184
191
|
|
|
185
|
-
def get_conn(self):
|
|
192
|
+
async def get_conn(self):
|
|
186
193
|
if self._client is None:
|
|
187
|
-
|
|
194
|
+
sync_hook = await self.get_sync_hook()
|
|
195
|
+
self._client = JobsAsyncClient(credentials=sync_hook.get_credentials(), client_info=CLIENT_INFO)
|
|
188
196
|
|
|
189
197
|
return self._client
|
|
190
198
|
|
|
191
199
|
async def get_operation(self, operation_name: str) -> operations_pb2.Operation:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
)
|
|
200
|
+
conn = await self.get_conn()
|
|
201
|
+
return await conn.get_operation(operations_pb2.GetOperationRequest(name=operation_name), timeout=120)
|
|
195
202
|
|
|
196
203
|
|
|
197
204
|
class CloudRunServiceHook(GoogleBaseHook):
|
|
@@ -257,7 +264,7 @@ class CloudRunServiceHook(GoogleBaseHook):
|
|
|
257
264
|
return operation.result()
|
|
258
265
|
|
|
259
266
|
|
|
260
|
-
class CloudRunServiceAsyncHook(
|
|
267
|
+
class CloudRunServiceAsyncHook(GoogleBaseAsyncHook):
|
|
261
268
|
"""
|
|
262
269
|
Async hook for the Google Cloud Run services.
|
|
263
270
|
|
|
@@ -272,6 +279,8 @@ class CloudRunServiceAsyncHook(GoogleBaseHook):
|
|
|
272
279
|
account from the list granting this role to the originating account.
|
|
273
280
|
"""
|
|
274
281
|
|
|
282
|
+
sync_hook_class = CloudRunServiceHook
|
|
283
|
+
|
|
275
284
|
def __init__(
|
|
276
285
|
self,
|
|
277
286
|
gcp_conn_id: str = "google_cloud_default",
|
|
@@ -38,7 +38,7 @@ from inspect import signature
|
|
|
38
38
|
from pathlib import Path
|
|
39
39
|
from subprocess import PIPE, Popen
|
|
40
40
|
from tempfile import NamedTemporaryFile, _TemporaryFileWrapper, gettempdir
|
|
41
|
-
from typing import TYPE_CHECKING, Any
|
|
41
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
42
42
|
from urllib.parse import quote_plus
|
|
43
43
|
|
|
44
44
|
import httpx
|
|
@@ -50,8 +50,14 @@ from googleapiclient.errors import HttpError
|
|
|
50
50
|
# Number of retries - used by googleapiclient method calls to perform retries
|
|
51
51
|
# For requests that are "retriable"
|
|
52
52
|
from airflow.exceptions import AirflowException
|
|
53
|
-
from airflow.
|
|
54
|
-
|
|
53
|
+
from airflow.providers.google.version_compat import AIRFLOW_V_3_1_PLUS
|
|
54
|
+
|
|
55
|
+
if AIRFLOW_V_3_1_PLUS:
|
|
56
|
+
from airflow.sdk import Connection
|
|
57
|
+
else:
|
|
58
|
+
from airflow.models import Connection # type: ignore[assignment,attr-defined,no-redef]
|
|
59
|
+
|
|
60
|
+
from airflow.providers.common.compat.sdk import BaseHook
|
|
55
61
|
from airflow.providers.google.cloud.hooks.secret_manager import (
|
|
56
62
|
GoogleCloudSecretManagerHook,
|
|
57
63
|
)
|
|
@@ -64,10 +70,10 @@ from airflow.providers.google.common.hooks.base_google import (
|
|
|
64
70
|
from airflow.utils.log.logging_mixin import LoggingMixin
|
|
65
71
|
|
|
66
72
|
if TYPE_CHECKING:
|
|
73
|
+
from google.cloud.secretmanager_v1 import AccessSecretVersionResponse
|
|
67
74
|
from requests import Session
|
|
68
75
|
|
|
69
76
|
from airflow.providers.common.sql.hooks.sql import DbApiHook
|
|
70
|
-
from google.cloud.secretmanager_v1 import AccessSecretVersionResponse
|
|
71
77
|
|
|
72
78
|
UNIX_PATH_MAX = 108
|
|
73
79
|
|
|
@@ -502,7 +508,7 @@ class CloudSqlProxyRunner(LoggingMixin):
|
|
|
502
508
|
:param project_id: Optional id of the Google Cloud project to connect to - it overwrites
|
|
503
509
|
default project id taken from the Google Cloud connection.
|
|
504
510
|
:param sql_proxy_version: Specific version of SQL proxy to download
|
|
505
|
-
(for example 'v1.13'). By default latest version is downloaded.
|
|
511
|
+
(for example 'v1.13'). By default, latest version is downloaded.
|
|
506
512
|
:param sql_proxy_binary_path: If specified, then proxy will be
|
|
507
513
|
used from the path specified rather than dynamically generated. This means
|
|
508
514
|
that if the binary is not present in that path it will also be downloaded.
|
|
@@ -634,35 +640,32 @@ class CloudSqlProxyRunner(LoggingMixin):
|
|
|
634
640
|
self._download_sql_proxy_if_needed()
|
|
635
641
|
if self.sql_proxy_process:
|
|
636
642
|
raise AirflowException(f"The sql proxy is already running: {self.sql_proxy_process}")
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
raise AirflowException(f"Error when starting the cloud_sql_proxy {line}!")
|
|
664
|
-
if "Ready for new connections" in line:
|
|
665
|
-
return
|
|
643
|
+
command_to_run = [self.sql_proxy_path]
|
|
644
|
+
command_to_run.extend(self.command_line_parameters)
|
|
645
|
+
self.log.info("Creating directory %s", self.cloud_sql_proxy_socket_directory)
|
|
646
|
+
Path(self.cloud_sql_proxy_socket_directory).mkdir(parents=True, exist_ok=True)
|
|
647
|
+
command_to_run.extend(self._get_credential_parameters())
|
|
648
|
+
self.log.info("Running the command: `%s`", " ".join(command_to_run))
|
|
649
|
+
|
|
650
|
+
self.sql_proxy_process = Popen(command_to_run, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
|
651
|
+
self.log.info("The pid of cloud_sql_proxy: %s", self.sql_proxy_process.pid)
|
|
652
|
+
while True:
|
|
653
|
+
line = (
|
|
654
|
+
self.sql_proxy_process.stderr.readline().decode("utf-8")
|
|
655
|
+
if self.sql_proxy_process.stderr
|
|
656
|
+
else ""
|
|
657
|
+
)
|
|
658
|
+
return_code = self.sql_proxy_process.poll()
|
|
659
|
+
if line == "" and return_code is not None:
|
|
660
|
+
self.sql_proxy_process = None
|
|
661
|
+
raise AirflowException(f"The cloud_sql_proxy finished early with return code {return_code}!")
|
|
662
|
+
if line != "":
|
|
663
|
+
self.log.info(line)
|
|
664
|
+
if "googleapi: Error" in line or "invalid instance name:" in line:
|
|
665
|
+
self.stop_proxy()
|
|
666
|
+
raise AirflowException(f"Error when starting the cloud_sql_proxy {line}!")
|
|
667
|
+
if "Ready for new connections" in line:
|
|
668
|
+
return
|
|
666
669
|
|
|
667
670
|
def stop_proxy(self) -> None:
|
|
668
671
|
"""
|
|
@@ -672,10 +675,9 @@ class CloudSqlProxyRunner(LoggingMixin):
|
|
|
672
675
|
"""
|
|
673
676
|
if not self.sql_proxy_process:
|
|
674
677
|
raise AirflowException("The sql proxy is not started yet")
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
self.sql_proxy_process = None
|
|
678
|
+
self.log.info("Stopping the cloud_sql_proxy pid: %s", self.sql_proxy_process.pid)
|
|
679
|
+
self.sql_proxy_process.kill()
|
|
680
|
+
self.sql_proxy_process = None
|
|
679
681
|
# Cleanup!
|
|
680
682
|
self.log.info("Removing the socket directory: %s", self.cloud_sql_proxy_socket_directory)
|
|
681
683
|
shutil.rmtree(self.cloud_sql_proxy_socket_directory, ignore_errors=True)
|
|
@@ -691,7 +693,7 @@ class CloudSqlProxyRunner(LoggingMixin):
|
|
|
691
693
|
self.log.info("Skipped removing proxy - it was not downloaded: %s", self.sql_proxy_path)
|
|
692
694
|
if os.path.isfile(self.credentials_path):
|
|
693
695
|
self.log.info("Removing generated credentials file %s", self.credentials_path)
|
|
694
|
-
# Here file cannot be
|
|
696
|
+
# Here file cannot be deleted by concurrent task (each task has its own copy)
|
|
695
697
|
os.remove(self.credentials_path)
|
|
696
698
|
|
|
697
699
|
def get_proxy_version(self) -> str | None:
|
|
@@ -704,8 +706,7 @@ class CloudSqlProxyRunner(LoggingMixin):
|
|
|
704
706
|
matched = re.search("[Vv]ersion (.*?);", result)
|
|
705
707
|
if matched:
|
|
706
708
|
return matched.group(1)
|
|
707
|
-
|
|
708
|
-
return None
|
|
709
|
+
return None
|
|
709
710
|
|
|
710
711
|
def get_socket_path(self) -> str:
|
|
711
712
|
"""
|
|
@@ -852,8 +853,8 @@ class CloudSQLDatabaseHook(BaseHook):
|
|
|
852
853
|
self.user = self._get_iam_db_login()
|
|
853
854
|
self.password = self._generate_login_token(service_account=self.cloudsql_connection.login)
|
|
854
855
|
else:
|
|
855
|
-
self.user = self.cloudsql_connection.login
|
|
856
|
-
self.password = self.cloudsql_connection.password
|
|
856
|
+
self.user = cast("str", self.cloudsql_connection.login)
|
|
857
|
+
self.password = cast("str", self.cloudsql_connection.password)
|
|
857
858
|
self.public_ip = self.cloudsql_connection.host
|
|
858
859
|
self.public_port = self.cloudsql_connection.port
|
|
859
860
|
self.ssl_cert = ssl_cert
|
|
@@ -908,10 +909,9 @@ class CloudSQLDatabaseHook(BaseHook):
|
|
|
908
909
|
secret_data = json.loads(base64.b64decode(secret.payload.data))
|
|
909
910
|
if cert_name in secret_data:
|
|
910
911
|
return secret_data[cert_name]
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
)
|
|
912
|
+
raise AirflowException(
|
|
913
|
+
"Invalid secret format. Expected dictionary with keys: `sslcert`, `sslkey`, `sslrootcert`"
|
|
914
|
+
)
|
|
915
915
|
|
|
916
916
|
def _set_temporary_ssl_file(
|
|
917
917
|
self, cert_name: str, cert_path: str | None = None, cert_value: str | None = None
|
|
@@ -1051,15 +1051,26 @@ class CloudSQLDatabaseHook(BaseHook):
|
|
|
1051
1051
|
def _quote(value) -> str | None:
|
|
1052
1052
|
return quote_plus(value) if value else None
|
|
1053
1053
|
|
|
1054
|
-
def
|
|
1054
|
+
def _reserve_port(self):
|
|
1055
1055
|
if self.use_proxy:
|
|
1056
1056
|
if self.sql_proxy_use_tcp:
|
|
1057
1057
|
if not self.sql_proxy_tcp_port:
|
|
1058
1058
|
self.reserve_free_tcp_port()
|
|
1059
1059
|
if not self.sql_proxy_unique_path:
|
|
1060
1060
|
self.sql_proxy_unique_path = self._generate_unique_path()
|
|
1061
|
+
|
|
1062
|
+
def _generate_connection_uri(self) -> str:
|
|
1063
|
+
self._reserve_port()
|
|
1061
1064
|
if not self.database_type:
|
|
1062
1065
|
raise ValueError("The database_type should be set")
|
|
1066
|
+
if not self.user:
|
|
1067
|
+
raise AirflowException("The login parameter needs to be set in connection")
|
|
1068
|
+
if not self.public_ip:
|
|
1069
|
+
raise AirflowException("The location parameter needs to be set in connection")
|
|
1070
|
+
if not self.password:
|
|
1071
|
+
raise AirflowException("The password parameter needs to be set in connection")
|
|
1072
|
+
if not self.database:
|
|
1073
|
+
raise AirflowException("The database parameter needs to be set in connection")
|
|
1063
1074
|
|
|
1064
1075
|
database_uris = CONNECTION_URIS[self.database_type]
|
|
1065
1076
|
ssl_spec = None
|
|
@@ -1078,14 +1089,6 @@ class CloudSQLDatabaseHook(BaseHook):
|
|
|
1078
1089
|
ssl_spec = {"cert": self.sslcert, "key": self.sslkey, "ca": self.sslrootcert}
|
|
1079
1090
|
else:
|
|
1080
1091
|
format_string = public_uris["non-ssl"]
|
|
1081
|
-
if not self.user:
|
|
1082
|
-
raise AirflowException("The login parameter needs to be set in connection")
|
|
1083
|
-
if not self.public_ip:
|
|
1084
|
-
raise AirflowException("The location parameter needs to be set in connection")
|
|
1085
|
-
if not self.password:
|
|
1086
|
-
raise AirflowException("The password parameter needs to be set in connection")
|
|
1087
|
-
if not self.database:
|
|
1088
|
-
raise AirflowException("The database parameter needs to be set in connection")
|
|
1089
1092
|
|
|
1090
1093
|
connection_uri = format_string.format(
|
|
1091
1094
|
user=quote_plus(self.user) if self.user else "",
|
|
@@ -1109,6 +1112,8 @@ class CloudSQLDatabaseHook(BaseHook):
|
|
|
1109
1112
|
return connection_uri
|
|
1110
1113
|
|
|
1111
1114
|
def _get_instance_socket_name(self) -> str:
|
|
1115
|
+
if self.project_id is None:
|
|
1116
|
+
raise ValueError("The project_id should not be none")
|
|
1112
1117
|
return self.project_id + ":" + self.location + ":" + self.instance
|
|
1113
1118
|
|
|
1114
1119
|
def _get_sqlproxy_instance_specification(self) -> str:
|
|
@@ -1117,6 +1122,69 @@ class CloudSQLDatabaseHook(BaseHook):
|
|
|
1117
1122
|
instance_specification += f"=tcp:{self.sql_proxy_tcp_port}"
|
|
1118
1123
|
return instance_specification
|
|
1119
1124
|
|
|
1125
|
+
def _generate_connection_parameters(self) -> dict:
|
|
1126
|
+
self._reserve_port()
|
|
1127
|
+
if not self.database_type:
|
|
1128
|
+
raise ValueError("The database_type should be set")
|
|
1129
|
+
if not self.user:
|
|
1130
|
+
raise AirflowException("The login parameter needs to be set in connection")
|
|
1131
|
+
if not self.public_ip:
|
|
1132
|
+
raise AirflowException("The location parameter needs to be set in connection")
|
|
1133
|
+
if not self.password:
|
|
1134
|
+
raise AirflowException("The password parameter needs to be set in connection")
|
|
1135
|
+
if not self.database:
|
|
1136
|
+
raise AirflowException("The database parameter needs to be set in connection")
|
|
1137
|
+
|
|
1138
|
+
connection_parameters = {}
|
|
1139
|
+
|
|
1140
|
+
connection_parameters["conn_type"] = self.database_type
|
|
1141
|
+
connection_parameters["login"] = self.user
|
|
1142
|
+
connection_parameters["password"] = self.password
|
|
1143
|
+
connection_parameters["schema"] = self.database
|
|
1144
|
+
connection_parameters["extra"] = {}
|
|
1145
|
+
|
|
1146
|
+
database_uris = CONNECTION_URIS[self.database_type]
|
|
1147
|
+
if self.use_proxy:
|
|
1148
|
+
proxy_uris = database_uris["proxy"]
|
|
1149
|
+
if self.sql_proxy_use_tcp:
|
|
1150
|
+
connection_parameters["host"] = "127.0.0.1"
|
|
1151
|
+
connection_parameters["port"] = self.sql_proxy_tcp_port
|
|
1152
|
+
else:
|
|
1153
|
+
socket_path = f"{self.sql_proxy_unique_path}/{self._get_instance_socket_name()}"
|
|
1154
|
+
if "localhost" in proxy_uris["socket"]:
|
|
1155
|
+
connection_parameters["host"] = "localhost"
|
|
1156
|
+
connection_parameters["extra"].update({"unix_socket": socket_path})
|
|
1157
|
+
else:
|
|
1158
|
+
connection_parameters["host"] = socket_path
|
|
1159
|
+
else:
|
|
1160
|
+
public_uris = database_uris["public"]
|
|
1161
|
+
if self.use_ssl:
|
|
1162
|
+
connection_parameters["host"] = self.public_ip
|
|
1163
|
+
connection_parameters["port"] = self.public_port
|
|
1164
|
+
if "ssl_spec" in public_uris["ssl"]:
|
|
1165
|
+
connection_parameters["extra"].update(
|
|
1166
|
+
{
|
|
1167
|
+
"ssl": json.dumps(
|
|
1168
|
+
{"cert": self.sslcert, "key": self.sslkey, "ca": self.sslrootcert}
|
|
1169
|
+
)
|
|
1170
|
+
}
|
|
1171
|
+
)
|
|
1172
|
+
else:
|
|
1173
|
+
connection_parameters["extra"].update(
|
|
1174
|
+
{
|
|
1175
|
+
"sslmode": "verify-ca",
|
|
1176
|
+
"sslcert": self.sslcert,
|
|
1177
|
+
"sslkey": self.sslkey,
|
|
1178
|
+
"sslrootcert": self.sslrootcert,
|
|
1179
|
+
}
|
|
1180
|
+
)
|
|
1181
|
+
else:
|
|
1182
|
+
connection_parameters["host"] = self.public_ip
|
|
1183
|
+
connection_parameters["port"] = self.public_port
|
|
1184
|
+
if connection_parameters.get("extra"):
|
|
1185
|
+
connection_parameters["extra"] = json.dumps(connection_parameters["extra"])
|
|
1186
|
+
return connection_parameters
|
|
1187
|
+
|
|
1120
1188
|
def create_connection(self) -> Connection:
|
|
1121
1189
|
"""
|
|
1122
1190
|
Create a connection.
|
|
@@ -1124,8 +1192,11 @@ class CloudSQLDatabaseHook(BaseHook):
|
|
|
1124
1192
|
Connection ID will be randomly generated according to whether it uses
|
|
1125
1193
|
proxy, TCP, UNIX sockets, SSL.
|
|
1126
1194
|
"""
|
|
1127
|
-
|
|
1128
|
-
|
|
1195
|
+
if AIRFLOW_V_3_1_PLUS:
|
|
1196
|
+
kwargs = self._generate_connection_parameters()
|
|
1197
|
+
else:
|
|
1198
|
+
kwargs = {"uri": self._generate_connection_uri()}
|
|
1199
|
+
connection = Connection(conn_id=self.db_conn_id, **kwargs)
|
|
1129
1200
|
self.log.info("Creating connection %s", self.db_conn_id)
|
|
1130
1201
|
return connection
|
|
1131
1202
|
|
|
@@ -1141,6 +1212,8 @@ class CloudSQLDatabaseHook(BaseHook):
|
|
|
1141
1212
|
raise ValueError("Proxy runner can only be retrieved in case of use_proxy = True")
|
|
1142
1213
|
if not self.sql_proxy_unique_path:
|
|
1143
1214
|
raise ValueError("The sql_proxy_unique_path should be set")
|
|
1215
|
+
if self.project_id is None:
|
|
1216
|
+
raise ValueError("The project_id should not be None")
|
|
1144
1217
|
return CloudSqlProxyRunner(
|
|
1145
1218
|
path_prefix=self.sql_proxy_unique_path,
|
|
1146
1219
|
instance_specification=self._get_sqlproxy_instance_specification(),
|
|
@@ -1177,9 +1250,9 @@ class CloudSQLDatabaseHook(BaseHook):
|
|
|
1177
1250
|
raise ValueError("The db_hook should be set")
|
|
1178
1251
|
if not isinstance(self.db_hook, PostgresHook):
|
|
1179
1252
|
raise ValueError(f"The db_hook should be PostgresHook and is {type(self.db_hook)}")
|
|
1180
|
-
conn = getattr(self.db_hook, "conn")
|
|
1181
|
-
if conn and conn.notices:
|
|
1182
|
-
for output in
|
|
1253
|
+
conn = getattr(self.db_hook, "conn", None)
|
|
1254
|
+
if conn and hasattr(conn, "notices") and conn.notices:
|
|
1255
|
+
for output in conn.notices:
|
|
1183
1256
|
self.log.info(output)
|
|
1184
1257
|
|
|
1185
1258
|
def reserve_free_tcp_port(self) -> None:
|
|
@@ -1205,8 +1278,7 @@ class CloudSQLDatabaseHook(BaseHook):
|
|
|
1205
1278
|
|
|
1206
1279
|
if self.database_type == "postgres":
|
|
1207
1280
|
return self.cloudsql_connection.login.split(".gserviceaccount.com")[0]
|
|
1208
|
-
|
|
1209
|
-
return self.cloudsql_connection.login.split("@")[0]
|
|
1281
|
+
return self.cloudsql_connection.login.split("@")[0]
|
|
1210
1282
|
|
|
1211
1283
|
def _generate_login_token(self, service_account) -> str:
|
|
1212
1284
|
"""Generate an IAM login token for Cloud SQL and return the token."""
|
|
@@ -1215,7 +1287,7 @@ class CloudSQLDatabaseHook(BaseHook):
|
|
|
1215
1287
|
cloud_sql_hook = CloudSQLHook(api_version="v1", gcp_conn_id=self.gcp_conn_id)
|
|
1216
1288
|
|
|
1217
1289
|
with cloud_sql_hook.provide_authorized_gcloud():
|
|
1218
|
-
proc = subprocess.run(cmd, capture_output=True)
|
|
1290
|
+
proc = subprocess.run(cmd, check=False, capture_output=True)
|
|
1219
1291
|
|
|
1220
1292
|
if proc.returncode != 0:
|
|
1221
1293
|
stderr_last_20_lines = "\n".join(proc.stderr.decode().strip().splitlines()[-20:])
|
|
@@ -36,6 +36,14 @@ from copy import deepcopy
|
|
|
36
36
|
from datetime import timedelta
|
|
37
37
|
from typing import TYPE_CHECKING, Any
|
|
38
38
|
|
|
39
|
+
from google.cloud.storage_transfer_v1 import (
|
|
40
|
+
ListTransferJobsRequest,
|
|
41
|
+
RunTransferJobRequest,
|
|
42
|
+
StorageTransferServiceAsyncClient,
|
|
43
|
+
TransferJob,
|
|
44
|
+
TransferOperation,
|
|
45
|
+
)
|
|
46
|
+
from google.protobuf.json_format import MessageToDict
|
|
39
47
|
from googleapiclient.discovery import Resource, build
|
|
40
48
|
from googleapiclient.errors import HttpError
|
|
41
49
|
|
|
@@ -46,21 +54,14 @@ from airflow.providers.google.common.hooks.base_google import (
|
|
|
46
54
|
GoogleBaseAsyncHook,
|
|
47
55
|
GoogleBaseHook,
|
|
48
56
|
)
|
|
49
|
-
from google.api_core import protobuf_helpers
|
|
50
|
-
from google.cloud.storage_transfer_v1 import (
|
|
51
|
-
ListTransferJobsRequest,
|
|
52
|
-
StorageTransferServiceAsyncClient,
|
|
53
|
-
TransferJob,
|
|
54
|
-
TransferOperation,
|
|
55
|
-
)
|
|
56
57
|
|
|
57
58
|
if TYPE_CHECKING:
|
|
58
|
-
from
|
|
59
|
-
|
|
59
|
+
from google.api_core import operation_async
|
|
60
60
|
from google.cloud.storage_transfer_v1.services.storage_transfer_service.pagers import (
|
|
61
61
|
ListTransferJobsAsyncPager,
|
|
62
62
|
)
|
|
63
|
-
from google.longrunning import operations_pb2
|
|
63
|
+
from google.longrunning import operations_pb2
|
|
64
|
+
from proto import Message
|
|
64
65
|
|
|
65
66
|
log = logging.getLogger(__name__)
|
|
66
67
|
|
|
@@ -219,7 +220,7 @@ class CloudDataTransferServiceHook(GoogleBaseHook):
|
|
|
219
220
|
return (
|
|
220
221
|
self.get_conn().transferJobs().create(body=body).execute(num_retries=self.num_retries)
|
|
221
222
|
)
|
|
222
|
-
|
|
223
|
+
if transfer_job.get(STATUS) == GcpTransferJobsStatus.DISABLED:
|
|
223
224
|
return self.enable_transfer_job(job_name=job_name, project_id=body.get(PROJECT_ID))
|
|
224
225
|
else:
|
|
225
226
|
raise e
|
|
@@ -604,7 +605,7 @@ class CloudDataTransferServiceAsyncHook(GoogleBaseAsyncHook):
|
|
|
604
605
|
self,
|
|
605
606
|
request_filter: dict | None = None,
|
|
606
607
|
**kwargs,
|
|
607
|
-
) -> list[
|
|
608
|
+
) -> list[dict[str, Any]]:
|
|
608
609
|
"""
|
|
609
610
|
Get a transfer operation in Google Storage Transfer Service.
|
|
610
611
|
|
|
@@ -661,7 +662,12 @@ class CloudDataTransferServiceAsyncHook(GoogleBaseAsyncHook):
|
|
|
661
662
|
)
|
|
662
663
|
|
|
663
664
|
transfer_operations = [
|
|
664
|
-
|
|
665
|
+
MessageToDict(
|
|
666
|
+
getattr(op, "_pb", op),
|
|
667
|
+
preserving_proto_field_name=True,
|
|
668
|
+
use_integers_for_enums=True,
|
|
669
|
+
)
|
|
670
|
+
for op in operations
|
|
665
671
|
]
|
|
666
672
|
|
|
667
673
|
return transfer_operations
|
|
@@ -678,7 +684,7 @@ class CloudDataTransferServiceAsyncHook(GoogleBaseAsyncHook):
|
|
|
678
684
|
|
|
679
685
|
@staticmethod
|
|
680
686
|
async def operations_contain_expected_statuses(
|
|
681
|
-
operations: list[
|
|
687
|
+
operations: list[dict[str, Any]], expected_statuses: set[str] | str
|
|
682
688
|
) -> bool:
|
|
683
689
|
"""
|
|
684
690
|
Check whether an operation exists with the expected status.
|
|
@@ -697,7 +703,7 @@ class CloudDataTransferServiceAsyncHook(GoogleBaseAsyncHook):
|
|
|
697
703
|
if not operations:
|
|
698
704
|
return False
|
|
699
705
|
|
|
700
|
-
current_statuses = {
|
|
706
|
+
current_statuses = {TransferOperation.Status(op["metadata"]["status"]).name for op in operations}
|
|
701
707
|
|
|
702
708
|
if len(current_statuses - expected_statuses_set) != len(current_statuses):
|
|
703
709
|
return True
|
|
@@ -708,3 +714,17 @@ class CloudDataTransferServiceAsyncHook(GoogleBaseAsyncHook):
|
|
|
708
714
|
f"Expected: {', '.join(expected_statuses_set)}"
|
|
709
715
|
)
|
|
710
716
|
return False
|
|
717
|
+
|
|
718
|
+
async def run_transfer_job(self, job_name: str) -> operation_async.AsyncOperation:
|
|
719
|
+
"""
|
|
720
|
+
Run Google Storage Transfer Service job.
|
|
721
|
+
|
|
722
|
+
:param job_name: (Required) Name of the job to run.
|
|
723
|
+
"""
|
|
724
|
+
client = await self.get_conn()
|
|
725
|
+
request = RunTransferJobRequest(
|
|
726
|
+
job_name=job_name,
|
|
727
|
+
project_id=self.project_id,
|
|
728
|
+
)
|
|
729
|
+
operation = await client.run_transfer_job(request=request)
|
|
730
|
+
return operation
|