mirror of
https://github.com/Skyvern-AI/skyvern.git
synced 2026-07-09 16:09:13 +00:00
Deprecate Streamlit credential path (#5836)
This commit is contained in:
parent
dddbe89fb7
commit
dc8c86867c
13 changed files with 258 additions and 91 deletions
|
|
@ -11,7 +11,8 @@
|
|||
**/.vscode
|
||||
*.env*
|
||||
!/.env
|
||||
# Streamlit ignores
|
||||
# Generated local deployment credentials
|
||||
**/.skyvern
|
||||
**/secrets*.toml
|
||||
|
||||
# Skyvern
|
||||
|
|
@ -25,4 +26,4 @@ skyvern-frontend/node_modules
|
|||
|
||||
.dockerignore
|
||||
.gitignore
|
||||
Dockerfile
|
||||
Dockerfile
|
||||
|
|
|
|||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -172,7 +172,8 @@ postgres-data
|
|||
files/
|
||||
temp/
|
||||
|
||||
# Streamlit ignores
|
||||
# Generated local deployment credentials
|
||||
.skyvern/
|
||||
**/secrets*.toml
|
||||
|
||||
## Frontend
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
[theme]
|
||||
|
||||
# The preset Streamlit theme that your custom theme inherits from.
|
||||
# One of "light" or "dark".
|
||||
base = "dark"
|
||||
|
|
@ -34,7 +34,8 @@ services:
|
|||
- ./videos:/data/videos
|
||||
- ./har:/data/har
|
||||
- ./log:/data/log
|
||||
- ./.streamlit:/app/.streamlit
|
||||
# Generated credentials allow the UI to pick up the local API key on first startup.
|
||||
- ./.skyvern:/app/.skyvern
|
||||
# Uncomment the following two lines if you want to connect to any local changes
|
||||
# - ./skyvern:/app/skyvern
|
||||
# - ./alembic:/app/alembic
|
||||
|
|
@ -130,10 +131,11 @@ services:
|
|||
postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "test", "-f", "/app/.streamlit/secrets.toml"]
|
||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/heartbeat', timeout=5)"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
retries: 24
|
||||
start_period: 180s
|
||||
skyvern-ui:
|
||||
image: public.ecr.aws/skyvern/skyvern-ui:latest
|
||||
restart: on-failure
|
||||
|
|
@ -144,7 +146,8 @@ services:
|
|||
- ./artifacts:/data/artifacts
|
||||
- ./videos:/data/videos
|
||||
- ./har:/data/har
|
||||
- ./.streamlit:/app/.streamlit
|
||||
# Generated credentials allow the UI to pick up the local API key on first startup.
|
||||
- ./.skyvern:/app/.skyvern
|
||||
# User secrets and frontend config live in skyvern-frontend/.env
|
||||
# (loaded via env_file). No inline environment for the UI service.
|
||||
env_file:
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ Copy the frontend environment file:
|
|||
cp skyvern-frontend/.env.example skyvern-frontend/.env
|
||||
```
|
||||
|
||||
The default values work for local development. You'll update `VITE_SKYVERN_API_KEY` after the first startup.
|
||||
The default values work for local development. Docker Compose mounts the generated local credentials into the UI container, so you can leave `VITE_SKYVERN_API_KEY` as `YOUR_API_KEY` for the first startup.
|
||||
|
||||
### 4. Start the services
|
||||
|
||||
|
|
@ -84,10 +84,10 @@ All three services should show `healthy` in the STATUS column. The `skyvern` con
|
|||
Once healthy, retrieve your API key:
|
||||
|
||||
```bash
|
||||
cat .streamlit/secrets.toml
|
||||
cat .skyvern/credentials.toml
|
||||
```
|
||||
|
||||
This file is auto-generated by Skyvern on first startup. The `.streamlit` path is a legacy artifact. The credentials inside are standard Skyvern API keys.
|
||||
This file is auto-generated by Skyvern on first startup. The credentials inside are standard Skyvern API keys.
|
||||
|
||||
You'll see output like:
|
||||
|
||||
|
|
@ -100,21 +100,7 @@ configs = [
|
|||
|
||||
The `host` value uses the Docker-internal hostname `skyvern`. From your machine, use `http://localhost:8000` instead. You only need the `cred` value. This is your API key.
|
||||
|
||||
### 6. Update frontend configuration
|
||||
|
||||
Add your API key to the frontend environment:
|
||||
|
||||
```bash skyvern-frontend/.env
|
||||
VITE_SKYVERN_API_KEY=eyJhbGciOiJIUzI1...
|
||||
```
|
||||
|
||||
Restart the UI to pick up the change:
|
||||
|
||||
```bash
|
||||
docker compose restart skyvern-ui
|
||||
```
|
||||
|
||||
### 7. Verify the installation
|
||||
### 6. Verify the installation
|
||||
|
||||
Open [http://localhost:8080](http://localhost:8080) in your browser. You should see the Skyvern dashboard.
|
||||
|
||||
|
|
@ -162,7 +148,7 @@ Docker Compose mounts several directories for persistent storage:
|
|||
| `./videos` | `/data/videos` | Browser session recordings |
|
||||
| `./har` | `/data/har` | HTTP Archive files for debugging |
|
||||
| `./log` | `/data/log` | Application logs |
|
||||
| `./.streamlit` | `/app/.streamlit` | Generated API credentials |
|
||||
| `./.skyvern` | `/app/.skyvern` | Generated API credentials |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -280,7 +266,7 @@ This deletes all data including task history, credentials, and recordings.
|
|||
|
||||
```bash
|
||||
docker compose down -v
|
||||
rm -rf postgres-data artifacts videos har log .streamlit
|
||||
rm -rf postgres-data artifacts videos har log .skyvern
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
|
|
@ -342,7 +328,7 @@ Common causes:
|
|||
The API key is missing, malformed, or doesn't match the organization. Verify:
|
||||
|
||||
1. The `x-api-key` header is included in your request
|
||||
2. The key matches exactly what's in `.streamlit/secrets.toml`
|
||||
2. The key matches exactly what's in `.skyvern/credentials.toml`
|
||||
3. No extra whitespace or newlines in the key
|
||||
|
||||
```bash
|
||||
|
|
@ -359,9 +345,9 @@ The API key format is invalid (JWT decode failed). This usually means:
|
|||
Regenerate credentials by resetting the installation:
|
||||
|
||||
```bash
|
||||
rm -rf .streamlit
|
||||
rm -rf .skyvern
|
||||
docker compose restart skyvern
|
||||
cat .streamlit/secrets.toml # Get new key
|
||||
cat .skyvern/credentials.toml # Get new key
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
|
|
@ -371,7 +357,7 @@ Check that `skyvern-frontend/.env` has the correct values:
|
|||
```bash
|
||||
VITE_API_BASE_URL=http://localhost:8000/api/v1
|
||||
VITE_WSS_BASE_URL=ws://localhost:8000/api/v1
|
||||
VITE_SKYVERN_API_KEY=<your-key-from-secrets.toml>
|
||||
VITE_SKYVERN_API_KEY=<your-key-from-credentials.toml>
|
||||
```
|
||||
|
||||
After updating, restart the UI:
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ The backend pod takes 1-2 minutes to become ready as it runs database migrations
|
|||
Wait for the backend pod to show `1/1` in the `READY` column of `kubectl get pods -n skyvern` before running this command. The API key file is generated during startup and won't exist until the pod is ready.
|
||||
|
||||
```bash
|
||||
kubectl exec -n skyvern deployment/skyvern-backend -- cat /app/.streamlit/secrets.toml
|
||||
kubectl exec -n skyvern deployment/skyvern-backend -- cat /app/.skyvern/credentials.toml
|
||||
```
|
||||
|
||||
Copy the `cred` value and update `frontend/frontend-secrets.yaml`:
|
||||
|
|
@ -434,7 +434,7 @@ This removes all resources in the `skyvern` namespace.
|
|||
To clean up host storage (if using hostPath):
|
||||
|
||||
```bash
|
||||
rm -rf /data/artifacts /data/videos /data/har /data/log /app/.streamlit
|
||||
rm -rf /data/artifacts /data/videos /data/har /data/log /app/.skyvern
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -62,13 +62,15 @@ if [ "$run_migration" = true ]; then
|
|||
alembic check
|
||||
fi
|
||||
|
||||
if [ ! -f ".streamlit/secrets.toml" ]; then
|
||||
SKYVERN_CREDENTIALS_FILE="${SKYVERN_CREDENTIALS_FILE:-/app/.skyvern/credentials.toml}"
|
||||
mkdir -p "$(dirname "$SKYVERN_CREDENTIALS_FILE")"
|
||||
|
||||
if [ ! -f "$SKYVERN_CREDENTIALS_FILE" ]; then
|
||||
echo "Creating organization and API token..."
|
||||
org_output=$(python scripts/create_organization.py Skyvern-Open-Source)
|
||||
api_token=$(echo "$org_output" | awk '/token=/{gsub(/.*token='\''|'\''.*/, ""); print}')
|
||||
# Update the secrets-open-source.toml file
|
||||
echo -e "[skyvern]\nconfigs = [\n {\"env\" = \"local\", \"host\" = \"http://skyvern:8000/api/v1\", \"orgs\" = [{name=\"Skyvern\", cred=\"$api_token\"}]}\n]" > .streamlit/secrets.toml
|
||||
echo ".streamlit/secrets.toml file updated with organization details."
|
||||
echo -e "[skyvern]\nconfigs = [\n {\"env\" = \"local\", \"host\" = \"http://skyvern:8000/api/v1\", \"orgs\" = [{name=\"Skyvern\", cred=\"$api_token\"}]}\n]" > "$SKYVERN_CREDENTIALS_FILE"
|
||||
echo "$SKYVERN_CREDENTIALS_FILE file updated with organization details."
|
||||
fi
|
||||
|
||||
_kill_xvfb_on_term() {
|
||||
|
|
|
|||
|
|
@ -8,18 +8,19 @@ VITE_WSS_BASE_URL="${VITE_WSS_BASE_URL:-ws://localhost:8000/api/v1}"
|
|||
VITE_ARTIFACT_API_BASE_URL="${VITE_ARTIFACT_API_BASE_URL:-http://localhost:9090}"
|
||||
|
||||
# Priority for VITE_SKYVERN_API_KEY:
|
||||
# 1. .streamlit/secrets.toml (generated by the backend on first run)
|
||||
# 2. Environment variable (from .env file or docker-compose environment),
|
||||
# 1. Environment variable (from .env file or docker-compose environment),
|
||||
# but only if it looks like a real key (not a placeholder)
|
||||
SECRETS_KEY=$(sed -n 's/.*cred\s*=\s*"\([^"]*\)".*/\1/p' .streamlit/secrets.toml 2>/dev/null || echo "")
|
||||
if [ -n "$SECRETS_KEY" ]; then
|
||||
VITE_SKYVERN_API_KEY="$SECRETS_KEY"
|
||||
echo "Using VITE_SKYVERN_API_KEY from .streamlit/secrets.toml"
|
||||
elif [ -n "$VITE_SKYVERN_API_KEY" ] && [ "$VITE_SKYVERN_API_KEY" != "YOUR_API_KEY" ]; then
|
||||
# 2. Generated credentials file from the backend first-run setup
|
||||
SKYVERN_CREDENTIALS_FILE="${SKYVERN_CREDENTIALS_FILE:-/app/.skyvern/credentials.toml}"
|
||||
GENERATED_KEY=$(sed -n 's/.*cred\s*=\s*"\([^"]*\)".*/\1/p' "$SKYVERN_CREDENTIALS_FILE" 2>/dev/null || echo "")
|
||||
if [ -n "$VITE_SKYVERN_API_KEY" ] && [ "$VITE_SKYVERN_API_KEY" != "YOUR_API_KEY" ]; then
|
||||
VITE_SKYVERN_API_KEY=$(echo "$VITE_SKYVERN_API_KEY" | xargs)
|
||||
echo "Using VITE_SKYVERN_API_KEY from environment variable"
|
||||
elif [ -n "$GENERATED_KEY" ]; then
|
||||
VITE_SKYVERN_API_KEY="$GENERATED_KEY"
|
||||
echo "Using VITE_SKYVERN_API_KEY from $SKYVERN_CREDENTIALS_FILE"
|
||||
else
|
||||
echo "WARNING: No VITE_SKYVERN_API_KEY found in .streamlit/secrets.toml or environment"
|
||||
echo "WARNING: No VITE_SKYVERN_API_KEY found in environment or $SKYVERN_CREDENTIALS_FILE"
|
||||
VITE_SKYVERN_API_KEY=""
|
||||
fi
|
||||
|
||||
|
|
@ -37,4 +38,3 @@ find /app/dist -name "*.js" -exec sed -i \
|
|||
node localServer.js &
|
||||
node artifactServer.js &
|
||||
wait
|
||||
|
||||
|
|
|
|||
|
|
@ -31,11 +31,12 @@ spec:
|
|||
mountPath: /data/har
|
||||
- name: log
|
||||
mountPath: /data/log
|
||||
- name: streamlit
|
||||
mountPath: /app/.streamlit
|
||||
- name: credentials
|
||||
mountPath: /app/.skyvern
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["test", "-f", "/app/.streamlit/secrets.toml"]
|
||||
httpGet:
|
||||
path: /api/v1/heartbeat
|
||||
port: 8000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
|
|
@ -57,7 +58,7 @@ spec:
|
|||
hostPath:
|
||||
path: /data/log
|
||||
type: DirectoryOrCreate
|
||||
- name: streamlit
|
||||
- name: credentials
|
||||
hostPath:
|
||||
path: /app/.streamlit
|
||||
path: /app/.skyvern
|
||||
type: DirectoryOrCreate
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ spec:
|
|||
mountPath: /data/videos
|
||||
- name: har
|
||||
mountPath: /data/har
|
||||
- name: streamlit
|
||||
mountPath: /app/.streamlit
|
||||
- name: credentials
|
||||
mountPath: /app/.skyvern
|
||||
volumes:
|
||||
- name: artifacts
|
||||
hostPath:
|
||||
|
|
@ -44,7 +44,7 @@ spec:
|
|||
hostPath:
|
||||
path: /data/har
|
||||
type: DirectoryOrCreate
|
||||
- name: streamlit
|
||||
- name: credentials
|
||||
hostPath:
|
||||
path: /app/.streamlit
|
||||
path: /app/.skyvern
|
||||
type: DirectoryOrCreate
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import importlib.util
|
|||
import json
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
|
|
@ -25,6 +26,9 @@ from skyvern.cli.console import console
|
|||
|
||||
doctor_app = typer.Typer(help="Check Skyvern installation health.")
|
||||
|
||||
GENERATED_CREDENTIALS_FILE = Path(".skyvern/credentials.toml")
|
||||
LEGACY_STREAMLIT_CREDENTIALS_FILE = Path(".streamlit/secrets.toml")
|
||||
|
||||
|
||||
@dataclass
|
||||
class CheckResult:
|
||||
|
|
@ -460,9 +464,27 @@ def _check_llm_config() -> CheckResult:
|
|||
)
|
||||
|
||||
|
||||
def _read_credential_file(path: Path) -> str:
|
||||
if not path.exists():
|
||||
return ""
|
||||
m = re.search(r'(?<![A-Za-z0-9_])cred\s*=\s*"([^"]*)"', path.read_text())
|
||||
return m.group(1) if m else ""
|
||||
|
||||
|
||||
def _read_generated_credential() -> str:
|
||||
return _read_credential_file(GENERATED_CREDENTIALS_FILE)
|
||||
|
||||
|
||||
def _read_legacy_streamlit_credential() -> str:
|
||||
return _read_credential_file(LEGACY_STREAMLIT_CREDENTIALS_FILE)
|
||||
|
||||
|
||||
def _check_api_key_consistency() -> CheckResult:
|
||||
"""Check that API keys are consistent across backend .env, frontend .env, and secrets.toml."""
|
||||
import re
|
||||
"""Check that local API keys are consistent.
|
||||
|
||||
Generated Docker credentials are only a fallback for compose startup; backend .env
|
||||
remains the preferred source when present.
|
||||
"""
|
||||
|
||||
from dotenv import dotenv_values
|
||||
|
||||
|
|
@ -470,35 +492,34 @@ def _check_api_key_consistency() -> CheckResult:
|
|||
|
||||
backend_env = resolve_backend_env_path()
|
||||
frontend_env = Path("skyvern-frontend/.env")
|
||||
secrets_toml = Path(".streamlit/secrets.toml")
|
||||
|
||||
backend_key = dotenv_values(backend_env).get("SKYVERN_API_KEY", "") if backend_env.exists() else ""
|
||||
frontend_raw = dotenv_values(frontend_env).get("VITE_SKYVERN_API_KEY", "") if frontend_env.exists() else ""
|
||||
frontend_key = "" if frontend_raw in ("", "YOUR_API_KEY") else frontend_raw
|
||||
generated_key = _read_generated_credential()
|
||||
legacy_key = _read_legacy_streamlit_credential()
|
||||
|
||||
secrets_key = ""
|
||||
if secrets_toml.exists():
|
||||
m = re.search(r'cred\s*=\s*"([^"]*)"', secrets_toml.read_text())
|
||||
if m:
|
||||
secrets_key = m.group(1)
|
||||
|
||||
canonical = secrets_key or backend_key
|
||||
# Docker Compose can inject the generated credentials file into the UI
|
||||
# without requiring VITE_SKYVERN_API_KEY in skyvern-frontend/.env.
|
||||
# Legacy installs may only have the API key in the old compatibility file;
|
||||
# treat it as a migration source.
|
||||
canonical = backend_key or generated_key or legacy_key
|
||||
if not canonical:
|
||||
return CheckResult(
|
||||
name="API Key Consistency",
|
||||
status="warn",
|
||||
detail="No API key found in backend .env or .streamlit/secrets.toml",
|
||||
detail="No API key found in backend .env or generated credentials",
|
||||
hint="Run `skyvern init` or `skyvern quickstart` to generate an API key",
|
||||
)
|
||||
|
||||
mismatches: list[str] = []
|
||||
if backend_key and backend_key != canonical:
|
||||
mismatches.append("backend .env differs from secrets.toml")
|
||||
if not backend_key and not generated_key:
|
||||
mismatches.append("SKYVERN_API_KEY not set in backend .env")
|
||||
if not frontend_env.exists():
|
||||
mismatches.append("skyvern-frontend/.env missing")
|
||||
elif not frontend_key:
|
||||
elif not frontend_key and not generated_key:
|
||||
mismatches.append("VITE_SKYVERN_API_KEY not set in frontend .env")
|
||||
elif frontend_key != canonical:
|
||||
elif frontend_key and frontend_key != canonical:
|
||||
mismatches.append("frontend .env differs from backend")
|
||||
|
||||
if mismatches:
|
||||
|
|
@ -509,7 +530,51 @@ def _check_api_key_consistency() -> CheckResult:
|
|||
hint="Run `skyvern doctor --fix` to sync API keys",
|
||||
)
|
||||
|
||||
return CheckResult(name="API Key Consistency", status="ok", detail="Keys consistent across all config files")
|
||||
return CheckResult(name="API Key Consistency", status="ok", detail="Backend and frontend API keys are consistent")
|
||||
|
||||
|
||||
def _check_legacy_streamlit_secrets() -> CheckResult:
|
||||
if not LEGACY_STREAMLIT_CREDENTIALS_FILE.exists():
|
||||
return CheckResult(name="Legacy Streamlit Secrets", status="ok", detail="not present")
|
||||
|
||||
from dotenv import dotenv_values
|
||||
|
||||
from skyvern.utils.env_paths import resolve_backend_env_path
|
||||
|
||||
backend_env = resolve_backend_env_path()
|
||||
backend_key = dotenv_values(backend_env).get("SKYVERN_API_KEY", "") if backend_env.exists() else ""
|
||||
legacy_key = _read_legacy_streamlit_credential()
|
||||
|
||||
if not legacy_key:
|
||||
return CheckResult(
|
||||
name="Legacy Streamlit Secrets",
|
||||
status="warn",
|
||||
detail=".streamlit/secrets.toml exists but no cred value was found",
|
||||
hint="Remove the file, or run `skyvern doctor --fix` to remove the deprecated file",
|
||||
)
|
||||
|
||||
if not backend_key:
|
||||
return CheckResult(
|
||||
name="Legacy Streamlit Secrets",
|
||||
status="warn",
|
||||
detail=".streamlit/secrets.toml has a legacy API key but backend .env is missing SKYVERN_API_KEY",
|
||||
hint="Run `skyvern doctor --fix` to migrate the key into .env and skyvern-frontend/.env",
|
||||
)
|
||||
|
||||
if legacy_key != backend_key:
|
||||
return CheckResult(
|
||||
name="Legacy Streamlit Secrets",
|
||||
status="warn",
|
||||
detail=".streamlit/secrets.toml is deprecated and differs from backend .env",
|
||||
hint="Run `skyvern doctor --fix` to remove the deprecated file",
|
||||
)
|
||||
|
||||
return CheckResult(
|
||||
name="Legacy Streamlit Secrets",
|
||||
status="warn",
|
||||
detail=".streamlit/secrets.toml matches backend .env; deprecated compatibility file only",
|
||||
hint="Run `skyvern doctor --fix` to remove the deprecated file",
|
||||
)
|
||||
|
||||
|
||||
def _redact_password(db_string: str) -> str:
|
||||
|
|
@ -576,6 +641,7 @@ _CHECKS = [
|
|||
_check_docker,
|
||||
_check_llm_config,
|
||||
_check_api_key_consistency,
|
||||
_check_legacy_streamlit_secrets,
|
||||
_check_playwright_browser,
|
||||
_check_port_8000,
|
||||
_check_api_connectivity,
|
||||
|
|
@ -603,6 +669,8 @@ def _try_fix(result: CheckResult) -> bool:
|
|||
return _fix_install_playwright()
|
||||
if result.name == "API Key Consistency" and result.status == "error":
|
||||
return _fix_api_key_consistency()
|
||||
if result.name == "Legacy Streamlit Secrets" and result.status == "warn":
|
||||
return _fix_legacy_streamlit_secrets()
|
||||
if result.name == "Docker" and "not running" in result.detail:
|
||||
console.print(" [yellow]→ Please start Docker Desktop manually[/yellow]")
|
||||
return False
|
||||
|
|
@ -671,8 +739,6 @@ def _fix_start_postgres() -> bool:
|
|||
|
||||
|
||||
def _fix_api_key_consistency() -> bool:
|
||||
import re
|
||||
|
||||
from dotenv import dotenv_values, set_key
|
||||
|
||||
from skyvern.utils.env_paths import resolve_backend_env_path
|
||||
|
|
@ -680,23 +746,22 @@ def _fix_api_key_consistency() -> bool:
|
|||
backend_env = resolve_backend_env_path()
|
||||
frontend_env = Path("skyvern-frontend/.env")
|
||||
frontend_example = Path("skyvern-frontend/.env.example")
|
||||
secrets_toml = Path(".streamlit/secrets.toml")
|
||||
|
||||
backend_key = dotenv_values(backend_env).get("SKYVERN_API_KEY", "") if backend_env.exists() else ""
|
||||
secrets_key = ""
|
||||
if secrets_toml.exists():
|
||||
m = re.search(r'cred\s*=\s*"([^"]*)"', secrets_toml.read_text())
|
||||
if m:
|
||||
secrets_key = m.group(1)
|
||||
generated_key = _read_generated_credential()
|
||||
legacy_key = _read_legacy_streamlit_credential()
|
||||
|
||||
canonical = secrets_key or backend_key
|
||||
canonical = backend_key or generated_key or legacy_key
|
||||
if not canonical:
|
||||
console.print(" [yellow]→ No source API key found to sync from[/yellow]")
|
||||
return False
|
||||
|
||||
if not frontend_env.exists() and frontend_example.exists():
|
||||
import shutil
|
||||
if not backend_env.exists():
|
||||
backend_env.touch()
|
||||
|
||||
set_key(str(backend_env), "SKYVERN_API_KEY", canonical)
|
||||
|
||||
if not frontend_env.exists() and frontend_example.exists():
|
||||
shutil.copy(frontend_example, frontend_env)
|
||||
console.print(" [cyan]Created skyvern-frontend/.env from .env.example[/cyan]")
|
||||
|
||||
|
|
@ -705,8 +770,51 @@ def _fix_api_key_consistency() -> bool:
|
|||
return False
|
||||
|
||||
set_key(str(frontend_env), "VITE_SKYVERN_API_KEY", canonical)
|
||||
source = "secrets.toml" if secrets_key else "backend .env"
|
||||
console.print(f" [green]✅ Synced VITE_SKYVERN_API_KEY in skyvern-frontend/.env (from {source})[/green]")
|
||||
source = (
|
||||
"backend .env"
|
||||
if backend_key
|
||||
else ".skyvern/credentials.toml"
|
||||
if generated_key
|
||||
else "legacy .streamlit/secrets.toml"
|
||||
)
|
||||
console.print(f" [green]✅ Synced local API key across backend and frontend (from {source})[/green]")
|
||||
return True
|
||||
|
||||
|
||||
def _fix_legacy_streamlit_secrets() -> bool:
|
||||
"""Best-effort migration for old .streamlit/secrets.toml installs."""
|
||||
from dotenv import dotenv_values, set_key
|
||||
|
||||
from skyvern.utils.env_paths import resolve_backend_env_path
|
||||
|
||||
backend_env = resolve_backend_env_path()
|
||||
frontend_env = Path("skyvern-frontend/.env")
|
||||
frontend_example = Path("skyvern-frontend/.env.example")
|
||||
backend_key = dotenv_values(backend_env).get("SKYVERN_API_KEY", "") if backend_env.exists() else ""
|
||||
legacy_key = _read_legacy_streamlit_credential()
|
||||
|
||||
if not LEGACY_STREAMLIT_CREDENTIALS_FILE.exists():
|
||||
return False
|
||||
|
||||
if not legacy_key:
|
||||
console.print(
|
||||
" [yellow]→ Legacy .streamlit/secrets.toml has no cred value; leaving it for inspection[/yellow]"
|
||||
)
|
||||
return False
|
||||
|
||||
if not backend_key and legacy_key:
|
||||
if not backend_env.exists():
|
||||
backend_env.touch()
|
||||
set_key(str(backend_env), "SKYVERN_API_KEY", legacy_key)
|
||||
if not frontend_env.exists() and frontend_example.exists():
|
||||
shutil.copy(frontend_example, frontend_env)
|
||||
if frontend_env.exists():
|
||||
set_key(str(frontend_env), "VITE_SKYVERN_API_KEY", legacy_key)
|
||||
console.print(" [green]✅ Migrated legacy .streamlit API key into .env files[/green]")
|
||||
|
||||
LEGACY_STREAMLIT_CREDENTIALS_FILE.unlink()
|
||||
console.print(" [green]✅ Removed deprecated .streamlit/secrets.toml[/green]")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -711,7 +711,7 @@ class Settings(BaseSettings):
|
|||
|
||||
def execute_all_steps(self) -> bool:
|
||||
"""
|
||||
This provides the functionality to execute steps one by one through the Streamlit UI.
|
||||
This provides the functionality to execute steps one by one through the local UI.
|
||||
***Value is always True if ENV is not local.***
|
||||
|
||||
:return: True if env is not local, else the value of EXECUTE_ALL_STEPS
|
||||
|
|
|
|||
70
tests/unit/test_cli_doctor.py
Normal file
70
tests/unit/test_cli_doctor.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from skyvern.cli import doctor
|
||||
|
||||
|
||||
def _prepare_workspace(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
frontend = tmp_path / "skyvern-frontend"
|
||||
frontend.mkdir()
|
||||
(frontend / ".env.example").write_text("VITE_SKYVERN_API_KEY=YOUR_API_KEY\n")
|
||||
return tmp_path
|
||||
|
||||
|
||||
def _write_legacy_secret(tmp_path: Path, body: str) -> Path:
|
||||
legacy = tmp_path / ".streamlit" / "secrets.toml"
|
||||
legacy.parent.mkdir()
|
||||
legacy.write_text(body)
|
||||
return legacy
|
||||
|
||||
|
||||
def test_legacy_streamlit_check_is_ok_when_file_missing(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_prepare_workspace(tmp_path, monkeypatch)
|
||||
|
||||
result = doctor._check_legacy_streamlit_secrets()
|
||||
|
||||
assert result.status == "ok"
|
||||
assert result.detail == "not present"
|
||||
|
||||
|
||||
def test_legacy_streamlit_fix_preserves_unparseable_file(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_prepare_workspace(tmp_path, monkeypatch)
|
||||
legacy = _write_legacy_secret(tmp_path, '[general]\nnot_cred = "keep-me"\n')
|
||||
|
||||
result = doctor._check_legacy_streamlit_secrets()
|
||||
|
||||
assert result.status == "warn"
|
||||
assert "no cred value" in result.detail
|
||||
assert doctor._fix_legacy_streamlit_secrets() is False
|
||||
assert legacy.exists()
|
||||
|
||||
|
||||
def test_legacy_streamlit_fix_migrates_only_parseable_key(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_prepare_workspace(tmp_path, monkeypatch)
|
||||
legacy = _write_legacy_secret(tmp_path, '[general]\ncred = "legacy-key"\n')
|
||||
|
||||
result = doctor._check_legacy_streamlit_secrets()
|
||||
|
||||
assert result.status == "warn"
|
||||
assert "backend .env is missing" in result.detail
|
||||
assert doctor._fix_legacy_streamlit_secrets() is True
|
||||
assert not legacy.exists()
|
||||
assert "SKYVERN_API_KEY='legacy-key'" in (tmp_path / ".env").read_text()
|
||||
assert "VITE_SKYVERN_API_KEY='legacy-key'" in (tmp_path / "skyvern-frontend" / ".env").read_text()
|
||||
|
||||
|
||||
def test_legacy_streamlit_fix_removes_matching_deprecated_file(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_prepare_workspace(tmp_path, monkeypatch)
|
||||
legacy = _write_legacy_secret(tmp_path, '[general]\ncred = "same-key"\n')
|
||||
(tmp_path / ".env").write_text('SKYVERN_API_KEY="same-key"\n')
|
||||
|
||||
result = doctor._check_legacy_streamlit_secrets()
|
||||
|
||||
assert result.status == "warn"
|
||||
assert "deprecated compatibility file" in result.detail
|
||||
assert doctor._fix_legacy_streamlit_secrets() is True
|
||||
assert not legacy.exists()
|
||||
Loading…
Add table
Add a link
Reference in a new issue