Skip to content
53 changes: 51 additions & 2 deletions .ci/pull_fluent_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,68 @@
Pull a Fluent Docker image based on the FLUENT_IMAGE_TAG environment variable.
"""

import re
import subprocess
import time

from ansys.fluent.core import config
from ansys.fluent.core.docker.utils import get_ghcr_fluent_image_name

MAX_RETRIES = 5
BASE_DELAY = 1.0 # seconds

def pull_fluent_image():

def pull_fluent_image(): # pylint: disable=missing-raises-doc
"""Pull Fluent Docker image and clean up dangling images."""
Comment thread
Gobot1234 marked this conversation as resolved.
fluent_image_tag = config.fluent_image_tag
image_name = get_ghcr_fluent_image_name(fluent_image_tag)
separator = "@" if fluent_image_tag.startswith("sha256") else ":"
full_image_name = f"{image_name}{separator}{fluent_image_tag}"
subprocess.run(["docker", "pull", full_image_name], check=True)

# Retry logic for handling rate limits (429 errors)

for attempt in range(MAX_RETRIES):
try:
subprocess.run(
["docker", "pull", full_image_name],
check=True,
stderr=subprocess.PIPE,
text=True,
)
print(f"Successfully pulled Docker image: {full_image_name}")
break # Success, exit retry loop
except subprocess.CalledProcessError as e:
stderr_output = (e.stderr or "").lower()

# Check if it's a 429 rate limit error
if "toomanyrequests" in stderr_output or "429" in stderr_output:
if attempt < MAX_RETRIES - 1:
# Parse retry-after hint if available
retry_after = None
match = re.search(r"retry-after:\s*([\d.]+)\s*ms", stderr_output)
if match:
retry_after = float(match.group(1)) / 1000

Comment on lines +43 to +46
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The retry-after parsing only matches values expressed in milliseconds (... ms). If Docker/registry emits Retry-After in seconds (a common format) this hint will be ignored and the script will always fall back to exponential backoff. Consider supporting both s and ms (and/or bare integer seconds) so the backoff respects the server-provided delay when available.

Copilot uses AI. Check for mistakes.
# Use retry-after if available, otherwise exponential backoff
delay = (
retry_after
if retry_after is not None
else BASE_DELAY * (2**attempt)
)

print(
f"Rate limit hit (429), retrying in {delay:.2f} seconds... (attempt {attempt + 1}/{MAX_RETRIES})"
)
time.sleep(delay)
else:
print(
"Max retries reached. Failed to pull image due to rate limiting."
)
raise
else:
# Not a rate limit error, re-raise immediately
Comment on lines +61 to +64
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because stderr is captured (stderr=subprocess.PIPE), Docker error output won’t be streamed to CI logs. When the exception is re-raised (non-429 path, or after max retries), the captured stderr likely won’t be visible, making failures hard to diagnose. Consider printing/logging e.stderr before re-raising (and/or including it in a new exception message) so CI logs retain the original docker pull error details.

Suggested change
)
raise
else:
# Not a rate limit error, re-raise immediately
)
print("docker pull stderr output:\n" + (e.stderr or ""))
raise
else:
# Not a rate limit error, re-raise immediately
print("docker pull stderr output:\n" + (e.stderr or ""))

Copilot uses AI. Check for mistakes.
raise

subprocess.run(["docker", "image", "prune", "-f"], check=True)


Expand Down
1 change: 1 addition & 0 deletions doc/changelog.d/4940.maintenance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add 429 support for pulling images
Loading