File size: 6,184 Bytes
0f6657d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | # Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.
"""
OpenEnv validate command.
This module provides the 'openenv validate' command to check if environments
are properly configured for multi-mode deployment.
"""
import json
from pathlib import Path
from typing import Annotated
import typer
from openenv.cli._validation import (
build_local_validation_json_report,
format_validation_report,
get_deployment_modes,
validate_multi_mode_deployment,
validate_running_environment,
)
def _looks_like_url(value: str) -> bool:
"""Return True when the value appears to be a URL target."""
candidate = value.strip().lower()
return candidate.startswith("http://") or candidate.startswith("https://")
def validate(
target: Annotated[
str | None,
typer.Argument(
help=(
"Path to the environment directory (default: current directory) "
"or a running OpenEnv URL (http://... or https://...)"
),
),
] = None,
url: Annotated[
str | None,
typer.Option(
"--url",
help="Validate a running OpenEnv server by base URL (e.g. http://localhost:8000)",
),
] = None,
json_output: Annotated[
bool,
typer.Option(
"--json",
help="Output local validation report as JSON (runtime validation is JSON by default)",
),
] = False,
timeout: Annotated[
float,
typer.Option(
"--timeout",
help="HTTP timeout in seconds for runtime validation",
min=0.1,
),
] = 5.0,
verbose: Annotated[
bool, typer.Option("--verbose", "-v", help="Show detailed information")
] = False,
) -> None:
"""
Validate local environments and running OpenEnv servers.
Local validation checks if an environment is properly configured with:
- Required files (pyproject.toml, openenv.yaml, server/app.py, etc.)
- Docker deployment support
- uv run server capability
- python -m module execution
Runtime validation checks if a live OpenEnv server conforms to the
versioned runtime API contract and returns a criteria-based JSON report.
Examples:
# Validate current directory (recommended)
$ cd my_env
$ openenv validate
# Validate a running environment and return JSON criteria
$ openenv validate --url http://localhost:8000
$ openenv validate https://my-env.hf.space
# Validate with detailed output
$ openenv validate --verbose
# Validate specific environment
$ openenv validate envs/echo_env
"""
runtime_target = url
if (
runtime_target is not None
and target is not None
and not _looks_like_url(target)
):
typer.echo(
"Error: Cannot combine a local path argument with --url runtime validation",
err=True,
)
raise typer.Exit(1)
if target is not None and _looks_like_url(target):
if runtime_target is not None and runtime_target != target:
typer.echo(
"Error: Conflicting runtime targets provided via argument and --url",
err=True,
)
raise typer.Exit(1)
runtime_target = target
if runtime_target is not None:
try:
report = validate_running_environment(runtime_target, timeout_s=timeout)
except ValueError as exc:
typer.echo(f"Error: {exc}", err=True)
raise typer.Exit(1) from exc
typer.echo(json.dumps(report, indent=2))
if not report.get("passed", False):
raise typer.Exit(1)
return
# Determine environment path (default to current directory)
if target is None:
env_path_obj = Path.cwd()
else:
env_path_obj = Path(target)
if not env_path_obj.exists():
typer.echo(f"Error: Path does not exist: {env_path_obj}", err=True)
raise typer.Exit(1)
if not env_path_obj.is_dir():
typer.echo(f"Error: Path is not a directory: {env_path_obj}", err=True)
raise typer.Exit(1)
# Check for openenv.yaml to confirm this is an environment directory
openenv_yaml = env_path_obj / "openenv.yaml"
if not openenv_yaml.exists():
typer.echo(
f"Error: Not an OpenEnv environment directory (missing openenv.yaml): {env_path_obj}",
err=True,
)
typer.echo(
"Hint: Run this command from the environment root directory or specify the path",
err=True,
)
raise typer.Exit(1)
env_name = env_path_obj.name
if env_name.endswith("_env"):
base_name = env_name[:-4]
else:
base_name = env_name
# Run validation
is_valid, issues = validate_multi_mode_deployment(env_path_obj)
modes = get_deployment_modes(env_path_obj)
if json_output:
report = build_local_validation_json_report(
env_name=base_name,
env_path=env_path_obj,
is_valid=is_valid,
issues=issues,
deployment_modes=modes if verbose else None,
)
typer.echo(json.dumps(report, indent=2))
if not is_valid:
raise typer.Exit(1)
return
# Show validation report
report = format_validation_report(base_name, is_valid, issues)
typer.echo(report)
# Show deployment modes if verbose
if verbose:
typer.echo("\nSupported deployment modes:")
for mode, supported in modes.items():
status = "[YES]" if supported else "[NO]"
typer.echo(f" {status} {mode}")
if is_valid:
typer.echo("\nUsage examples:")
typer.echo(f" cd {env_path_obj.name} && uv run server")
typer.echo(f" cd {env_path_obj.name} && openenv build")
typer.echo(f" cd {env_path_obj.name} && openenv push")
if not is_valid:
raise typer.Exit(1)
|