airbyte.secrets.util

Helper functions for working with secrets.

 1# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
 2"""Helper functions for working with secrets."""
 3
 4from __future__ import annotations
 5
 6import warnings
 7from typing import Any, cast
 8
 9from airbyte import exceptions as exc
10from airbyte.secrets.base import SecretManager, SecretSourceEnum, SecretString
11from airbyte.secrets.config import _get_secret_sources
12
13
14def get_secret(
15    secret_name: str,
16    /,
17    *,
18    sources: list[SecretManager | SecretSourceEnum] | None = None,
19    allow_prompt: bool = True,
20    **kwargs: dict[str, Any],
21) -> SecretString:
22    """Get a secret from the environment.
23
24    The optional `sources` argument of enum type `SecretSourceEnum` or list of `SecretSourceEnum`
25    options. If left blank, all available sources will be checked. If a list of `SecretSourceEnum`
26    entries is passed, then the sources will be checked using the provided ordering.
27
28    If `allow_prompt` is `True` or if SecretSourceEnum.PROMPT is declared in the `source` arg, then
29    the user will be prompted to enter the secret if it is not found in any of the other sources.
30    """
31    if "source" in kwargs:
32        warnings.warn(
33            message="The `source` argument is deprecated. Use the `sources` argument instead.",
34            category=DeprecationWarning,
35            stacklevel=2,
36        )
37        sources = kwargs.pop("source")  # type: ignore [assignment]
38
39    available_sources: dict[str, SecretManager] = {}
40    for available_source in _get_secret_sources():
41        # Add available sources to the dict. Order matters.
42        available_sources[available_source.name] = available_source
43
44    if sources is None:
45        # If ANY is in the list, then we don't need to check any other sources.
46        # This is the default behavior.
47        sources = list(available_sources.values())
48
49    elif not isinstance(sources, list):
50        sources = [sources]  # type: ignore [unreachable]  # This is a 'just in case' catch.
51
52    # Replace any SecretSourceEnum strings with the matching SecretManager object
53    for source in list(sources):
54        if isinstance(source, SecretSourceEnum):
55            if source not in available_sources:
56                raise exc.PyAirbyteInputError(
57                    guidance="Invalid secret source name.",
58                    input_value=source,
59                    context={
60                        "Available Sources": list(available_sources.keys()),
61                    },
62                )
63
64            sources[sources.index(source)] = available_sources[source]
65
66    secret_managers = cast(list[SecretManager], sources)
67
68    if SecretSourceEnum.PROMPT in secret_managers:
69        prompt_source = secret_managers.pop(
70            # Mis-typed, but okay here since we have equality logic for the enum comparison:
71            secret_managers.index(SecretSourceEnum.PROMPT),  # type: ignore [arg-type]
72        )
73
74        if allow_prompt:
75            # Always check prompt last. Add it to the end of the list.
76            secret_managers.append(prompt_source)
77
78    for secret_mgr in secret_managers:
79        val = secret_mgr.get_secret(secret_name)
80        if val:
81            return SecretString(val)
82
83    raise exc.PyAirbyteSecretNotFoundError(
84        secret_name=secret_name,
85        sources=[str(s) for s in available_sources],
86    )
def get_secret( secret_name: str, /, *, sources: list[airbyte.secrets.SecretManager | airbyte.SecretSourceEnum] | None = None, allow_prompt: bool = True, **kwargs: dict[str, typing.Any]) -> airbyte.secrets.SecretString:
15def get_secret(
16    secret_name: str,
17    /,
18    *,
19    sources: list[SecretManager | SecretSourceEnum] | None = None,
20    allow_prompt: bool = True,
21    **kwargs: dict[str, Any],
22) -> SecretString:
23    """Get a secret from the environment.
24
25    The optional `sources` argument of enum type `SecretSourceEnum` or list of `SecretSourceEnum`
26    options. If left blank, all available sources will be checked. If a list of `SecretSourceEnum`
27    entries is passed, then the sources will be checked using the provided ordering.
28
29    If `allow_prompt` is `True` or if SecretSourceEnum.PROMPT is declared in the `source` arg, then
30    the user will be prompted to enter the secret if it is not found in any of the other sources.
31    """
32    if "source" in kwargs:
33        warnings.warn(
34            message="The `source` argument is deprecated. Use the `sources` argument instead.",
35            category=DeprecationWarning,
36            stacklevel=2,
37        )
38        sources = kwargs.pop("source")  # type: ignore [assignment]
39
40    available_sources: dict[str, SecretManager] = {}
41    for available_source in _get_secret_sources():
42        # Add available sources to the dict. Order matters.
43        available_sources[available_source.name] = available_source
44
45    if sources is None:
46        # If ANY is in the list, then we don't need to check any other sources.
47        # This is the default behavior.
48        sources = list(available_sources.values())
49
50    elif not isinstance(sources, list):
51        sources = [sources]  # type: ignore [unreachable]  # This is a 'just in case' catch.
52
53    # Replace any SecretSourceEnum strings with the matching SecretManager object
54    for source in list(sources):
55        if isinstance(source, SecretSourceEnum):
56            if source not in available_sources:
57                raise exc.PyAirbyteInputError(
58                    guidance="Invalid secret source name.",
59                    input_value=source,
60                    context={
61                        "Available Sources": list(available_sources.keys()),
62                    },
63                )
64
65            sources[sources.index(source)] = available_sources[source]
66
67    secret_managers = cast(list[SecretManager], sources)
68
69    if SecretSourceEnum.PROMPT in secret_managers:
70        prompt_source = secret_managers.pop(
71            # Mis-typed, but okay here since we have equality logic for the enum comparison:
72            secret_managers.index(SecretSourceEnum.PROMPT),  # type: ignore [arg-type]
73        )
74
75        if allow_prompt:
76            # Always check prompt last. Add it to the end of the list.
77            secret_managers.append(prompt_source)
78
79    for secret_mgr in secret_managers:
80        val = secret_mgr.get_secret(secret_name)
81        if val:
82            return SecretString(val)
83
84    raise exc.PyAirbyteSecretNotFoundError(
85        secret_name=secret_name,
86        sources=[str(s) for s in available_sources],
87    )

Get a secret from the environment.

The optional sources argument of enum type SecretSourceEnum or list of SecretSourceEnum options. If left blank, all available sources will be checked. If a list of SecretSourceEnum entries is passed, then the sources will be checked using the provided ordering.

If allow_prompt is True or if SecretSourceEnum.PROMPT is declared in the source arg, then the user will be prompted to enter the secret if it is not found in any of the other sources.