Skip to content

Commit 7ab45d4

Browse files
authored
Update secrets backends to use get_conn_value instead of get_conn_uri (#22348)
In #19857 we enabled storing connections as JSON instead of URI and renamed get_conn_uri to get_conn_value to be consistent with this change. The method get_conn_uri is now deprecated and should warn when used.
1 parent f06b395 commit 7ab45d4

File tree

10 files changed

+141
-32
lines changed

10 files changed

+141
-32
lines changed

airflow/providers/amazon/aws/secrets/secrets_manager.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@
1919

2020
import ast
2121
import json
22+
import re
2223
import sys
24+
import warnings
2325
from typing import Optional
2426
from urllib.parse import urlencode
2527

2628
import boto3
2729

30+
from airflow.version import version as airflow_version
31+
2832
if sys.version_info >= (3, 8):
2933
from functools import cached_property
3034
else:
@@ -34,6 +38,11 @@
3438
from airflow.utils.log.logging_mixin import LoggingMixin
3539

3640

41+
def _parse_version(val):
42+
val = re.sub(r'(\d+\.\d+\.\d+).*', lambda x: x.group(1), val)
43+
return tuple(int(x) for x in val.split('.'))
44+
45+
3746
class SecretsManagerBackend(BaseSecretsBackend, LoggingMixin):
3847
"""
3948
Retrieves Connection or Variables from AWS Secrets Manager
@@ -173,9 +182,9 @@ def get_uri_from_secret(self, secret):
173182

174183
return connection
175184

176-
def get_conn_uri(self, conn_id: str):
185+
def get_conn_value(self, conn_id: str):
177186
"""
178-
Get Connection Value
187+
Get serialized representation of Connection
179188
180189
:param conn_id: connection id
181190
"""
@@ -199,6 +208,24 @@ def get_conn_uri(self, conn_id: str):
199208

200209
return connection
201210

211+
def get_conn_uri(self, conn_id: str) -> Optional[str]:
212+
"""
213+
Return URI representation of Connection conn_id.
214+
215+
As of Airflow version 2.3.0 this method is deprecated.
216+
217+
:param conn_id: the connection id
218+
:return: deserialized Connection
219+
"""
220+
if _parse_version(airflow_version) >= (2, 3):
221+
warnings.warn(
222+
f"Method `{self.__class__.__name__}.get_conn_uri` is deprecated and will be removed "
223+
"in a future release. Please use method `get_conn_value` instead.",
224+
DeprecationWarning,
225+
stacklevel=2,
226+
)
227+
return self.get_conn_value(conn_id)
228+
202229
def get_variable(self, key: str) -> Optional[str]:
203230
"""
204231
Get Airflow Variable from Environment Variable

airflow/providers/amazon/aws/secrets/systems_manager.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616
# specific language governing permissions and limitations
1717
# under the License.
1818
"""Objects relating to sourcing connections from AWS SSM Parameter Store"""
19+
import re
1920
import sys
21+
import warnings
2022
from typing import Optional
2123

2224
import boto3
2325

26+
from airflow.version import version as airflow_version
27+
2428
if sys.version_info >= (3, 8):
2529
from functools import cached_property
2630
else:
@@ -30,6 +34,11 @@
3034
from airflow.utils.log.logging_mixin import LoggingMixin
3135

3236

37+
def _parse_version(val):
38+
val = re.sub(r'(\d+\.\d+\.\d+).*', lambda x: x.group(1), val)
39+
return tuple(int(x) for x in val.split('.'))
40+
41+
3342
class SystemsManagerParameterStoreBackend(BaseSecretsBackend, LoggingMixin):
3443
"""
3544
Retrieves Connection or Variables from AWS SSM Parameter Store
@@ -86,7 +95,7 @@ def client(self):
8695
session = boto3.Session(profile_name=self.profile_name)
8796
return session.client("ssm", **self.kwargs)
8897

89-
def get_conn_uri(self, conn_id: str) -> Optional[str]:
98+
def get_conn_value(self, conn_id: str) -> Optional[str]:
9099
"""
91100
Get param value
92101
@@ -97,6 +106,24 @@ def get_conn_uri(self, conn_id: str) -> Optional[str]:
97106

98107
return self._get_secret(self.connections_prefix, conn_id)
99108

109+
def get_conn_uri(self, conn_id: str) -> Optional[str]:
110+
"""
111+
Return URI representation of Connection conn_id.
112+
113+
As of Airflow version 2.3.0 this method is deprecated.
114+
115+
:param conn_id: the connection id
116+
:return: deserialized Connection
117+
"""
118+
if _parse_version(airflow_version) >= (2, 3):
119+
warnings.warn(
120+
f"Method `{self.__class__.__name__}.get_conn_uri` is deprecated and will be removed "
121+
"in a future release. Please use method `get_conn_value` instead.",
122+
DeprecationWarning,
123+
stacklevel=2,
124+
)
125+
return self.get_conn_value(conn_id)
126+
100127
def get_variable(self, key: str) -> Optional[str]:
101128
"""
102129
Get Airflow Variable from Environment Variable

airflow/providers/google/cloud/secrets/secret_manager.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
"""Objects relating to sourcing connections from Google Cloud Secrets Manager"""
1919
import logging
20+
import re
21+
import warnings
2022
from typing import Optional
2123

2224
from google.auth.exceptions import DefaultCredentialsError
@@ -26,12 +28,18 @@
2628
from airflow.providers.google.cloud.utils.credentials_provider import get_credentials_and_project_id
2729
from airflow.secrets import BaseSecretsBackend
2830
from airflow.utils.log.logging_mixin import LoggingMixin
31+
from airflow.version import version as airflow_version
2932

3033
log = logging.getLogger(__name__)
3134

3235
SECRET_ID_PATTERN = r"^[a-zA-Z0-9-_]*$"
3336

3437

38+
def _parse_version(val):
39+
val = re.sub(r'(\d+\.\d+\.\d+).*', lambda x: x.group(1), val)
40+
return tuple(int(x) for x in val.split('.'))
41+
42+
3543
class CloudSecretManagerBackend(BaseSecretsBackend, LoggingMixin):
3644
"""
3745
Retrieves Connection object from Google Cloud Secrets Manager
@@ -121,9 +129,9 @@ def _is_valid_prefix_and_sep(self) -> bool:
121129
prefix = self.connections_prefix + self.sep
122130
return _SecretManagerClient.is_valid_secret_name(prefix)
123131

124-
def get_conn_uri(self, conn_id: str) -> Optional[str]:
132+
def get_conn_value(self, conn_id: str) -> Optional[str]:
125133
"""
126-
Get secret value from the SecretManager.
134+
Get serialized representation of Connection
127135
128136
:param conn_id: connection id
129137
"""
@@ -132,6 +140,24 @@ def get_conn_uri(self, conn_id: str) -> Optional[str]:
132140

133141
return self._get_secret(self.connections_prefix, conn_id)
134142

143+
def get_conn_uri(self, conn_id: str) -> Optional[str]:
144+
"""
145+
Return URI representation of Connection conn_id.
146+
147+
As of Airflow version 2.3.0 this method is deprecated.
148+
149+
:param conn_id: the connection id
150+
:return: deserialized Connection
151+
"""
152+
if _parse_version(airflow_version) >= (2, 3):
153+
warnings.warn(
154+
f"Method `{self.__class__.__name__}.get_conn_uri` is deprecated and will be removed "
155+
"in a future release. Please use method `get_conn_value` instead.",
156+
DeprecationWarning,
157+
stacklevel=2,
158+
)
159+
return self.get_conn_value(conn_id)
160+
135161
def get_variable(self, key: str) -> Optional[str]:
136162
"""
137163
Get Airflow Variable from Environment Variable

airflow/providers/hashicorp/secrets/vault.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
# specific language governing permissions and limitations
1717
# under the License.
1818
"""Objects relating to sourcing connections & variables from Hashicorp Vault"""
19+
import warnings
1920
from typing import TYPE_CHECKING, Optional
2021

2122
from airflow.providers.hashicorp._internal_client.vault_client import _VaultClient
@@ -168,14 +169,22 @@ def get_response(self, conn_id: str) -> Optional[dict]:
168169

169170
def get_conn_uri(self, conn_id: str) -> Optional[str]:
170171
"""
171-
Get secret value from Vault. Store the secret in the form of URI
172+
Get serialized representation of connection
172173
173174
:param conn_id: The connection id
174175
:rtype: str
175176
:return: The connection uri retrieved from the secret
176177
"""
177-
response = self.get_response(conn_id)
178178

179+
# Since VaultBackend implements `get_connection`, `get_conn_uri` is not used. So we
180+
# don't need to implement (or direct users to use) method `get_conn_value` instead
181+
warnings.warn(
182+
f"Method `{self.__class__.__name__}.get_conn_uri` is deprecated and will be removed "
183+
"in a future release.",
184+
PendingDeprecationWarning,
185+
stacklevel=2,
186+
)
187+
response = self.get_response(conn_id)
179188
return response.get("conn_uri") if response else None
180189

181190
# Make sure connection is imported this way for type checking, otherwise when importing

airflow/providers/microsoft/azure/secrets/key_vault.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@
1414
# KIND, either express or implied. See the License for the
1515
# specific language governing permissions and limitations
1616
# under the License.
17+
import re
1718
import sys
19+
import warnings
1820
from typing import Optional
1921

2022
from azure.core.exceptions import ResourceNotFoundError
2123
from azure.identity import DefaultAzureCredential
2224
from azure.keyvault.secrets import SecretClient
2325

26+
from airflow.version import version as airflow_version
27+
2428
if sys.version_info >= (3, 8):
2529
from functools import cached_property
2630
else:
@@ -30,6 +34,11 @@
3034
from airflow.utils.log.logging_mixin import LoggingMixin
3135

3236

37+
def _parse_version(val):
38+
val = re.sub(r'(\d+\.\d+\.\d+).*', lambda x: x.group(1), val)
39+
return tuple(int(x) for x in val.split('.'))
40+
41+
3342
class AzureKeyVaultBackend(BaseSecretsBackend, LoggingMixin):
3443
"""
3544
Retrieves Airflow Connections or Variables from Azure Key Vault secrets.
@@ -100,9 +109,9 @@ def client(self) -> SecretClient:
100109
client = SecretClient(vault_url=self.vault_url, credential=credential, **self.kwargs)
101110
return client
102111

103-
def get_conn_uri(self, conn_id: str) -> Optional[str]:
112+
def get_conn_value(self, conn_id: str) -> Optional[str]:
104113
"""
105-
Get an Airflow Connection URI from an Azure Key Vault secret
114+
Get a serialized representation of Airflow Connection from an Azure Key Vault secret
106115
107116
:param conn_id: The Airflow connection id to retrieve
108117
"""
@@ -111,6 +120,24 @@ def get_conn_uri(self, conn_id: str) -> Optional[str]:
111120

112121
return self._get_secret(self.connections_prefix, conn_id)
113122

123+
def get_conn_uri(self, conn_id: str) -> Optional[str]:
124+
"""
125+
Return URI representation of Connection conn_id.
126+
127+
As of Airflow version 2.3.0 this method is deprecated.
128+
129+
:param conn_id: the connection id
130+
:return: deserialized Connection
131+
"""
132+
if _parse_version(airflow_version) >= (2, 3):
133+
warnings.warn(
134+
f"Method `{self.__class__.__name__}.get_conn_uri` is deprecated and will be removed "
135+
"in a future release. Please use method `get_conn_value` instead.",
136+
DeprecationWarning,
137+
stacklevel=2,
138+
)
139+
return self.get_conn_value(conn_id)
140+
114141
def get_variable(self, key: str) -> Optional[str]:
115142
"""
116143
Get an Airflow Variable from an Azure Key Vault secret.

docs/apache-airflow/security/secrets/secrets-backend/index.rst

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,28 +99,21 @@ Roll your own secrets backend
9999
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
100100

101101
A secrets backend is a subclass of :py:class:`airflow.secrets.BaseSecretsBackend` and must implement either
102-
:py:meth:`~airflow.secrets.BaseSecretsBackend.get_connection` or :py:meth:`~airflow.secrets.BaseSecretsBackend.get_conn_uri`.
102+
:py:meth:`~airflow.secrets.BaseSecretsBackend.get_connection` or :py:meth:`~airflow.secrets.BaseSecretsBackend.get_conn_value`.
103103

104104
After writing your backend class, provide the fully qualified class name in the ``backend`` key in the ``[secrets]``
105105
section of ``airflow.cfg``.
106106

107107
Additional arguments to your SecretsBackend can be configured in ``airflow.cfg`` by supplying a JSON string to ``backend_kwargs``, which will be passed to the ``__init__`` of your SecretsBackend.
108108
See :ref:`Configuration <secrets_backend_configuration>` for more details, and :ref:`SSM Parameter Store <ssm_parameter_store_secrets>` for an example.
109109

110-
.. note::
111-
112-
If you are rolling your own secrets backend, you don't strictly need to use airflow's URI format. But
113-
doing so makes it easier to switch between environment variables, the metastore, and your secrets backend.
114110

115111
Adapt to non-Airflow compatible secret formats for connections
116112
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
117113

118114
The default implementation of Secret backend requires use of an Airflow-specific format of storing
119115
secrets for connections. Currently most community provided implementations require the connections to
120-
be stored as URIs (with the possibility of adding more friendly formats in the future)
121-
:doc:`apache-airflow-providers:core-extensions/secrets-backends`. However some organizations may prefer
122-
to keep the credentials (passwords/tokens etc) in other formats --
123-
for example when you want the same credentials to be used across multiple clients, or when you want to
124-
use built-in mechanism of rotating the credentials that do not work well with the Airflow-specific format.
116+
be stored as JSON or the Airflow Connection URI format (see
117+
:doc:`apache-airflow-providers:core-extensions/secrets-backends`). However some organizations may need to store the credentials (passwords/tokens etc) in some other way, for example if the same credentials store needs to be used for multiple data platforms, or if you are using a service with a built-in mechanism of rotating the credentials that does not work with the Airflow-specific format.
125118
In this case you will need to roll your own secret backend as described in the previous chapter,
126-
possibly extending existing secret backend and adapt it to the scheme used by your organization.
119+
possibly extending an existing secrets backend and adapting it to the scheme used by your organization.

tests/providers/amazon/aws/secrets/test_secrets_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323

2424

2525
class TestSecretsManagerBackend(TestCase):
26-
@mock.patch("airflow.providers.amazon.aws.secrets.secrets_manager.SecretsManagerBackend.get_conn_uri")
27-
def test_aws_secrets_manager_get_connections(self, mock_get_uri):
28-
mock_get_uri.return_value = "scheme://user:pass@host:100"
26+
@mock.patch("airflow.providers.amazon.aws.secrets.secrets_manager.SecretsManagerBackend.get_conn_value")
27+
def test_aws_secrets_manager_get_connections(self, mock_get_value):
28+
mock_get_value.return_value = "scheme://user:pass@host:100"
2929
conn_list = SecretsManagerBackend().get_connections("fake_conn")
3030
conn = conn_list[0]
3131
assert conn.host == 'host'

tests/providers/amazon/aws/secrets/test_systems_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
class TestSsmSecrets(TestCase):
2828
@mock.patch(
2929
"airflow.providers.amazon.aws.secrets.systems_manager."
30-
"SystemsManagerParameterStoreBackend.get_conn_uri"
30+
"SystemsManagerParameterStoreBackend.get_conn_value"
3131
)
32-
def test_aws_ssm_get_connections(self, mock_get_uri):
33-
mock_get_uri.return_value = "scheme://user:pass@host:100"
32+
def test_aws_ssm_get_connections(self, mock_get_value):
33+
mock_get_value.return_value = "scheme://user:pass@host:100"
3434
conn_list = SystemsManagerParameterStoreBackend().get_connections("fake_conn")
3535
conn = conn_list[0]
3636
assert conn.host == 'host'

tests/providers/google/cloud/secrets/test_secret_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ def test_get_conn_uri(self, connections_prefix, mock_client_callable, mock_get_c
108108
mock_client.secret_version_path.assert_called_once_with(PROJECT_ID, secret_id, "latest")
109109

110110
@mock.patch(MODULE_NAME + ".get_credentials_and_project_id")
111-
@mock.patch(MODULE_NAME + ".CloudSecretManagerBackend.get_conn_uri")
112-
def test_get_connections(self, mock_get_uri, mock_get_creds):
111+
@mock.patch(MODULE_NAME + ".CloudSecretManagerBackend.get_conn_value")
112+
def test_get_connections(self, mock_get_value, mock_get_creds):
113113
mock_get_creds.return_value = CREDENTIALS, PROJECT_ID
114-
mock_get_uri.return_value = CONN_URI
114+
mock_get_value.return_value = CONN_URI
115115
conns = CloudSecretManagerBackend().get_connections(conn_id=CONN_ID)
116116
assert isinstance(conns, list)
117117
assert isinstance(conns[0], Connection)

tests/providers/microsoft/azure/secrets/test_azure_key_vault.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424

2525

2626
class TestAzureKeyVaultBackend(TestCase):
27-
@mock.patch('airflow.providers.microsoft.azure.secrets.key_vault.AzureKeyVaultBackend.get_conn_uri')
28-
def test_get_connections(self, mock_get_uri):
29-
mock_get_uri.return_value = 'scheme://user:pass@host:100'
27+
@mock.patch('airflow.providers.microsoft.azure.secrets.key_vault.AzureKeyVaultBackend.get_conn_value')
28+
def test_get_connections(self, mock_get_value):
29+
mock_get_value.return_value = 'scheme://user:pass@host:100'
3030
conn_list = AzureKeyVaultBackend().get_connections('fake_conn')
3131
conn = conn_list[0]
3232
assert conn.host == 'host'

0 commit comments

Comments
 (0)