mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-11-09 17:53:56 -10:00
Pre-pull the full-stack CI service images through docker-pull-with-retry.sh, excluding images produced by the build. Allow five attempts with 15, 30, 45 and 60 second waits so short registry refusals can recover. Resolves #15977 Co-authored-by: Reinier van der Leer <[email protected]> Co-authored-by: Claude Opus 5.5 (Claude Code) <[email protected]> Co-authored-by: gpt-6-astra (Codex) <[email protected]>
105 lines
3.8 KiB
Python
105 lines
3.8 KiB
Python
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
SCRIPT = Path(__file__).with_name("docker-pull-with-retry.sh")
|
|
BASH = (
|
|
str(Path("C:/Program Files/Git/bin/bash.exe"))
|
|
if sys.platform == "win32"
|
|
else shutil.which("bash")
|
|
)
|
|
|
|
|
|
class DockerPullRetryTests(unittest.TestCase):
|
|
def run_pull(self, failures, args):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
docker = root / "docker"
|
|
docker.write_text(
|
|
"#!/usr/bin/env bash\n"
|
|
'[[ "$#" == 2 && "$1" == pull ]] || exit 90\n'
|
|
'count=0; [[ ! -f "$COUNTER" ]] || read -r count < "$COUNTER"\n'
|
|
'count=$((count + 1)); echo "$count" > "$COUNTER"\n'
|
|
'printf "%s" "$2" > "$ARGUMENT"\n'
|
|
"((count > FAILURES))\n",
|
|
newline="\n",
|
|
)
|
|
sleep = root / "sleep"
|
|
sleep.write_text(
|
|
'#!/usr/bin/env bash\necho "$1" >> "$DELAYS"\n', newline="\n"
|
|
)
|
|
script = root / "pull.sh"
|
|
script.write_text(SCRIPT.read_text(), newline="\n")
|
|
docker.chmod(0o755)
|
|
sleep.chmod(0o755)
|
|
result = subprocess.run(
|
|
[
|
|
BASH,
|
|
"-c",
|
|
'export PATH="$(cd "$STUB_BIN" && pwd):$PATH"; exec bash "$@"',
|
|
"bash",
|
|
str(script),
|
|
*args,
|
|
],
|
|
env={
|
|
**os.environ,
|
|
"PATH": directory + os.pathsep + os.environ["PATH"],
|
|
"STUB_BIN": directory,
|
|
"COUNTER": str(root / "counter"),
|
|
"ARGUMENT": str(root / "argument"),
|
|
"DELAYS": str(root / "delays"),
|
|
"FAILURES": str(failures),
|
|
},
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
count = (
|
|
int((root / "counter").read_text())
|
|
if (root / "counter").exists()
|
|
else 0
|
|
)
|
|
delays = (
|
|
(root / "delays").read_text().splitlines()
|
|
if (root / "delays").exists()
|
|
else []
|
|
)
|
|
argument = (
|
|
(root / "argument").read_text()
|
|
if (root / "argument").exists()
|
|
else None
|
|
)
|
|
return result, count, delays, argument
|
|
|
|
def test_success_after_zero_one_or_four_failures(self):
|
|
for failures in (0, 1, 4):
|
|
with self.subTest(failures=failures):
|
|
result, count, delays, argument = self.run_pull(failures, ["image:tag"])
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual(count, failures + 1)
|
|
self.assertEqual(delays, ["15", "30", "45", "60"][:failures])
|
|
self.assertEqual(argument, "image:tag")
|
|
|
|
def test_terminal_failure_is_not_reported_as_success(self):
|
|
result, count, delays, _ = self.run_pull(5, ["image:tag"])
|
|
self.assertEqual(result.returncode, 1, result.stderr)
|
|
self.assertEqual(count, 5)
|
|
self.assertEqual(delays, ["15", "30", "45", "60"])
|
|
|
|
def test_invalid_arguments_never_call_docker(self):
|
|
for args in ([], [""], ["one", "two"]):
|
|
with self.subTest(args=args):
|
|
result, count, _, _ = self.run_pull(0, args)
|
|
self.assertEqual(result.returncode, 2)
|
|
self.assertEqual(count, 0)
|
|
|
|
def test_image_remains_one_literal_argument(self):
|
|
image = "image with spaces; no-command"
|
|
result, _, _, argument = self.run_pull(0, [image])
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual(argument, image)
|