4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
183 lines
4.9 KiB
Python
183 lines
4.9 KiB
Python
"""Secrets Manager access."""
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from botocore.exceptions import ClientError
|
|
|
|
from tests.shared.infrastructure_sdk.deployer import DEFAULT_REGION, get_boto3_client
|
|
|
|
|
|
def get_secret_value(secret_name: str, region: str = DEFAULT_REGION) -> dict[str, Any]:
|
|
"""Get secret value as dict (JSON parsed).
|
|
|
|
Args:
|
|
secret_name: Secret name or ARN.
|
|
region: AWS region.
|
|
|
|
Returns:
|
|
Secret value as parsed JSON dict.
|
|
|
|
Raises:
|
|
ValueError: If secret doesn't exist or isn't valid JSON.
|
|
"""
|
|
secrets_client = get_boto3_client("secretsmanager", region)
|
|
|
|
try:
|
|
response = secrets_client.get_secret_value(SecretId=secret_name)
|
|
except ClientError as e:
|
|
if e.response["Error"]["Code"] == "ResourceNotFoundException":
|
|
raise ValueError(f"Secret '{secret_name}' not found") from e
|
|
raise
|
|
|
|
secret_string = response.get("SecretString")
|
|
if not secret_string:
|
|
raise ValueError(f"Secret '{secret_name}' has no string value (might be binary)")
|
|
|
|
try:
|
|
result: dict[str, Any] = json.loads(secret_string)
|
|
return result
|
|
except json.JSONDecodeError as e:
|
|
raise ValueError(f"Secret '{secret_name}' is not valid JSON: {e}") from e
|
|
|
|
|
|
def get_secret_string(secret_name: str, region: str = DEFAULT_REGION) -> str:
|
|
"""Get secret value as raw string.
|
|
|
|
Args:
|
|
secret_name: Secret name or ARN.
|
|
region: AWS region.
|
|
|
|
Returns:
|
|
Secret value as string.
|
|
|
|
Raises:
|
|
ValueError: If secret doesn't exist.
|
|
"""
|
|
secrets_client = get_boto3_client("secretsmanager", region)
|
|
|
|
try:
|
|
response = secrets_client.get_secret_value(SecretId=secret_name)
|
|
except ClientError as e:
|
|
if e.response["Error"]["Code"] == "ResourceNotFoundException":
|
|
raise ValueError(f"Secret '{secret_name}' not found") from e
|
|
raise
|
|
|
|
secret_string = response.get("SecretString")
|
|
if not secret_string:
|
|
raise ValueError(f"Secret '{secret_name}' has no string value")
|
|
|
|
return str(secret_string)
|
|
|
|
|
|
def get_secret_arn(secret_name: str, region: str = DEFAULT_REGION) -> str:
|
|
"""Get secret ARN.
|
|
|
|
Args:
|
|
secret_name: Secret name.
|
|
region: AWS region.
|
|
|
|
Returns:
|
|
Secret ARN.
|
|
|
|
Raises:
|
|
ValueError: If secret doesn't exist.
|
|
"""
|
|
secrets_client = get_boto3_client("secretsmanager", region)
|
|
|
|
try:
|
|
response = secrets_client.describe_secret(SecretId=secret_name)
|
|
except ClientError as e:
|
|
if e.response["Error"]["Code"] == "ResourceNotFoundException":
|
|
raise ValueError(f"Secret '{secret_name}' not found") from e
|
|
raise
|
|
|
|
return str(response["ARN"])
|
|
|
|
|
|
def secret_exists(secret_name: str, region: str = DEFAULT_REGION) -> bool:
|
|
"""Check if a secret exists.
|
|
|
|
Args:
|
|
secret_name: Secret name or ARN.
|
|
region: AWS region.
|
|
|
|
Returns:
|
|
True if secret exists.
|
|
"""
|
|
secrets_client = get_boto3_client("secretsmanager", region)
|
|
|
|
try:
|
|
secrets_client.describe_secret(SecretId=secret_name)
|
|
return True
|
|
except ClientError as e:
|
|
if e.response["Error"]["Code"] == "ResourceNotFoundException":
|
|
return False
|
|
raise
|
|
|
|
|
|
def list_secrets(
|
|
prefix: str | None = None,
|
|
max_results: int = 100,
|
|
region: str = DEFAULT_REGION,
|
|
) -> list[dict[str, Any]]:
|
|
"""List secrets.
|
|
|
|
Args:
|
|
prefix: Optional name prefix filter.
|
|
max_results: Maximum secrets to return.
|
|
region: AWS region.
|
|
|
|
Returns:
|
|
List of secret metadata.
|
|
"""
|
|
secrets_client = get_boto3_client("secretsmanager", region)
|
|
|
|
params: dict[str, Any] = {"MaxResults": min(max_results, 100)}
|
|
|
|
if prefix:
|
|
params["Filters"] = [{"Key": "name", "Values": [prefix]}]
|
|
|
|
secrets = []
|
|
paginator = secrets_client.get_paginator("list_secrets")
|
|
|
|
for page in paginator.paginate(**params):
|
|
for secret in page.get("SecretList", []):
|
|
secrets.append(
|
|
{
|
|
"name": secret["Name"],
|
|
"arn": secret["ARN"],
|
|
"description": secret.get("Description"),
|
|
"last_changed": secret.get("LastChangedDate"),
|
|
}
|
|
)
|
|
if len(secrets) >= max_results:
|
|
return secrets
|
|
|
|
return secrets
|
|
|
|
|
|
def get_secret_for_ecs(
|
|
secret_name: str, json_key: str | None = None, region: str = DEFAULT_REGION
|
|
) -> dict[str, str]:
|
|
"""Get secret reference for ECS container definition.
|
|
|
|
Args:
|
|
secret_name: Secret name.
|
|
json_key: Optional JSON key within the secret.
|
|
region: AWS region.
|
|
|
|
Returns:
|
|
Dict with 'name' and 'valueFrom' for ECS secrets config.
|
|
"""
|
|
arn = get_secret_arn(secret_name, region)
|
|
|
|
value_from = arn
|
|
if json_key:
|
|
value_from = f"{arn}:{json_key}::"
|
|
|
|
return {
|
|
"name": json_key or secret_name.split("/")[-1].upper(),
|
|
"valueFrom": value_from,
|
|
}
|