diff --git a/README.md b/README.md index d09200bf..41082ffe 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,9 @@ VeADK provides several useful command line tools for faster deployment and optim policies; target `cn-beijing` (default) or `cn-shanghai` with `--region`, automatically locate the Identity user pool across Beijing and Shanghai, and select the VeFaaS project with `--project` (default `default`); + deployment credentials can come from explicit CLI options, the + `VOLCENGINE_ACCESS_KEY` / `VOLCENGINE_SECRET_KEY` environment variables, or + the `[default]` profile in `~/.volc/credentials`; Shanghai Functions, gateways, and AgentKit resources stay in Shanghai while VeFaaS Application operations use its Beijing control-plane endpoint; the selected region is also used for temporary-chat and Skill-creation sessions; diff --git a/docs/content/docs/framework/frontend.en.mdx b/docs/content/docs/framework/frontend.en.mdx index bb8c8d57..e16bd362 100644 --- a/docs/content/docs/framework/frontend.en.mdx +++ b/docs/content/docs/framework/frontend.en.mdx @@ -242,6 +242,12 @@ veadk studio deploy \ --vefaas-app-name ``` +Deployment credentials can be supplied explicitly with +`--volcengine-access-key` / `--volcengine-secret-key`, through the current +process's `VOLCENGINE_ACCESS_KEY` / `VOLCENGINE_SECRET_KEY` environment +variables, or through the `[default]` profile in `~/.volc/credentials` when the +other sources are absent. + ### Custom Studio branding Use `--site-title` for a system name of up to six characters and `--site-logo` diff --git a/docs/content/docs/framework/frontend.mdx b/docs/content/docs/framework/frontend.mdx index 9dfdb557..f769d099 100644 --- a/docs/content/docs/framework/frontend.mdx +++ b/docs/content/docs/framework/frontend.mdx @@ -204,6 +204,11 @@ veadk studio deploy \ --vefaas-app-name ``` +部署凭据支持通过 `--volcengine-access-key` / `--volcengine-secret-key` +显式传入,也支持当前进程的 `VOLCENGINE_ACCESS_KEY` / +`VOLCENGINE_SECRET_KEY` 环境变量。两者均未提供时,会读取 +`~/.volc/credentials` 中的 `[default]` 配置。 + ### 自定义 Studio 品牌 使用 `--site-title` 设置不超过 6 个字符的系统名称,使用 diff --git a/tests/cli/test_studio_deploy_target.py b/tests/cli/test_studio_deploy_target.py index 46f60125..9f6224a4 100644 --- a/tests/cli/test_studio_deploy_target.py +++ b/tests/cli/test_studio_deploy_target.py @@ -24,7 +24,11 @@ sanitize_for_serialization, ) -from veadk.cli.cli_frontend import _resolve_studio_identity_region, studio +from veadk.cli.cli_frontend import ( + _resolve_studio_cloud_credentials, + _resolve_studio_identity_region, + studio, +) from veadk.config import veadk_environments from veadk.integrations.ve_identity.identity_client import IdentityClient @@ -45,6 +49,48 @@ def _skip_serverless_role_setup(monkeypatch: pytest.MonkeyPatch) -> None: ) +def test_studio_credentials_prefer_inline_environment( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + credentials_path = tmp_path / "credentials" + credentials_path.write_text( + "[default]\naccess_key_id=file-ak\nsecret_access_key=file-sk\n", + encoding="utf-8", + ) + monkeypatch.setenv("VOLCENGINE_ACCESS_KEY", "env-ak") + monkeypatch.setenv("VOLCENGINE_SECRET_KEY", "env-sk") + + credentials = _resolve_studio_cloud_credentials( + None, + None, + credentials_path, + ) + + assert credentials == ("env-ak", "env-sk") + + +def test_studio_credentials_fall_back_to_volc_default_profile( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + credentials_path = tmp_path / "credentials" + credentials_path.write_text( + "[default]\naccess_key_id=file-ak\nsecret_access_key=file-sk\n", + encoding="utf-8", + ) + monkeypatch.delenv("VOLCENGINE_ACCESS_KEY", raising=False) + monkeypatch.delenv("VOLCENGINE_SECRET_KEY", raising=False) + + credentials = _resolve_studio_cloud_credentials( + None, + None, + credentials_path, + ) + + assert credentials == ("file-ak", "file-sk") + + @pytest.mark.parametrize( ("stage", "expected_prefix"), [ diff --git a/veadk/cli/cli_frontend.py b/veadk/cli/cli_frontend.py index 59b65679..13d296fb 100644 --- a/veadk/cli/cli_frontend.py +++ b/veadk/cli/cli_frontend.py @@ -3501,6 +3501,48 @@ def _resolve_studio_identity_region( ) +def _resolve_studio_cloud_credentials( + access_key: str | None, + secret_key: str | None, + credentials_path: Path | None = None, +) -> tuple[str, str]: + """Resolve Studio deploy credentials from CLI, environment, or ~/.volc.""" + import configparser + + resolved_access_key = access_key or os.getenv("VOLCENGINE_ACCESS_KEY", "") + resolved_secret_key = secret_key or os.getenv("VOLCENGINE_SECRET_KEY", "") + if resolved_access_key and resolved_secret_key: + return resolved_access_key, resolved_secret_key + + path = credentials_path or Path.home() / ".volc" / "credentials" + if path.is_file(): + parser = configparser.ConfigParser(interpolation=None) + try: + with path.open(encoding="utf-8") as credentials_file: + parser.read_file(credentials_file) + except (OSError, UnicodeError, configparser.Error) as error: + raise click.ClickException( + f"Failed to read Volcengine credentials file '{path}': {error}" + ) from error + default_profile = parser["default"] if parser.has_section("default") else {} + resolved_access_key = ( + resolved_access_key or str(default_profile.get("access_key_id", "")).strip() + ) + resolved_secret_key = ( + resolved_secret_key + or str(default_profile.get("secret_access_key", "")).strip() + ) + + if resolved_access_key and resolved_secret_key: + return resolved_access_key, resolved_secret_key + raise click.ClickException( + "Volcengine credentials required: pass --volcengine-access-key/" + "--volcengine-secret-key, set VOLCENGINE_ACCESS_KEY/" + "VOLCENGINE_SECRET_KEY, or configure the [default] profile in " + "~/.volc/credentials." + ) + + @studio.command("deploy") @click.option( "--user-pool-id", @@ -3640,7 +3682,7 @@ def frontend_deploy( import tempfile import shutil - from veadk.config import getenv, veadk_environments + from veadk.config import veadk_environments try: branding_title = normalize_site_title(site_title) @@ -3648,13 +3690,10 @@ def frontend_deploy( except ValueError as error: raise click.ClickException(str(error)) from error - ak = volcengine_access_key or getenv("VOLCENGINE_ACCESS_KEY") - sk = volcengine_secret_key or getenv("VOLCENGINE_SECRET_KEY") - if not ak or not sk: - raise click.ClickException( - "Volcengine credentials required: set VOLCENGINE_ACCESS_KEY/SECRET_KEY " - "or pass --volcengine-access-key/--volcengine-secret-key." - ) + ak, sk = _resolve_studio_cloud_credentials( + volcengine_access_key, + volcengine_secret_key, + ) identity_region = _resolve_studio_identity_region( access_key=ak,