Improve deploy agent safety checks
This commit is contained in:
parent
4cf42fce4c
commit
0abe9cbf4b
|
|
@ -70,6 +70,11 @@ SESSION_STATE:
|
||||||
D10: "Updated ./ollama_client.py for reliability: urlopen timeout=10, try/except guards for HTTPError, URLError, JSONDecodeError, invalid response shape, fallback Exception; errors return 'ERROR: <message>'."
|
D10: "Updated ./ollama_client.py for reliability: urlopen timeout=10, try/except guards for HTTPError, URLError, JSONDecodeError, invalid response shape, fallback Exception; errors return 'ERROR: <message>'."
|
||||||
D11: "Created ./deploy_agent.py: imports ask from ollama_client; generate_compose(service)->strict YAML-only prompt; propagates 'ERROR:' responses; inline test generate_compose('nginx')."
|
D11: "Created ./deploy_agent.py: imports ask from ollama_client; generate_compose(service)->strict YAML-only prompt; propagates 'ERROR:' responses; inline test generate_compose('nginx')."
|
||||||
D12: "User requested git commit on 2026-04-22; commit scope includes ./codex_context.yaml, ./ollama_client.py, ./deploy_agent.py, ./start-codex.sh."
|
D12: "User requested git commit on 2026-04-22; commit scope includes ./codex_context.yaml, ./ollama_client.py, ./deploy_agent.py, ./start-codex.sh."
|
||||||
|
D13: "Git commit created on 2026-04-22: 4cf42fc 'Add local Ollama automation scripts'."
|
||||||
|
D14: "Updated ./deploy_agent.py: added PyYAML validation, requires top-level services key, retries invalid output up to 2 times with corrective prompt, returns 'ERROR: invalid docker-compose' after exhaustion."
|
||||||
|
D15: "Extended ./deploy_agent.py with deploy_service(service): generates compose, writes ./deployments/<service[-n]>/docker-compose.yml without overwriting existing directories, runs 'docker compose up -d' via subprocess, returns DEPLOYED or ERROR."
|
||||||
|
D16: "Updated ./deploy_agent.py with get_service_status(path), post-deploy 'docker compose ps' verification requiring 'Up', error outputs including ps output when available, and pre-deploy 'docker ps' port-80 check that adds prompt note 'Use a different port than 80'."
|
||||||
|
D17: "User requested git commit on 2026-04-22; commit scope includes ./deploy_agent.py and ./codex_context.yaml for deployment status and safety updates."
|
||||||
todos:
|
todos:
|
||||||
T1: "For all future meaningful changes/decisions, update and overwrite ./codex_context.yaml."
|
T1: "For all future meaningful changes/decisions, update and overwrite ./codex_context.yaml."
|
||||||
T2: "DONE: Commit current changes."
|
T2: "DONE: Commit current changes."
|
||||||
|
|
@ -80,6 +85,10 @@ SESSION_STATE:
|
||||||
T7: "DONE: Add minimal local Ollama Python client."
|
T7: "DONE: Add minimal local Ollama Python client."
|
||||||
T8: "DONE: Harden local Ollama Python client error handling."
|
T8: "DONE: Harden local Ollama Python client error handling."
|
||||||
T9: "DONE: Add compose-generation agent using local LLM client."
|
T9: "DONE: Add compose-generation agent using local LLM client."
|
||||||
|
T10: "DONE: Commit local Ollama automation scripts."
|
||||||
|
T11: "DONE: Add docker-compose YAML validation and retry logic."
|
||||||
|
T12: "DONE: Add automatic service deployment workflow."
|
||||||
|
T13: "DONE: Add deployment status verification and basic port-80 safety check."
|
||||||
issues:
|
issues:
|
||||||
I1: "Tailscale DNS health warning: configured DNS servers unreachable."
|
I1: "Tailscale DNS health warning: configured DNS servers unreachable."
|
||||||
I2: "Preferred gateway path unavailable: 100.108.208.3:8080 connection failed."
|
I2: "Preferred gateway path unavailable: 100.108.208.3:8080 connection failed."
|
||||||
|
|
|
||||||
106
deploy_agent.py
106
deploy_agent.py
|
|
@ -1,16 +1,118 @@
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from ollama_client import ask
|
from ollama_client import ask
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
def generate_compose(service: str) -> str:
|
def _build_prompt(service: str, extra: str = "") -> str:
|
||||||
prompt = (
|
prompt = (
|
||||||
"Output ONLY valid docker-compose YAML. No explanations. No markdown.\n\n"
|
"Output ONLY valid docker-compose YAML. No explanations. No markdown.\n\n"
|
||||||
f"Generate a docker-compose file for this service: {service}"
|
f"Generate a docker-compose file for this service: {service}"
|
||||||
)
|
)
|
||||||
|
if extra:
|
||||||
|
prompt = f"{prompt}\n{extra}"
|
||||||
|
return prompt
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_compose(service: str, extra: str = "") -> str:
|
||||||
|
base_prompt = (
|
||||||
|
_build_prompt(service, extra)
|
||||||
|
)
|
||||||
|
retry_prompt = (
|
||||||
|
"Previous output was invalid. Fix YAML and return valid docker-compose only."
|
||||||
|
)
|
||||||
|
|
||||||
|
prompt = base_prompt
|
||||||
|
for _ in range(3):
|
||||||
response = ask(prompt)
|
response = ask(prompt)
|
||||||
if response.startswith("ERROR:"):
|
if response.startswith("ERROR:"):
|
||||||
return response
|
return response
|
||||||
|
try:
|
||||||
|
parsed = yaml.safe_load(response)
|
||||||
|
except yaml.YAMLError:
|
||||||
|
prompt = f"{base_prompt}\n\n{retry_prompt}"
|
||||||
|
continue
|
||||||
|
if isinstance(parsed, dict) and "services" in parsed:
|
||||||
return response
|
return response
|
||||||
|
prompt = f"{base_prompt}\n\n{retry_prompt}"
|
||||||
|
return "ERROR: invalid docker-compose"
|
||||||
|
|
||||||
|
|
||||||
|
def generate_compose(service: str) -> str:
|
||||||
|
return _generate_compose(service)
|
||||||
|
|
||||||
|
|
||||||
|
def get_service_status(path: Path) -> str:
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["docker", "compose", "ps"],
|
||||||
|
cwd=path,
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
return f"ERROR: {exc}"
|
||||||
|
output = result.stdout.strip()
|
||||||
|
if result.returncode != 0:
|
||||||
|
error = result.stderr.strip() or output or "docker compose ps failed"
|
||||||
|
return f"ERROR: {error}"
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def deploy_service(service: str) -> str:
|
||||||
|
prompt_extra = ""
|
||||||
|
try:
|
||||||
|
docker_ps = subprocess.run(
|
||||||
|
["docker", "ps"],
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
docker_ps_output = docker_ps.stdout
|
||||||
|
if ":80->" in docker_ps_output or "0.0.0.0:80-" in docker_ps_output or "[::]:80-" in docker_ps_output:
|
||||||
|
prompt_extra = "Use a different port than 80"
|
||||||
|
except Exception:
|
||||||
|
docker_ps_output = ""
|
||||||
|
|
||||||
|
compose = _generate_compose(service, prompt_extra)
|
||||||
|
if compose.startswith("ERROR:"):
|
||||||
|
return compose
|
||||||
|
|
||||||
|
deployments_dir = Path("./deployments")
|
||||||
|
target_dir = deployments_dir / service
|
||||||
|
suffix = 1
|
||||||
|
while target_dir.exists():
|
||||||
|
target_dir = deployments_dir / f"{service}-{suffix}"
|
||||||
|
suffix += 1
|
||||||
|
|
||||||
|
target_dir.mkdir(parents=True, exist_ok=False)
|
||||||
|
compose_path = target_dir / "docker-compose.yml"
|
||||||
|
compose_path.write_text(compose, encoding="utf-8")
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
["docker", "compose", "up", "-d"],
|
||||||
|
cwd=target_dir,
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError as exc:
|
||||||
|
message = exc.stderr.strip() or exc.stdout.strip() or str(exc)
|
||||||
|
return f"ERROR: {message}"
|
||||||
|
except Exception as exc:
|
||||||
|
return f"ERROR: {exc}"
|
||||||
|
|
||||||
|
status = get_service_status(target_dir)
|
||||||
|
if status.startswith("ERROR:"):
|
||||||
|
return status
|
||||||
|
if "Up" not in status:
|
||||||
|
return f"ERROR: no running services\n{status}"
|
||||||
|
|
||||||
|
return f"DEPLOYED: {target_dir.name}\n{status}"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print(generate_compose("nginx"))
|
print(deploy_service("nginx"))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue