socaity-cli 0.1.4__tar.gz → 0.1.6__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 (37) hide show
  1. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/PKG-INFO +1 -1
  2. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/pyproject.toml +1 -1
  3. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/deployment.py +14 -3
  4. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/registry_push.py +12 -1
  5. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli.egg-info/PKG-INFO +1 -1
  6. socaity_cli-0.1.6/test/test_registry_push.py +32 -0
  7. socaity_cli-0.1.4/test/test_registry_push.py +0 -12
  8. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/README.md +0 -0
  9. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/setup.cfg +0 -0
  10. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/__init__.py +0 -0
  11. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/__main__.py +0 -0
  12. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/apipod_commands.py +0 -0
  13. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/auth.py +0 -0
  14. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/backend_client.py +0 -0
  15. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/cli.py +0 -0
  16. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/__init__.py +0 -0
  17. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/analytics.py +0 -0
  18. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/catalog.py +0 -0
  19. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/deployment.py +0 -0
  20. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/files.py +0 -0
  21. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/jobs.py +0 -0
  22. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/projects.py +0 -0
  23. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/sdk_install.py +0 -0
  24. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/credentials.py +0 -0
  25. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/errors.py +0 -0
  26. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/http.py +0 -0
  27. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/prompts.py +0 -0
  28. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/requires.py +0 -0
  29. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/sdk_commands.py +0 -0
  30. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli.egg-info/SOURCES.txt +0 -0
  31. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli.egg-info/dependency_links.txt +0 -0
  32. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli.egg-info/entry_points.txt +0 -0
  33. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli.egg-info/requires.txt +0 -0
  34. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli.egg-info/top_level.txt +0 -0
  35. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/test/test_cli.py +0 -0
  36. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/test/test_credentials.py +0 -0
  37. {socaity_cli-0.1.4 → socaity_cli-0.1.6}/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.4
3
+ Version: 0.1.6
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.4"
7
+ version = "0.1.6"
8
8
  description = "Socaity platform CLI: login, catalog browsing, SDK installs and APIPod deployment commands."
9
9
  requires-python = ">=3.8"
10
10
  authors = [
@@ -29,6 +29,10 @@ _TERMINAL_STATUSES = frozenset({
29
29
  "cancelled",
30
30
  })
31
31
  _POLL_INTERVAL_S = 5.0
32
+ # Harbor often returns 201 minutes after Docker's header timeout. Wait
33
+ # before the next docker push so the blob can be skipped instead of
34
+ # starting a second multi-GB PUT while the first commit is still running.
35
+ _TRANSIENT_PUSH_BACKOFF_S = (120, 240, 360)
32
36
  _DEFAULT_TIMEOUT_S = 3600.0
33
37
 
34
38
 
@@ -189,12 +193,19 @@ def push_image_to_registry(
189
193
  print(str(exc))
190
194
  return None
191
195
  except PushFailed as exc:
192
- if is_transient_push_error(str(exc)) and transient_attempts < 2:
196
+ if (
197
+ is_transient_push_error(str(exc))
198
+ and transient_attempts < len(_TRANSIENT_PUSH_BACKOFF_S)
199
+ ):
200
+ wait = _TRANSIENT_PUSH_BACKOFF_S[transient_attempts]
193
201
  transient_attempts += 1
202
+ print(f"Push interrupted: {exc}")
194
203
  print(
195
- f"Push interrupted ({exc}); retrying the same repository "
196
- f"({transient_attempts}/3)..."
204
+ f"Harbor may still be committing the layer. Waiting {wait}s, "
205
+ f"then retrying the same repository "
206
+ f"({transient_attempts}/{len(_TRANSIENT_PUSH_BACKOFF_S)})..."
197
207
  )
208
+ time.sleep(wait)
198
209
  continue
199
210
  print(str(exc))
200
211
  return None
@@ -41,6 +41,17 @@ def is_transient_push_error(message: str) -> bool:
41
41
  return any(marker in lower for marker in _TRANSIENT_PUSH_MARKERS)
42
42
 
43
43
 
44
+ def _push_command(ref: str) -> list:
45
+ """``docker push`` args. Docker 25+ dropped ``--max-concurrent-uploads``."""
46
+ help_out = _docker(["push", "--help"])
47
+ help_text = (help_out.stdout or b"").decode() + (help_out.stderr or b"").decode()
48
+ args = ["docker", "push"]
49
+ if "--max-concurrent-uploads" in help_text:
50
+ args.append("--max-concurrent-uploads=1")
51
+ args.append(ref)
52
+ return args
53
+
54
+
44
55
  def _docker(args: list, *, env: Optional[dict] = None, input_bytes: Optional[bytes] = None) -> subprocess.CompletedProcess:
45
56
  try:
46
57
  return subprocess.run(["docker", *args], capture_output=True, env=env, input=input_bytes)
@@ -96,7 +107,7 @@ def push_image(
96
107
  raise PushFailed(f"docker login failed: {stderr[:300]}")
97
108
 
98
109
  push = subprocess.Popen(
99
- ["docker", "push", "--max-concurrent-uploads=1", ref],
110
+ _push_command(ref),
100
111
  stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env, text=True,
101
112
  )
102
113
  digest, last_lines = None, []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: socaity-cli
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: Socaity platform CLI: login, catalog browsing, SDK installs and APIPod deployment commands.
5
5
  Author: SocAIty
6
6
  License: MIT
@@ -0,0 +1,32 @@
1
+ from socaity_cli.registry_push import _push_command, 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("")
13
+
14
+
15
+ def test_push_command_omits_unknown_max_concurrent_flag(monkeypatch):
16
+ class _Help:
17
+ stdout = b"Usage: docker push [OPTIONS] NAME[:TAG]\n"
18
+ stderr = b""
19
+
20
+ monkeypatch.setattr("socaity_cli.registry_push._docker", lambda args, **kwargs: _Help())
21
+ assert _push_command("harbor.example/repo:tag") == ["docker", "push", "harbor.example/repo:tag"]
22
+
23
+
24
+ def test_push_command_uses_max_concurrent_when_supported(monkeypatch):
25
+ class _Help:
26
+ stdout = b" --max-concurrent-uploads int Max concurrent uploads\n"
27
+ stderr = b""
28
+
29
+ monkeypatch.setattr("socaity_cli.registry_push._docker", lambda args, **kwargs: _Help())
30
+ assert _push_command("harbor.example/repo:tag") == [
31
+ "docker", "push", "--max-concurrent-uploads=1", "harbor.example/repo:tag",
32
+ ]
@@ -1,12 +0,0 @@
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