1818"""
1919Objects relating to sourcing connections from GCP Secrets Manager
2020"""
21- import re
2221from typing import Optional
2322
2423from cached_property import cached_property
25- from google .api_core .exceptions import NotFound
26- from google .api_core .gapic_v1 .client_info import ClientInfo
27- from google .cloud .secretmanager_v1 import SecretManagerServiceClient
2824
29- from airflow import version
3025from airflow .exceptions import AirflowException
31- from airflow .providers .google .cloud .utils .credentials_provider import (
32- _get_scopes , get_credentials_and_project_id ,
33- )
26+ from airflow .providers .google .cloud ._internal_client .secret_manager_client import _SecretManagerClient # noqa
27+ from airflow .providers .google .cloud .utils .credentials_provider import get_credentials_and_project_id
3428from airflow .secrets import BaseSecretsBackend
3529from airflow .utils .log .logging_mixin import LoggingMixin
3630
3731SECRET_ID_PATTERN = r"^[a-zA-Z0-9-_]*$"
3832
3933
40- class CloudSecretsManagerBackend (BaseSecretsBackend , LoggingMixin ):
34+ class CloudSecretManagerBackend (BaseSecretsBackend , LoggingMixin ):
4135 """
4236 Retrieves Connection object from GCP Secrets Manager
4337
@@ -46,11 +40,11 @@ class CloudSecretsManagerBackend(BaseSecretsBackend, LoggingMixin):
4640 .. code-block:: ini
4741
4842 [secrets]
49- backend = airflow.providers.google.cloud.secrets.secrets_manager.CloudSecretsManagerBackend
43+ backend = airflow.providers.google.cloud.secrets.secret_manager.CloudSecretManagerBackend
5044 backend_kwargs = {"connections_prefix": "airflow-connections", "sep": "-"}
5145
5246 For example, if the Secrets Manager secret id is ``airflow-connections-smtp_default``, this would be
53- accessiblen if you provide ``{"connections_prefix": "airflow-connections", "sep": "-"}`` and request
47+ accessible if you provide ``{"connections_prefix": "airflow-connections", "sep": "-"}`` and request
5448 conn_id ``smtp_default``.
5549
5650 If the Secrets Manager secret id is ``airflow-variables-hello``, this would be
@@ -63,60 +57,63 @@ class CloudSecretsManagerBackend(BaseSecretsBackend, LoggingMixin):
6357 :type connections_prefix: str
6458 :param variables_prefix: Specifies the prefix of the secret to read to get Variables.
6559 :type variables_prefix: str
66- :param gcp_key_path: Path to GCP Credential JSON file;
60+ :param gcp_key_path: Path to GCP Credential JSON file. Mutually exclusive with gcp_keyfile_dict.
6761 use default credentials in the current environment if not provided.
6862 :type gcp_key_path: str
63+ :param gcp_keyfile_dict: Dictionary of keyfile parameters. Mutually exclusive with gcp_key_path.
64+ :type gcp_keyfile_dict: dict
6965 :param gcp_scopes: Comma-separated string containing GCP scopes
7066 :type gcp_scopes: str
67+ :param project_id: Project id (if you want to override the project_id from credentials)
68+ :type project_id: str
7169 :param sep: separator used to concatenate connections_prefix and conn_id. Default: "-"
7270 :type sep: str
7371 """
7472 def __init__ (
7573 self ,
7674 connections_prefix : str = "airflow-connections" ,
7775 variables_prefix : str = "airflow-variables" ,
76+ gcp_keyfile_dict : Optional [dict ] = None ,
7877 gcp_key_path : Optional [str ] = None ,
7978 gcp_scopes : Optional [str ] = None ,
79+ project_id : Optional [str ] = None ,
8080 sep : str = "-" ,
8181 ** kwargs
8282 ):
8383 super ().__init__ (** kwargs )
8484 self .connections_prefix = connections_prefix
8585 self .variables_prefix = variables_prefix
86- self .gcp_key_path = gcp_key_path
87- self .gcp_scopes = gcp_scopes
8886 self .sep = sep
89- self .credentials : Optional [str ] = None
90- self .project_id : Optional [str ] = None
9187 if not self ._is_valid_prefix_and_sep ():
9288 raise AirflowException (
9389 "`connections_prefix`, `variables_prefix` and `sep` should "
9490 f"follows that pattern { SECRET_ID_PATTERN } "
9591 )
96-
97- def _is_valid_prefix_and_sep (self ) -> bool :
98- prefix = self .connections_prefix + self .sep
99- return bool (re .match (SECRET_ID_PATTERN , prefix ))
92+ self .credentials , self .project_id = get_credentials_and_project_id (
93+ keyfile_dict = gcp_keyfile_dict ,
94+ key_path = gcp_key_path ,
95+ scopes = gcp_scopes
96+ )
97+ # In case project id provided
98+ if project_id :
99+ self .project_id = project_id
100100
101101 @cached_property
102- def client (self ) -> SecretManagerServiceClient :
102+ def client (self ) -> _SecretManagerClient :
103103 """
104- Create an authenticated KMS client
104+ Cached property returning secret client.
105+
106+ :return: Secrets client
105107 """
106- scopes = _get_scopes (self .gcp_scopes )
107- self .credentials , self .project_id = get_credentials_and_project_id (
108- key_path = self .gcp_key_path ,
109- scopes = scopes
110- )
111- _client = SecretManagerServiceClient (
112- credentials = self .credentials ,
113- client_info = ClientInfo (client_library_version = 'airflow_v' + version .version )
114- )
115- return _client
108+ return _SecretManagerClient (credentials = self .credentials )
109+
110+ def _is_valid_prefix_and_sep (self ) -> bool :
111+ prefix = self .connections_prefix + self .sep
112+ return _SecretManagerClient .is_valid_secret_name (prefix )
116113
117114 def get_conn_uri (self , conn_id : str ) -> Optional [str ]:
118115 """
119- Get secret value from Secrets Manager .
116+ Get secret value from the SecretManager .
120117
121118 :param conn_id: connection id
122119 :type conn_id: str
@@ -134,23 +131,12 @@ def get_variable(self, key: str) -> Optional[str]:
134131
135132 def _get_secret (self , path_prefix : str , secret_id : str ) -> Optional [str ]:
136133 """
137- Get secret value from Parameter Store .
134+ Get secret value from the SecretManager based on prefix .
138135
139136 :param path_prefix: Prefix for the Path to get Secret
140137 :type path_prefix: str
141138 :param secret_id: Secret Key
142139 :type secret_id: str
143140 """
144141 secret_id = self .build_path (path_prefix , secret_id , self .sep )
145- # always return the latest version of the secret
146- secret_version = "latest"
147- name = self .client .secret_version_path (self .project_id , secret_id , secret_version )
148- try :
149- response = self .client .access_secret_version (name )
150- value = response .payload .data .decode ('UTF-8' )
151- return value
152- except NotFound :
153- self .log .error (
154- "GCP API Call Error (NotFound): Secret ID %s not found." , secret_id
155- )
156- return None
142+ return self .client .get_secret (secret_id = secret_id , project_id = self .project_id )
0 commit comments