From f115aa545208ace7b99895b76ba6487be3804eee Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Wed, 18 Feb 2026 15:56:59 +0000 Subject: [PATCH 1/7] ci: add 429 support for pulling images --- .ci/pull_fluent_image.py | 50 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/.ci/pull_fluent_image.py b/.ci/pull_fluent_image.py index d30565faed3b..4fdb35b4cebc 100644 --- a/.ci/pull_fluent_image.py +++ b/.ci/pull_fluent_image.py @@ -2,19 +2,65 @@ 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.""" 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, + capture_output=True, + text=True, + ) + break # Success, exit retry loop + except subprocess.CalledProcessError as e: + stderr_output = e.stderr.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", e.stderr, re.IGNORECASE + ) + if match: + retry_after = float(match.group(1)) / 1000 + + # Use retry-after if available, otherwise exponential backoff + delay = retry_after if retry_after 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 + raise + subprocess.run(["docker", "image", "prune", "-f"], check=True) From a42b2d321e826d7105fa4ba955ebad15c93c1937 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:58:45 +0000 Subject: [PATCH 2/7] chore: adding changelog file 4940.maintenance.md [dependabot-skip] --- doc/changelog.d/4940.maintenance.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/4940.maintenance.md diff --git a/doc/changelog.d/4940.maintenance.md b/doc/changelog.d/4940.maintenance.md new file mode 100644 index 000000000000..a272a1098178 --- /dev/null +++ b/doc/changelog.d/4940.maintenance.md @@ -0,0 +1 @@ +Add 429 support for pulling images From 08fc1510ba931e20754c281380b258245aa25ae1 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Mon, 30 Mar 2026 12:58:04 +0100 Subject: [PATCH 3/7] respond to copilot review --- .ci/pull_fluent_image.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.ci/pull_fluent_image.py b/.ci/pull_fluent_image.py index 4fdb35b4cebc..9384e66ebce4 100644 --- a/.ci/pull_fluent_image.py +++ b/.ci/pull_fluent_image.py @@ -24,15 +24,18 @@ def pull_fluent_image(): # pylint: disable=missing-raises-doc for attempt in range(MAX_RETRIES): try: - subprocess.run( + result = subprocess.run( ["docker", "pull", full_image_name], check=True, capture_output=True, text=True, ) + if result.stdout: + print(result.stdout, end="") + print(f"Successfully pulled Docker image: {full_image_name}") break # Success, exit retry loop except subprocess.CalledProcessError as e: - stderr_output = e.stderr.lower() + 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: @@ -40,7 +43,7 @@ def pull_fluent_image(): # pylint: disable=missing-raises-doc # Parse retry-after hint if available retry_after = None match = re.search( - r"retry-after:\s*([\d.]+)\s*ms", e.stderr, re.IGNORECASE + r"retry-after:\s*([\d.]+)\s*ms", stderr_output, re.IGNORECASE ) if match: retry_after = float(match.group(1)) / 1000 From ab0f5a8959f2e260de1b3a4608776a4e08238f6f Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Mon, 30 Mar 2026 13:09:14 +0100 Subject: [PATCH 4/7] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .ci/pull_fluent_image.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.ci/pull_fluent_image.py b/.ci/pull_fluent_image.py index 9384e66ebce4..5f9854b703a5 100644 --- a/.ci/pull_fluent_image.py +++ b/.ci/pull_fluent_image.py @@ -27,11 +27,9 @@ def pull_fluent_image(): # pylint: disable=missing-raises-doc result = subprocess.run( ["docker", "pull", full_image_name], check=True, - capture_output=True, + stderr=subprocess.PIPE, text=True, ) - if result.stdout: - print(result.stdout, end="") print(f"Successfully pulled Docker image: {full_image_name}") break # Success, exit retry loop except subprocess.CalledProcessError as e: @@ -43,7 +41,7 @@ def pull_fluent_image(): # pylint: disable=missing-raises-doc # Parse retry-after hint if available retry_after = None match = re.search( - r"retry-after:\s*([\d.]+)\s*ms", stderr_output, re.IGNORECASE + r"retry-after:\s*([\d.]+)\s*ms", stderr_output ) if match: retry_after = float(match.group(1)) / 1000 From ebaa65fe32e6118b0cdf4a023c8e9145e5253df9 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Mon, 30 Mar 2026 14:32:01 +0100 Subject: [PATCH 5/7] Update .ci/pull_fluent_image.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .ci/pull_fluent_image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/pull_fluent_image.py b/.ci/pull_fluent_image.py index 5f9854b703a5..504665d2297d 100644 --- a/.ci/pull_fluent_image.py +++ b/.ci/pull_fluent_image.py @@ -47,7 +47,7 @@ def pull_fluent_image(): # pylint: disable=missing-raises-doc retry_after = float(match.group(1)) / 1000 # Use retry-after if available, otherwise exponential backoff - delay = retry_after if retry_after else BASE_DELAY * (2**attempt) + 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})" From aa2f13fd1013804e1f0efe5157862ac125b53ab5 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Thu, 2 Apr 2026 14:27:27 +0100 Subject: [PATCH 6/7] fmt --- .ci/pull_fluent_image.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.ci/pull_fluent_image.py b/.ci/pull_fluent_image.py index 504665d2297d..96be494dfa1a 100644 --- a/.ci/pull_fluent_image.py +++ b/.ci/pull_fluent_image.py @@ -40,14 +40,16 @@ def pull_fluent_image(): # pylint: disable=missing-raises-doc 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 - ) + match = re.search(r"retry-after:\s*([\d.]+)\s*ms", stderr_output) if match: retry_after = float(match.group(1)) / 1000 # Use retry-after if available, otherwise exponential backoff - delay = retry_after if retry_after is not None else BASE_DELAY * (2**attempt) + 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})" From f96318daeaa3c1a44ade5d48ea7c976371c86ee1 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Thu, 2 Apr 2026 14:35:56 +0100 Subject: [PATCH 7/7] remove redundant variable assignment --- .ci/pull_fluent_image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/pull_fluent_image.py b/.ci/pull_fluent_image.py index 96be494dfa1a..fd69369fd453 100644 --- a/.ci/pull_fluent_image.py +++ b/.ci/pull_fluent_image.py @@ -24,7 +24,7 @@ def pull_fluent_image(): # pylint: disable=missing-raises-doc for attempt in range(MAX_RETRIES): try: - result = subprocess.run( + subprocess.run( ["docker", "pull", full_image_name], check=True, stderr=subprocess.PIPE,