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.
- offline_docker/__init__.py +3 -0
- offline_docker/__main__.py +4 -0
- offline_docker/actions.py +173 -0
- offline_docker/build.py +245 -0
- offline_docker/cli.py +486 -0
- offline_docker/config.py +692 -0
- offline_docker/configure.py +282 -0
- offline_docker/context.py +111 -0
- offline_docker/defaults.py +48 -0
- offline_docker/discover.py +139 -0
- offline_docker/docker_cmd.py +136 -0
- offline_docker/download_debs.py +142 -0
- offline_docker/download_images.py +26 -0
- offline_docker/download_npm.py +106 -0
- offline_docker/download_trivy.py +90 -0
- offline_docker/download_wheels.py +363 -0
- offline_docker/download_yarn.py +208 -0
- offline_docker/ensure_base.py +61 -0
- offline_docker/hooks.py +84 -0
- offline_docker/init_cmd.py +54 -0
- offline_docker/load_images.py +34 -0
- offline_docker/oc_discovery.py +206 -0
- offline_docker/resolve.py +306 -0
- offline_docker/scan.py +496 -0
- offline_docker/sources/__init__.py +18 -0
- offline_docker/sources/base.py +18 -0
- offline_docker/sources/filesystem.py +17 -0
- offline_docker/sources/nexus.py +61 -0
- offline_docker/sources/nexus_client.py +67 -0
- offline_docker/sources/proget.py +49 -0
- offline_docker/sources/proget_client.py +48 -0
- offline_docker/sources/upstream.py +36 -0
- offline_docker/status.py +247 -0
- offline_docker/sync_cache.py +125 -0
- offline_docker/trivy_scan.py +86 -0
- offline_docker/tui/__init__.py +19 -0
- offline_docker/tui/app.py +56 -0
- offline_docker/tui/runner.py +87 -0
- offline_docker/tui/screens.py +333 -0
- offline_docker/user_config.py +89 -0
- offline_docker/utils.py +73 -0
- offline_docker/validate.py +53 -0
- offline_docker/wheel_prune.py +141 -0
- offline_docker_cli-2.2.2.dist-info/METADATA +417 -0
- offline_docker_cli-2.2.2.dist-info/RECORD +49 -0
- offline_docker_cli-2.2.2.dist-info/WHEEL +5 -0
- offline_docker_cli-2.2.2.dist-info/entry_points.txt +2 -0
- offline_docker_cli-2.2.2.dist-info/licenses/LICENSE +21 -0
- offline_docker_cli-2.2.2.dist-info/top_level.txt +1 -0
|
@@ -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}")
|
offline_docker/build.py
ADDED
|
@@ -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}: готово ===")
|