offline-docker-cli 2.2.2__py3-none-any.whl

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 (49) hide show
  1. offline_docker/__init__.py +3 -0
  2. offline_docker/__main__.py +4 -0
  3. offline_docker/actions.py +173 -0
  4. offline_docker/build.py +245 -0
  5. offline_docker/cli.py +486 -0
  6. offline_docker/config.py +692 -0
  7. offline_docker/configure.py +282 -0
  8. offline_docker/context.py +111 -0
  9. offline_docker/defaults.py +48 -0
  10. offline_docker/discover.py +139 -0
  11. offline_docker/docker_cmd.py +136 -0
  12. offline_docker/download_debs.py +142 -0
  13. offline_docker/download_images.py +26 -0
  14. offline_docker/download_npm.py +106 -0
  15. offline_docker/download_trivy.py +90 -0
  16. offline_docker/download_wheels.py +363 -0
  17. offline_docker/download_yarn.py +208 -0
  18. offline_docker/ensure_base.py +61 -0
  19. offline_docker/hooks.py +84 -0
  20. offline_docker/init_cmd.py +54 -0
  21. offline_docker/load_images.py +34 -0
  22. offline_docker/oc_discovery.py +206 -0
  23. offline_docker/resolve.py +306 -0
  24. offline_docker/scan.py +496 -0
  25. offline_docker/sources/__init__.py +18 -0
  26. offline_docker/sources/base.py +18 -0
  27. offline_docker/sources/filesystem.py +17 -0
  28. offline_docker/sources/nexus.py +61 -0
  29. offline_docker/sources/nexus_client.py +67 -0
  30. offline_docker/sources/proget.py +49 -0
  31. offline_docker/sources/proget_client.py +48 -0
  32. offline_docker/sources/upstream.py +36 -0
  33. offline_docker/status.py +247 -0
  34. offline_docker/sync_cache.py +125 -0
  35. offline_docker/trivy_scan.py +86 -0
  36. offline_docker/tui/__init__.py +19 -0
  37. offline_docker/tui/app.py +56 -0
  38. offline_docker/tui/runner.py +87 -0
  39. offline_docker/tui/screens.py +333 -0
  40. offline_docker/user_config.py +89 -0
  41. offline_docker/utils.py +73 -0
  42. offline_docker/validate.py +53 -0
  43. offline_docker/wheel_prune.py +141 -0
  44. offline_docker_cli-2.2.2.dist-info/METADATA +417 -0
  45. offline_docker_cli-2.2.2.dist-info/RECORD +49 -0
  46. offline_docker_cli-2.2.2.dist-info/WHEEL +5 -0
  47. offline_docker_cli-2.2.2.dist-info/entry_points.txt +2 -0
  48. offline_docker_cli-2.2.2.dist-info/licenses/LICENSE +21 -0
  49. offline_docker_cli-2.2.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3 @@
