-
Notifications
You must be signed in to change notification settings - Fork 0
feat(core): support AICORE_SERVICE_KEY env var for credentials and rename env vars #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
yamaceay
wants to merge
11
commits into
main
Choose a base branch
from
feat/aicore-service-key
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
9ad26d9
feat(core): support AICORE_SERVICE_KEY env var for credentials
yamaceay 14db48a
fix: uv lock updated
yamaceay 19916ed
docs: update pydoc3 documentation [skip ci]
sap-ai-sdk-bot[bot] 3e692b0
Update packages/core/ai_core_sdk/credentials.py
yamaceay 3be9edf
change env var naming convention
yamaceay 41a890f
docs: update pydoc3 documentation [skip ci]
sap-ai-sdk-bot[bot] 20b293a
fix: cleanup unused code, make service key parsing inline, improve test
yamaceay 9717d47
Update packages/core/ai_core_sdk/credentials.py
yamaceay b51e307
Update packages/core/ai_core_sdk/credentials.py
yamaceay 333fd88
Merge branch 'main' into feat/aicore-service-key
yamaceay 95a64f2
Delete packages/core/docs/ai_core_sdk.helpers.constants.html
yamaceay File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,13 +8,12 @@ | |
| from dataclasses import dataclass | ||
|
|
||
| from ai_core_sdk.helpers import get_home | ||
| from ai_core_sdk.helpers.constants import (AI_CORE_PREFIX, AUTH_ENDPOINT_SUFFIX, CONFIG_FILE_ENV_VAR, PROFILE_ENV_VAR, | ||
| VCAP_AICORE_SERVICE_NAME, VCAP_SERVICES_ENV_VAR) | ||
| from ai_core_sdk.helpers.constants import (AI_CORE_PREFIX, AUTH_ENDPOINT_SUFFIX, ENV_VAR_AICORE_CONFIG_FILE, ENV_VAR_AICORE_PROFILE, | ||
| ENV_VAR_AICORE_SERVICE_KEY, VCAP_AICORE_SERVICE_NAME, ENV_VAR_VCAP_SERVICES) | ||
| from ai_core_sdk.helpers.logging import get_logger | ||
|
|
||
| logger = get_logger() | ||
|
|
||
|
|
||
| def get_nested_value(data_dict, keys: List[str]): | ||
| """ | ||
| Retrieve a nested value from a dictionary using a list of strings. | ||
|
|
@@ -28,14 +27,20 @@ def get_nested_value(data_dict, keys: List[str]): | |
| current_value = current_value[key] | ||
| return current_value | ||
|
|
||
| def _get_nested_value_safe(data: Dict, keys) -> Optional[Any]: | ||
| try: | ||
| return get_nested_value(data, keys) | ||
| except KeyError: | ||
| logger.debug("Key path %s not found in service key.", keys) | ||
| return None | ||
|
|
||
| @dataclass | ||
| class VCAPEnvironment: | ||
| services: List[Service] | ||
|
|
||
| @classmethod | ||
| def from_env(cls, env_var: Optional[str] = None): | ||
| env_var = env_var or VCAP_SERVICES_ENV_VAR | ||
| env_var = env_var or ENV_VAR_VCAP_SERVICES | ||
| env = json.loads(os.environ.get(env_var, '{}')) | ||
| return cls.from_dict(env) | ||
|
|
||
|
|
@@ -148,9 +153,9 @@ class Source: | |
| def init_conf(profile: str = None): | ||
| # Read configuration from ${AICORE_HOME}/config_<profile>.json. | ||
| home = pathlib.Path(get_home()) | ||
| profile = profile or os.environ.get(PROFILE_ENV_VAR) | ||
| profile = profile or os.environ.get(ENV_VAR_AICORE_PROFILE) | ||
| profile_config_file = f'config_{profile}.json' | ||
| direct_config_file = pathlib.Path(os.getenv(CONFIG_FILE_ENV_VAR)) if os.getenv(CONFIG_FILE_ENV_VAR) else None | ||
| direct_config_file = pathlib.Path(os.getenv(ENV_VAR_AICORE_CONFIG_FILE)) if os.getenv(ENV_VAR_AICORE_CONFIG_FILE) else None | ||
| path_to_config = (direct_config_file or | ||
| (home / ('config.json' if profile in ('default', '', None) else profile_config_file))) | ||
| config = {} | ||
|
|
@@ -241,12 +246,30 @@ def _str_or_none(value) -> Optional[str]: | |
| return str(value) if value else None | ||
|
|
||
|
|
||
| def _load_service_key() -> Dict[str, Any]: | ||
| """Read and parse AICORE_SERVICE_KEY from the environment. | ||
|
|
||
| :return: Parsed service key dict, or an empty dict if the env var is not set. | ||
| :raises ValueError: If the env var is set but contains invalid JSON. | ||
| """ | ||
| service_key_json_string = os.environ.get(ENV_VAR_AICORE_SERVICE_KEY) | ||
| if not service_key_json_string: | ||
| return {} | ||
| try: | ||
| return json.loads(service_key_json_string) | ||
| except json.JSONDecodeError as exc: | ||
| raise ValueError( | ||
| f"{ENV_VAR_AICORE_SERVICE_KEY} is set but contains invalid JSON: {exc}" | ||
| ) from exc | ||
|
|
||
|
|
||
|
|
||
| def fetch_credentials(profile: str = None, credential_values: List[CredentialsValue] = CORE_CREDENTIAL_VALUES, | ||
| validate: bool = True, **kwargs) -> Dict[str, str]: | ||
| """ | ||
| Fetch credentials from a single source based on precedence. | ||
|
|
||
| Precedence order: kwargs > environment variables > config file > VCAP service | ||
| Precedence order: kwargs > separate environment variables > service key > config file > VCAP service | ||
|
|
||
| Once a source is selected (first one with any credential), all credentials | ||
| come from that source only. Resource group is an exception and follows | ||
|
|
@@ -261,11 +284,18 @@ def fetch_credentials(profile: str = None, credential_values: List[CredentialsVa | |
| except KeyError: | ||
| vcap_service = None | ||
|
|
||
| service_key = _load_service_key() | ||
|
|
||
| # `cv.vcap_key` describes the full path inside a VCAP_SERVICES entry, starting with | ||
| # `credentials` (e.g. `('credentials', 'clientid')`) | ||
| sources = [ | ||
| Source("kwargs", | ||
| lambda cv: _str_or_none(kwargs.get(cv.name))), | ||
| Source("environment variables", | ||
| lambda cv: _str_or_none(os.environ.get(f'{AI_CORE_PREFIX}_{cv.name.upper()}'))), | ||
| # A service key is already the inner credentials object, so the leading `credentials` segment is stripped. | ||
| Source("service key", | ||
| lambda cv: _str_or_none(_get_nested_value_safe(service_key, cv.vcap_key[1:])) if cv.vcap_key else None), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [pp] writing |
||
| Source("config file", | ||
| lambda cv: _str_or_none(config.get(f'{AI_CORE_PREFIX}_{cv.name.upper()}'))), | ||
| Source("VCAP service", | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
q: not strictly part of this PR but is it intended that this eg converts from empty string to None (and 0 to None)? If yes maybe we could make the method name more meaningful