socaity-cli 0.1.3__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/PKG-INFO +1 -1
  2. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/pyproject.toml +1 -1
  3. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/deployment.py +13 -3
  4. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/registry_push.py +15 -1
  5. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli.egg-info/PKG-INFO +1 -1
  6. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli.egg-info/SOURCES.txt +2 -1
  7. socaity_cli-0.1.4/test/test_registry_push.py +12 -0
  8. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/README.md +0 -0
  9. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/setup.cfg +0 -0
  10. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/__init__.py +0 -0
  11. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/__main__.py +0 -0
  12. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/apipod_commands.py +0 -0
  13. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/auth.py +0 -0
  14. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/backend_client.py +0 -0
  15. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/cli.py +0 -0
  16. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/clients/__init__.py +0 -0
  17. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/clients/analytics.py +0 -0
  18. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/clients/catalog.py +0 -0
  19. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/clients/deployment.py +0 -0
  20. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/clients/files.py +0 -0
  21. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/clients/jobs.py +0 -0
  22. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/clients/projects.py +0 -0
  23. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/clients/sdk_install.py +0 -0
  24. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/credentials.py +0 -0
  25. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/errors.py +0 -0
  26. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/http.py +0 -0
  27. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/prompts.py +0 -0
  28. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/requires.py +0 -0
  29. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli/sdk_commands.py +0 -0
  30. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli.egg-info/dependency_links.txt +0 -0
  31. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli.egg-info/entry_points.txt +0 -0
  32. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli.egg-info/requires.txt +0 -0
  33. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/socaity_cli.egg-info/top_level.txt +0 -0
  34. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/test/test_cli.py +0 -0
  35. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/test/test_credentials.py +0 -0
  36. {socaity_cli-0.1.3 → socaity_cli-0.1.4}/test/test_private_slot_error.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: socaity-cli
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Socaity platform CLI: login, catalog browsing, SDK installs and APIPod deployment commands.
5
5
  Author: SocAIty
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "socaity-cli"
7
- version = "0.1.3"
7
+ version = "0.1.4"
8
8
  description = "Socaity platform CLI: login, catalog browsing, SDK installs and APIPod deployment commands."
9
9
  requires-python = ">=3.8"
10
10
  authors = [
@@ -17,6 +17,7 @@ from socaity_cli.registry_push import (
17
17
  DockerNotAvailable,
18
18
  PushFailed,
19
19
  image_size_bytes,
20
+ is_transient_push_error,
20
21
  push_image,
21
22
  )
22
23
 
@@ -160,7 +161,9 @@ def push_image_to_registry(
160
161
  print("Could not obtain registry push credentials.")
161
162
  return None
162
163
 
163
- for attempt in range(2):
164
+ refreshed = False
165
+ transient_attempts = 0
166
+ while True:
164
167
  try:
165
168
  digest, _size = push_image(
166
169
  registry=creds["registry"],
@@ -172,12 +175,13 @@ def push_image_to_registry(
172
175
  )
173
176
  return digest
174
177
  except CredentialsExpired:
175
- if attempt == 0:
178
+ if not refreshed:
176
179
  print("Push credentials expired; requesting fresh credentials...")
177
180
  creds = client.get_push_credentials(deployment_id, estimated_size_bytes=estimated_size)
178
181
  if creds is None:
179
182
  print("Could not refresh registry push credentials.")
180
183
  return None
184
+ refreshed = True
181
185
  continue
182
186
  print("Registry rejected the push credentials again.")
183
187
  return None
@@ -185,9 +189,15 @@ def push_image_to_registry(
185
189
  print(str(exc))
186
190
  return None
187
191
  except PushFailed as exc:
192
+ if is_transient_push_error(str(exc)) and transient_attempts < 2:
193
+ transient_attempts += 1
194
+ print(
195
+ f"Push interrupted ({exc}); retrying the same repository "
196
+ f"({transient_attempts}/3)..."
197
+ )
198
+ continue
188
199
  print(str(exc))
189
200
  return None
190
- return None
191
201
 
192
202
 
193
203
  def poll_deployment_until_terminal(
@@ -13,6 +13,14 @@ import tempfile
13
13
  from typing import Optional, Tuple
14
14
 
15
15
  _DIGEST_RE = re.compile(r"digest:\s*(sha256:[0-9a-f]{64})")
16
+ _TRANSIENT_PUSH_MARKERS = (
17
+ "timeout",
18
+ "eof",
19
+ "disconnected",
20
+ "connection reset",
21
+ "tls handshake",
22
+ "unexpected eof",
23
+ )
16
24
 
17
25
 
18
26
  class DockerNotAvailable(RuntimeError):
@@ -27,6 +35,12 @@ class PushFailed(RuntimeError):
27
35
  """Any other push failure; message is safe to print."""
28
36
 
29
37
 
38
+ def is_transient_push_error(message: str) -> bool:
39
+ """True when a docker push failed due to a drop or header timeout, not auth."""
40
+ lower = (message or "").lower()
41
+ return any(marker in lower for marker in _TRANSIENT_PUSH_MARKERS)
42
+
43
+
30
44
  def _docker(args: list, *, env: Optional[dict] = None, input_bytes: Optional[bytes] = None) -> subprocess.CompletedProcess:
31
45
  try:
32
46
  return subprocess.run(["docker", *args], capture_output=True, env=env, input=input_bytes)
@@ -82,7 +96,7 @@ def push_image(
82
96
  raise PushFailed(f"docker login failed: {stderr[:300]}")
83
97
 
84
98
  push = subprocess.Popen(
85
- ["docker", "push", ref],
99
+ ["docker", "push", "--max-concurrent-uploads=1", ref],
86
100
  stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env, text=True,
87
101
  )
88
102
  digest, last_lines = None, []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: socaity-cli
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Socaity platform CLI: login, catalog browsing, SDK installs and APIPod deployment commands.
5
5
  Author: SocAIty
6
6
  License: MIT
@@ -30,4 +30,5 @@ socaity_cli/clients/projects.py
30
30
  socaity_cli/clients/sdk_install.py
31
31
  test/test_cli.py
32
32
  test/test_credentials.py
33
- test/test_private_slot_error.py
33
+ test/test_private_slot_error.py
34
+ test/test_registry_push.py
@@ -0,0 +1,12 @@
1
+ from socaity_cli.registry_push import is_transient_push_error
2
+
3
+
4
+ def test_is_transient_push_error_matches_harbor_timeouts():
5
+ assert is_transient_push_error(
6
+ 'docker push failed: timeout awaiting response headers'
7
+ )
8
+ assert is_transient_push_error("client disconnected during blob PUT")
9
+ assert is_transient_push_error("unexpected EOF")
10
+ assert is_transient_push_error("connection reset by peer")
11
+ assert not is_transient_push_error("denied: requested access to the resource is denied")
12
+ assert not is_transient_push_error("")
File without changes
File without changes