1
+ """Universal Docker offline cache / build CLI."""
2
+
3
+ __version__ = "2.2.2"
@@ -0,0 +1,4 @@
1
+ from offline_docker.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,173 @@
1
+ """Shared command handlers for CLI and TUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from offline_docker import docker_cmd
8
+ from offline_docker.build import (
9
+ build_base_offline_cmd,
10
+ build_offline,
11
+ down_stack,
12
+ up_offline,
13
+ up_online,
14
+ )
15
+ from offline_docker.context import ProjectContext
16
+ from offline_docker.discover import format_discovery_report
17
+ from offline_docker.download_debs import download_debs
18
+ from offline_docker.download_images import download_docker_images
19
+ from offline_docker.download_npm import download_npm_cache
20
+ from offline_docker.download_trivy import download_trivy_db
21
+ from offline_docker.download_wheels import download_wheels
22
+ from offline_docker.download_yarn import download_yarn_cache
23
+ from offline_docker.hooks import run_after_cache_hooks
24
+ from offline_docker.load_images import load_docker_images
25
+ from offline_docker.trivy_scan import scan_trivy_offline
26
+ from offline_docker.validate import validate_offline_cache
27
+
28
+ # Canonical action names (CLI 2.x).
29
+ PROJECT_ACTIONS = frozenset(
30
+ {
31
+ "discover",
32
+ "cache",
33
+ "cache-wheels",
34
+ "cache-debs",
35
+ "cache-npm",
36
+ "cache-yarn",
37
+ "cache-images",
38
+ "cache-trivy",
39
+ "cache-check",
40
+ "cache-load",
41
+ "build",
42
+ "build-base",
43
+ "up",
44
+ "up-online",
45
+ "down",
46
+ "scan",
47
+ }
48
+ )
49
+
50
+ DOCKER_ACTIONS = frozenset(PROJECT_ACTIONS - {"discover", "cache-check"})
51
+
52
+
53
+ def resolve_action(name: str) -> str:
54
+ """Validate and return a canonical action name."""
55
+ if name not in PROJECT_ACTIONS:
56
+ raise ValueError(f"Unknown action: {name}")
57
+ return name
58
+
59
+
60
+ def download_offline_cache(ctx: ProjectContext) -> None:
61
+ print("========================================")
62
+ print(" cache (полный набор)")
63
+ print(f" Project: {ctx.root}")
64
+ print("========================================")
65
+ print()
66
+ from offline_docker.sync_cache import maybe_materialize
67
+
68
+ maybe_materialize(ctx)
69
+ download_debs(ctx)
70
+ print()
71
+ download_wheels(ctx)
72
+ print()
73
+ download_npm_cache(ctx)
74
+ print()
75
+ download_yarn_cache(ctx)
76
+ print()
77
+ download_docker_images(ctx)
78
+ print()
79
+ download_trivy_db(ctx)
80
+ print()
81
+ run_after_cache_hooks(ctx)
82
+ print("=== offline-cache готов ===")
83
+ print(docker_cmd.dir_size_human(ctx.offline_cache))
84
+
85
+
86
+ def run_action(name: str, ctx: ProjectContext, **opts: Any) -> None:
87
+ """Run a project-scoped action by name.
88
+
89
+ Supported opts (by action):
90
+ build / build-base / up-online: no_cache
91
+ build: targets (list[str] | None)
92
+ up: lab, no_build, no_deps, services, no_cache, online
93
+ down: lab, online, services
94
+ scan: target, image (scanner defaults to trivy)
95
+ """
96
+ canon = resolve_action(name)
97
+
98
+ if canon == "discover":
99
+ print(format_discovery_report(ctx.config))
100
+ return
101
+ if canon == "cache-wheels":
102
+ download_wheels(ctx)
103
+ return
104
+ if canon == "cache-debs":
105
+ download_debs(ctx)
106
+ return
107
+ if canon == "cache-npm":
108
+ download_npm_cache(ctx)
109
+ return
110
+ if canon == "cache-yarn":
111
+ download_yarn_cache(ctx)
112
+ return
113
+ if canon == "cache-images":
114
+ download_docker_images(ctx)
115
+ return
116
+ if canon == "cache-trivy":
117
+ download_trivy_db(ctx)
118
+ return
119
+ if canon == "cache":
120
+ download_offline_cache(ctx)
121
+ return
122
+ if canon == "cache-check":
123
+ validate_offline_cache(ctx)
124
+ return
125
+ if canon == "cache-load":
126
+ load_docker_images(ctx)
127
+ return
128
+ if canon == "build-base":
129
+ build_base_offline_cmd(ctx, no_cache=bool(opts.get("no_cache")))
130
+ return
131
+ if canon == "build":
132
+ targets = opts.get("targets")
133
+ build_offline(
134
+ ctx,
135
+ list(targets) if targets else None,
136
+ no_cache=bool(opts.get("no_cache")),
137
+ )
138
+ return
139
+ if canon == "up-online":
140
+ up_online(ctx, no_cache=bool(opts.get("no_cache")))
141
+ return
142
+ if canon == "up":
143
+ if opts.get("online"):
144
+ up_online(ctx, no_cache=bool(opts.get("no_cache")))
145
+ return
146
+ services = opts.get("services")
147
+ up_offline(
148
+ ctx,
149
+ lab=bool(opts.get("lab")),
150
+ no_build=bool(opts.get("no_build")),
151
+ no_deps=bool(opts.get("no_deps")),
152
+ no_cache=bool(opts.get("no_cache")),
153
+ services=list(services) if services else None,
154
+ )
155
+ return
156
+ if canon == "down":
157
+ services = opts.get("services")
158
+ down_stack(
159
+ ctx,
160
+ lab=bool(opts.get("lab")),
161
+ online=bool(opts.get("online")),
162
+ services=list(services) if services else None,
163
+ )
164
+ return
165
+ if canon == "scan":
166
+ target = opts.get("target")
167
+ if target not in ("backend", "frontend"):
168
+ raise ValueError("scan requires target=backend|frontend")
169
+ # Future: opts.get("scanner", "trivy")
170
+ scan_trivy_offline(ctx, target, opts.get("image"))
171
+ return
172
+
173
+ raise ValueError(f"Unhandled action: {canon}")
@@ -0,0 +1,245 @@
1
+ """Offline build orchestration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import subprocess
7
+ import tempfile
8
+ from pathlib import Path
9
+
10
+ from offline_docker import docker_cmd
11
+ from offline_docker.context import ProjectContext
12
+ from offline_docker.download_npm import download_npm_cache
13
+ from offline_docker.download_wheels import download_wheels
14
+ from offline_docker.download_yarn import download_yarn_cache
15
+ from offline_docker.ensure_base import build_base_offline, require_base_offline
16
+ from offline_docker.hooks import run_after_cache_hooks
17
+ from offline_docker.load_images import load_docker_images
18
+ from offline_docker.validate import validate_offline_cache
19
+
20
+
21
+ def _stamp_ui_project_label(image: str, project: str) -> None:
22
+ print(f">>> stamp UI label {image} -> com.docker.compose.project={project}")
23
+ with tempfile.TemporaryDirectory(prefix="od-stamp-") as tmp:
24
+ df = Path(tmp) / "Dockerfile"
25
+ df.write_text(
26
+ f"FROM {image}\nLABEL com.docker.compose.project={project}\n",
27
+ encoding="utf-8",
28
+ )
29
+ subprocess.run(
30
+ ["docker", "build", "-t", image, "-f", str(df), tmp],
31
+ check=True,
32
+ )
33
+
34
+
35
+ def build_base_offline_cmd(ctx: ProjectContext, *, no_cache: bool = False) -> None:
36
+ image = os.environ.get("IMAGE", ctx.config.images.base)
37
+ ctx.set_offline_cache_env()
38
+
39
+ print("=== build base ===")
40
+ print(f"Project: {ctx.root}")
41
+ print(f"Offline cache: {ctx.offline_cache}")
42
+ print(f"Image: {image}")
43
+ print()
44
+
45
+ from offline_docker.sync_cache import maybe_materialize
46
+
47
+ maybe_materialize(ctx)
48
+ download_wheels(ctx)
49
+ validate_offline_cache(ctx)
50
+ load_docker_images(ctx)
51
+ build_base_offline(ctx, image, no_cache=no_cache)
52
+
53
+ print()
54
+ print(f"=== Готово: {image} ===")
55
+
56
+
57
+ def build_offline(
58
+ ctx: ProjectContext, targets: list[str] | None = None, *, no_cache: bool = False
59
+ ) -> None:
60
+ if not targets:
61
+ targets = list(ctx.config.build_services)
62
+
63
+ ctx.set_offline_cache_env()
64
+ compose_args = ctx.config.compose_build_file_args()
65
+ env_file_args = ctx.config.compose_env_file_args()
66
+
67
+ print("=== build ===")
68
+ print(f"Project: {ctx.root}")
69
+ print(f"Offline cache: {ctx.offline_cache}")
70
+ print(f"Compose: {' '.join(compose_args)}")
71
+ print(f"Логи: {ctx.log_dir}")
72
+ print(f"Сервисы: {' '.join(targets)}")
73
+ if no_cache:
74
+ print("No-cache: да")
75
+ print()
76
+
77
+ from offline_docker.sync_cache import maybe_materialize
78
+
79
+ maybe_materialize(ctx)
80
+ download_wheels(ctx)
81
+ download_npm_cache(ctx)
82
+ download_yarn_cache(ctx)
83
+ run_after_cache_hooks(ctx)
84
+ validate_offline_cache(ctx)
85
+ load_docker_images(ctx)
86
+
87
+ compose_targets: list[str] = []
88
+ for svc in targets:
89
+ if svc == "base":
90
+ build_base_offline(ctx, no_cache=no_cache)
91
+ else:
92
+ compose_targets.append(svc)
93
+
94
+ uses_shared_base = "base" in ctx.config.build_services
95
+ for svc in compose_targets:
96
+ if svc == "backend" and uses_shared_base:
97
+ require_base_offline(ctx)
98
+ log_file = ctx.offline_build_log(svc)
99
+ print(f">>> compose build {svc} -> {log_file}")
100
+ build_cmd = ["docker", "compose", *env_file_args, *compose_args, "build"]
101
+ if no_cache:
102
+ build_cmd.append("--no-cache")
103
+ build_cmd.append(svc)
104
+ docker_cmd.run_tee(
105
+ build_cmd,
106
+ log_file,
107
+ cwd=ctx.root,
108
+ env={"DOCKER_BUILDKIT": "1"},
109
+ )
110
+
111
+ image_map = {
112
+ "backend": os.environ.get("BACKEND_IMAGE", ctx.config.images.backend),
113
+ "frontend": os.environ.get("FRONTEND_IMAGE", ctx.config.images.frontend),
114
+ }
115
+ for svc in compose_targets:
116
+ image = image_map.get(svc)
117
+ if image:
118
+ _stamp_ui_project_label(image, ctx.config.app_name)
119
+
120
+ print()
121
+ print(f"=== Готово. Логи: {ctx.log_dir}/offline-build-*.log ===")
122
+
123
+
124
+ def up_online(ctx: ProjectContext, *, no_cache: bool = False) -> None:
125
+ """Online compose up (compose_online only). Formerly ``dev-up``."""
126
+ from offline_docker.ensure_base import ensure_base_online
127
+
128
+ compose = ctx.config.compose_online
129
+ print("=== up --online ===")
130
+ print(f"Project: {ctx.root}")
131
+ print(f"Compose: {compose}")
132
+ print()
133
+
134
+ ensure_base_online(ctx, no_cache=no_cache)
135
+ build_cmd = ["docker", "compose", "-f", compose, "build"]
136
+ if no_cache:
137
+ build_cmd.append("--no-cache")
138
+ docker_cmd.run(build_cmd, cwd=ctx.root, env={"DOCKER_BUILDKIT": "1"})
139
+ docker_cmd.run(
140
+ ["docker", "compose", "-f", compose, "up", "-d"],
141
+ cwd=ctx.root,
142
+ env={"DOCKER_BUILDKIT": "1"},
143
+ )
144
+ print()
145
+ print("=== up --online: контейнеры подняты ===")
146
+
147
+
148
+ # Back-compat alias
149
+ dev_up = up_online
150
+
151
+
152
+ def _ensure_traefik_network() -> None:
153
+ subprocess.run(
154
+ ["docker", "network", "create", "traefik_proxy"],
155
+ check=False,
156
+ capture_output=True,
157
+ )
158
+
159
+
160
+ def up_offline(
161
+ ctx: ProjectContext,
162
+ *,
163
+ lab: bool = False,
164
+ no_build: bool = False,
165
+ no_deps: bool = False,
166
+ no_cache: bool = False,
167
+ services: list[str] | None = None,
168
+ ) -> None:
169
+ ctx.set_offline_cache_env()
170
+ compose_args = ctx.config.compose_up_file_args(lab=lab)
171
+ env_file_args = ctx.config.compose_env_file_args()
172
+
173
+ print("=== up ===" if not lab else "=== up --lab ===")
174
+ print(f"Project: {ctx.root}")
175
+ print(f"Offline cache: {ctx.offline_cache}")
176
+ print(f"Compose: {' '.join(compose_args)}")
177
+ if lab:
178
+ print("Lab: docker-compose.lab.yml")
179
+ if no_cache:
180
+ print("No-cache: да")
181
+ print()
182
+
183
+ _ensure_traefik_network()
184
+
185
+ if not no_build:
186
+ from offline_docker.sync_cache import maybe_materialize
187
+
188
+ maybe_materialize(ctx)
189
+ run_after_cache_hooks(ctx)
190
+ validate_offline_cache(ctx)
191
+ load_docker_images(ctx)
192
+
193
+ if no_cache and not no_build:
194
+ build_cmd = ["docker", "compose", *env_file_args, *compose_args, "build", "--no-cache"]
195
+ if services:
196
+ build_cmd.extend(services)
197
+ docker_cmd.run(build_cmd, cwd=ctx.root, env={"DOCKER_BUILDKIT": "1"})
198
+ no_build = True
199
+
200
+ cmd = ["docker", "compose", *env_file_args, *compose_args, "up", "-d"]
201
+ if no_build:
202
+ cmd.append("--no-build")
203
+ if no_deps:
204
+ cmd.append("--no-deps")
205
+ if services:
206
+ cmd.extend(services)
207
+
208
+ docker_cmd.run(cmd, cwd=ctx.root, env={"DOCKER_BUILDKIT": "1"})
209
+ print()
210
+ print("=== up: контейнеры подняты ===")
211
+
212
+
213
+ def down_stack(
214
+ ctx: ProjectContext,
215
+ *,
216
+ lab: bool = False,
217
+ online: bool = False,
218
+ services: list[str] | None = None,
219
+ ) -> None:
220
+ """Compose down for the same stack selection as ``up`` / ``up --lab`` / ``up --online``."""
221
+ if lab and online:
222
+ raise ValueError("--lab and --online are mutually exclusive")
223
+
224
+ env_file_args = ctx.config.compose_env_file_args()
225
+ if online:
226
+ compose = ctx.config.compose_online
227
+ compose_args = ["-f", compose]
228
+ label = "down --online"
229
+ else:
230
+ compose_args = ctx.config.compose_up_file_args(lab=lab)
231
+ label = "down --lab" if lab else "down"
232
+
233
+ print(f"=== {label} ===")
234
+ print(f"Project: {ctx.root}")
235
+ print(f"Compose: {' '.join(compose_args)}")
236
+ if services:
237
+ print(f"Services: {' '.join(services)}")
238
+ print()
239
+
240
+ cmd = ["docker", "compose", *env_file_args, *compose_args, "down"]
241
+ if services:
242
+ cmd.extend(services)
243
+ docker_cmd.run(cmd, cwd=ctx.root, env={"DOCKER_BUILDKIT": "1"})
244
+ print()
245
+ print(f"=== {label}: готово ===")