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.
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/PKG-INFO +1 -1
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/pyproject.toml +1 -1
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/deployment.py +14 -3
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/registry_push.py +12 -1
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli.egg-info/PKG-INFO +1 -1
- socaity_cli-0.1.6/test/test_registry_push.py +32 -0
- socaity_cli-0.1.4/test/test_registry_push.py +0 -12
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/README.md +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/setup.cfg +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/__init__.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/__main__.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/apipod_commands.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/auth.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/backend_client.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/cli.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/__init__.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/analytics.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/catalog.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/deployment.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/files.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/jobs.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/projects.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/clients/sdk_install.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/credentials.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/errors.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/http.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/prompts.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/requires.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli/sdk_commands.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli.egg-info/SOURCES.txt +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli.egg-info/dependency_links.txt +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli.egg-info/entry_points.txt +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli.egg-info/requires.txt +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/socaity_cli.egg-info/top_level.txt +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/test/test_cli.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/test/test_credentials.py +0 -0
- {socaity_cli-0.1.4 → socaity_cli-0.1.6}/test/test_private_slot_error.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "socaity-cli"
|
|
7
|
-
version = "0.1.
|
|
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
|
|
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"
|
|
196
|
-
f"
|
|
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
|
-
|
|
110
|
+
_push_command(ref),
|
|
100
111
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env, text=True,
|
|
101
112
|
)
|
|
102
113
|
digest, last_lines = None, []
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|