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
offline_docker/cli.py
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
"""Universal CLI for Docker offline cache / build / scan."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from offline_docker import __version__, docker_cmd
|
|
10
|
+
from offline_docker.actions import DOCKER_ACTIONS, run_action
|
|
11
|
+
from offline_docker.context import ProjectContext
|
|
12
|
+
|
|
13
|
+
_MAIN_EPILOG = """\
|
|
14
|
+
примеры:
|
|
15
|
+
offdock configure
|
|
16
|
+
offdock init -p .
|
|
17
|
+
offdock discover -p .
|
|
18
|
+
offdock cache -p /path/to/project
|
|
19
|
+
offdock cache check -p . # проверка полноты offline-cache
|
|
20
|
+
offdock cache load -p . # *.tar -> docker load
|
|
21
|
+
offdock build -p .
|
|
22
|
+
offdock up -p . --lab
|
|
23
|
+
offdock down -p .
|
|
24
|
+
offdock scan backend
|
|
25
|
+
offdock scan trivy frontend
|
|
26
|
+
offdock tui -p .
|
|
27
|
+
|
|
28
|
+
после pip install: offdock configure (Enter = только offline-cache)
|
|
29
|
+
init: рекурсивный черновик .offline-docker.json (или .generated.json если канон есть)
|
|
30
|
+
конфиг целевого проекта: .offline-docker.json (шаблон: examples/offline-docker.json.example)
|
|
31
|
+
TUI (опционально): pip install -e ".[tui]" && offdock tui
|
|
32
|
+
подробнее: offdock <команда> --help
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
_INIT_EPILOG = """\
|
|
36
|
+
примеры:
|
|
37
|
+
offdock init -p .
|
|
38
|
+
offdock init -p . --dry-run
|
|
39
|
+
|
|
40
|
+
выходной файл:
|
|
41
|
+
нет канона -> .offline-docker.json
|
|
42
|
+
есть .offline-docker.json/.yaml -> .offline-docker.generated.json (канон не трогаем)
|
|
43
|
+
|
|
44
|
+
черновик содержит секцию _init.gaps (список того, что доработать вручную).
|
|
45
|
+
offline-Dockerfile / compose.offline НЕ генерируются — только пути и gaps.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
_CACHE_EPILOG = """\
|
|
49
|
+
примеры:
|
|
50
|
+
offdock cache # полный download offline-cache
|
|
51
|
+
offdock cache wheels
|
|
52
|
+
offdock cache debs
|
|
53
|
+
offdock cache npm
|
|
54
|
+
offdock cache yarn
|
|
55
|
+
offdock cache images
|
|
56
|
+
offdock cache trivy
|
|
57
|
+
offdock cache check -p . # проверить полноту кэша в проекте
|
|
58
|
+
offdock cache load -p . # *.tar -> docker load в локальный daemon
|
|
59
|
+
|
|
60
|
+
cache check - валидация offline-cache (wheels/debs/npm/images/vendor/...),
|
|
61
|
+
бывший validate-offline-cache. Не качает ничего, только проверяет.
|
|
62
|
+
|
|
63
|
+
hooks.after_cache в .offline-docker.json — проектные скрипты после
|
|
64
|
+
стандартного cache (например scripts/cache-vendor.ps1 для vendor/).
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
_UP_EPILOG = """\
|
|
68
|
+
примеры:
|
|
69
|
+
offdock up -p .
|
|
70
|
+
offdock up -p . --lab
|
|
71
|
+
offdock up -p . --online
|
|
72
|
+
offdock up -p . --no-build
|
|
73
|
+
offdock up -p . --no-cache
|
|
74
|
+
offdock up -p . --no-deps backend
|
|
75
|
+
offdock up -p . --lab frontend
|
|
76
|
+
|
|
77
|
+
режимы (взаимоисключающие --lab / --online):
|
|
78
|
+
без флагов offline-стек (compose_up_offline)
|
|
79
|
+
--lab offline + docker-compose.lab.yml
|
|
80
|
+
--online только compose_online
|
|
81
|
+
|
|
82
|
+
--lab:
|
|
83
|
+
Подключает docker-compose.lab.yml к merge-стеку.
|
|
84
|
+
В проекте нужны: файл docker-compose.lab.yml и секция compose_up_offline_lab
|
|
85
|
+
в .offline-docker.json (см. examples/offline-docker.json.example).
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
_DOWN_EPILOG = """\
|
|
89
|
+
примеры:
|
|
90
|
+
offdock down -p .
|
|
91
|
+
offdock down -p . --lab
|
|
92
|
+
offdock down -p . --online
|
|
93
|
+
offdock down -p . backend frontend
|
|
94
|
+
offdock down -p . --lab backend
|
|
95
|
+
|
|
96
|
+
тот же выбор compose-файлов, что у up / up --lab / up --online.
|
|
97
|
+
без --volumes (данные томов не удаляются).
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
_SCAN_EPILOG = """\
|
|
101
|
+
примеры:
|
|
102
|
+
offdock scan backend
|
|
103
|
+
offdock scan frontend
|
|
104
|
+
offdock scan trivy backend
|
|
105
|
+
offdock scan trivy frontend myapp-frontend:latest
|
|
106
|
+
|
|
107
|
+
аргументы:
|
|
108
|
+
[trivy] опционально: сканер (default: trivy)
|
|
109
|
+
target обязательный: backend или frontend
|
|
110
|
+
image опционально: тег образа (default: images.backend / images.frontend из конфига)
|
|
111
|
+
|
|
112
|
+
перед сканом:
|
|
113
|
+
- образ собран локально (build)
|
|
114
|
+
- БД Trivy в offline-cache/trivy/ (cache trivy)
|
|
115
|
+
- образ aquasec/trivy:latest (cache load)
|
|
116
|
+
|
|
117
|
+
параметры scan (severity, scanners) — секция trivy в .offline-docker.json
|
|
118
|
+
лог: {log_dir}/trivy-scan-<target>.log
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
_BUILD_EPILOG = """\
|
|
122
|
+
примеры:
|
|
123
|
+
offdock build -p .
|
|
124
|
+
offdock build -p . backend
|
|
125
|
+
offdock build -p . base backend frontend
|
|
126
|
+
offdock build -p . --no-cache
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
_TUI_EPILOG = """\
|
|
130
|
+
примеры:
|
|
131
|
+
offdock tui
|
|
132
|
+
offdock tui -p .
|
|
133
|
+
|
|
134
|
+
нужна optional-зависимость:
|
|
135
|
+
pip install -e ".[tui]"
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
_CACHE_SUB_TO_ACTION = {
|
|
139
|
+
"wheels": "cache-wheels",
|
|
140
|
+
"debs": "cache-debs",
|
|
141
|
+
"npm": "cache-npm",
|
|
142
|
+
"yarn": "cache-yarn",
|
|
143
|
+
"images": "cache-images",
|
|
144
|
+
"trivy": "cache-trivy",
|
|
145
|
+
"check": "cache-check",
|
|
146
|
+
"load": "cache-load",
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _add_no_cache_arg(parser: argparse.ArgumentParser) -> None:
|
|
151
|
+
parser.add_argument(
|
|
152
|
+
"--no-cache",
|
|
153
|
+
action="store_true",
|
|
154
|
+
help="Собрать без кэша слоёв Docker (docker build/compose build --no-cache)",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _add_project_args(parser: argparse.ArgumentParser) -> None:
|
|
159
|
+
parser.add_argument(
|
|
160
|
+
"--project",
|
|
161
|
+
"-p",
|
|
162
|
+
type=Path,
|
|
163
|
+
default=None,
|
|
164
|
+
help="Путь к корню проекта (default: текущий каталог)",
|
|
165
|
+
)
|
|
166
|
+
parser.add_argument(
|
|
167
|
+
"--config",
|
|
168
|
+
type=Path,
|
|
169
|
+
default=None,
|
|
170
|
+
help="Путь к .offline-docker.json / .yaml (default: в корне проекта)",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _add_lab_online_group(parser: argparse.ArgumentParser) -> None:
|
|
175
|
+
mode = parser.add_mutually_exclusive_group()
|
|
176
|
+
mode.add_argument(
|
|
177
|
+
"--lab",
|
|
178
|
+
action="store_true",
|
|
179
|
+
help="home lab: добавить docker-compose.lab.yml",
|
|
180
|
+
)
|
|
181
|
+
mode.add_argument(
|
|
182
|
+
"--online",
|
|
183
|
+
action="store_true",
|
|
184
|
+
help="online compose (compose_online)",
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _resolve_project_path(explicit: Path | None) -> Path:
|
|
189
|
+
if explicit is not None:
|
|
190
|
+
return explicit.resolve()
|
|
191
|
+
return Path.cwd()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _load_ctx(args: argparse.Namespace) -> ProjectContext:
|
|
195
|
+
root = _resolve_project_path(args.project)
|
|
196
|
+
return ProjectContext.load(
|
|
197
|
+
root,
|
|
198
|
+
config_file=args.config,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _action_opts(args: argparse.Namespace) -> dict:
|
|
203
|
+
opts: dict = {}
|
|
204
|
+
if hasattr(args, "no_cache"):
|
|
205
|
+
opts["no_cache"] = args.no_cache
|
|
206
|
+
if hasattr(args, "targets"):
|
|
207
|
+
opts["targets"] = list(args.targets) if args.targets else None
|
|
208
|
+
if hasattr(args, "lab"):
|
|
209
|
+
opts["lab"] = args.lab
|
|
210
|
+
if hasattr(args, "online"):
|
|
211
|
+
opts["online"] = args.online
|
|
212
|
+
if hasattr(args, "no_build"):
|
|
213
|
+
opts["no_build"] = args.no_build
|
|
214
|
+
if hasattr(args, "no_deps"):
|
|
215
|
+
opts["no_deps"] = args.no_deps
|
|
216
|
+
if hasattr(args, "services"):
|
|
217
|
+
opts["services"] = list(args.services) if args.services else None
|
|
218
|
+
if hasattr(args, "target"):
|
|
219
|
+
opts["target"] = args.target
|
|
220
|
+
if hasattr(args, "image"):
|
|
221
|
+
opts["image"] = args.image
|
|
222
|
+
return opts
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
226
|
+
parser = argparse.ArgumentParser(
|
|
227
|
+
prog="offdock",
|
|
228
|
+
description="CLI для offline-cache, офлайн-сборки Docker и Trivy-сканирования.",
|
|
229
|
+
epilog=_MAIN_EPILOG,
|
|
230
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
231
|
+
)
|
|
232
|
+
parser.add_argument(
|
|
233
|
+
"-V",
|
|
234
|
+
"--version",
|
|
235
|
+
action="version",
|
|
236
|
+
version=f"%(prog)s {__version__}",
|
|
237
|
+
)
|
|
238
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
239
|
+
|
|
240
|
+
from offline_docker.configure import add_configure_parser
|
|
241
|
+
|
|
242
|
+
add_configure_parser(sub)
|
|
243
|
+
|
|
244
|
+
def add_cmd(
|
|
245
|
+
name: str,
|
|
246
|
+
help_text: str,
|
|
247
|
+
*,
|
|
248
|
+
description: str | None = None,
|
|
249
|
+
epilog: str | None = None,
|
|
250
|
+
) -> argparse.ArgumentParser:
|
|
251
|
+
p = sub.add_parser(
|
|
252
|
+
name,
|
|
253
|
+
help=help_text,
|
|
254
|
+
description=description or help_text,
|
|
255
|
+
epilog=epilog,
|
|
256
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
257
|
+
)
|
|
258
|
+
_add_project_args(p)
|
|
259
|
+
return p
|
|
260
|
+
|
|
261
|
+
p_init = add_cmd(
|
|
262
|
+
"init",
|
|
263
|
+
"Рекурсивный scan → черновик .offline-docker.json",
|
|
264
|
+
description=(
|
|
265
|
+
"Рекурсивно ищет compose/Dockerfile/python/frontend, "
|
|
266
|
+
"пишет черновик конфига и список Missing/gaps."
|
|
267
|
+
),
|
|
268
|
+
epilog=_INIT_EPILOG,
|
|
269
|
+
)
|
|
270
|
+
p_init.add_argument(
|
|
271
|
+
"--dry-run",
|
|
272
|
+
action="store_true",
|
|
273
|
+
help="Только отчёт, без записи файла",
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
add_cmd("discover", "Показать обнаруженные зависимости проекта")
|
|
277
|
+
|
|
278
|
+
p_cache = add_cmd(
|
|
279
|
+
"cache",
|
|
280
|
+
"Скачать / проверить / загрузить offline-cache",
|
|
281
|
+
description=(
|
|
282
|
+
"Без подкоманды — полный download. "
|
|
283
|
+
"Подкоманды: wheels, debs, npm, yarn, images, trivy, "
|
|
284
|
+
"check (валидация кэша), load (docker load)."
|
|
285
|
+
),
|
|
286
|
+
epilog=_CACHE_EPILOG,
|
|
287
|
+
)
|
|
288
|
+
cache_sub = p_cache.add_subparsers(dest="cache_cmd", required=False)
|
|
289
|
+
for cname, chelp in (
|
|
290
|
+
("wheels", "Скачать Python wheels"),
|
|
291
|
+
("debs", "Скачать .deb пакеты"),
|
|
292
|
+
("npm", "Скачать npm cache"),
|
|
293
|
+
("yarn", "Скачать yarn cache"),
|
|
294
|
+
("images", "docker pull + save базовых образов"),
|
|
295
|
+
("trivy", "Скачать БД уязвимостей Trivy"),
|
|
296
|
+
("check", "Проверить полноту offline-cache в проекте"),
|
|
297
|
+
("load", "docker load из offline-cache/docker"),
|
|
298
|
+
):
|
|
299
|
+
cp = cache_sub.add_parser(cname, help=chelp)
|
|
300
|
+
_add_project_args(cp)
|
|
301
|
+
|
|
302
|
+
p_build = add_cmd(
|
|
303
|
+
"build",
|
|
304
|
+
"Offline build base/backend/frontend",
|
|
305
|
+
epilog=_BUILD_EPILOG,
|
|
306
|
+
)
|
|
307
|
+
p_build.add_argument(
|
|
308
|
+
"targets",
|
|
309
|
+
nargs="*",
|
|
310
|
+
default=None,
|
|
311
|
+
help="base backend frontend (default: из config.build_services)",
|
|
312
|
+
)
|
|
313
|
+
_add_no_cache_arg(p_build)
|
|
314
|
+
|
|
315
|
+
p_up = add_cmd(
|
|
316
|
+
"up",
|
|
317
|
+
"Compose up (offline / --lab / --online)",
|
|
318
|
+
description="Поднять стек: offline (default), --lab или --online.",
|
|
319
|
+
epilog=_UP_EPILOG,
|
|
320
|
+
)
|
|
321
|
+
_add_lab_online_group(p_up)
|
|
322
|
+
p_up.add_argument(
|
|
323
|
+
"--no-build",
|
|
324
|
+
action="store_true",
|
|
325
|
+
help="Не пересобирать образы (только offline/lab)",
|
|
326
|
+
)
|
|
327
|
+
p_up.add_argument(
|
|
328
|
+
"--no-deps",
|
|
329
|
+
action="store_true",
|
|
330
|
+
help="Не пересоздавать зависимые сервисы (только offline/lab)",
|
|
331
|
+
)
|
|
332
|
+
_add_no_cache_arg(p_up)
|
|
333
|
+
p_up.add_argument(
|
|
334
|
+
"services",
|
|
335
|
+
nargs="*",
|
|
336
|
+
default=None,
|
|
337
|
+
help="сервисы (default: все; игнорируется при --online)",
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
p_down = add_cmd(
|
|
341
|
+
"down",
|
|
342
|
+
"Compose down (тот же стек, что up)",
|
|
343
|
+
epilog=_DOWN_EPILOG,
|
|
344
|
+
)
|
|
345
|
+
_add_lab_online_group(p_down)
|
|
346
|
+
p_down.add_argument(
|
|
347
|
+
"services",
|
|
348
|
+
nargs="*",
|
|
349
|
+
default=None,
|
|
350
|
+
help="сервисы (default: весь стек)",
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
p_scan = add_cmd(
|
|
354
|
+
"scan",
|
|
355
|
+
"Скан образа (default: trivy)",
|
|
356
|
+
description="Trivy scan образа backend или frontend (офлайн).",
|
|
357
|
+
epilog=_SCAN_EPILOG,
|
|
358
|
+
)
|
|
359
|
+
p_scan.add_argument(
|
|
360
|
+
"scanner_or_target",
|
|
361
|
+
help="trivy | backend | frontend",
|
|
362
|
+
)
|
|
363
|
+
p_scan.add_argument(
|
|
364
|
+
"target_or_image",
|
|
365
|
+
nargs="?",
|
|
366
|
+
default=None,
|
|
367
|
+
help="target (если первый arg = scanner) или image tag",
|
|
368
|
+
)
|
|
369
|
+
p_scan.add_argument(
|
|
370
|
+
"image",
|
|
371
|
+
nargs="?",
|
|
372
|
+
default=None,
|
|
373
|
+
help="опциональный тег образа",
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
add_cmd(
|
|
377
|
+
"tui",
|
|
378
|
+
"Интерактивный Textual TUI (optional: pip install -e \".[tui]\")",
|
|
379
|
+
description="Terminal UI поверх тех же команд offdock.",
|
|
380
|
+
epilog=_TUI_EPILOG,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
return parser
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _normalize_scan_args(args: argparse.Namespace) -> None:
|
|
387
|
+
"""Resolve scan trivy|backend positional forms into target/image."""
|
|
388
|
+
first = args.scanner_or_target
|
|
389
|
+
second = args.target_or_image
|
|
390
|
+
third = args.image
|
|
391
|
+
if first in ("backend", "frontend"):
|
|
392
|
+
args.target = first
|
|
393
|
+
args.image = second
|
|
394
|
+
args.scanner = "trivy"
|
|
395
|
+
elif first == "trivy":
|
|
396
|
+
if second not in ("backend", "frontend"):
|
|
397
|
+
raise SystemExit(
|
|
398
|
+
"scan trivy: нужен target backend|frontend "
|
|
399
|
+
f"(получено: {second!r})"
|
|
400
|
+
)
|
|
401
|
+
args.target = second
|
|
402
|
+
args.image = third
|
|
403
|
+
args.scanner = "trivy"
|
|
404
|
+
else:
|
|
405
|
+
raise SystemExit(
|
|
406
|
+
f"scan: неизвестный аргумент {first!r}; "
|
|
407
|
+
"ожидается trivy|backend|frontend"
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _resolve_action_name(args: argparse.Namespace) -> str:
|
|
412
|
+
cmd = args.command
|
|
413
|
+
if cmd == "cache":
|
|
414
|
+
sub = getattr(args, "cache_cmd", None)
|
|
415
|
+
if not sub:
|
|
416
|
+
return "cache"
|
|
417
|
+
return _CACHE_SUB_TO_ACTION[sub]
|
|
418
|
+
return cmd
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
422
|
+
"""Parse CLI argv (exposed for tests)."""
|
|
423
|
+
parser = _build_parser()
|
|
424
|
+
args = parser.parse_args(argv)
|
|
425
|
+
if args.command == "scan":
|
|
426
|
+
_normalize_scan_args(args)
|
|
427
|
+
return args
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def main(argv: list[str] | None = None) -> int:
|
|
431
|
+
args = parse_args(argv)
|
|
432
|
+
|
|
433
|
+
if args.command == "configure":
|
|
434
|
+
from offline_docker.configure import run_configure
|
|
435
|
+
|
|
436
|
+
run_configure(args)
|
|
437
|
+
return 0
|
|
438
|
+
|
|
439
|
+
if args.command == "init":
|
|
440
|
+
from offline_docker.init_cmd import run_init
|
|
441
|
+
|
|
442
|
+
run_init(_resolve_project_path(args.project), dry_run=args.dry_run)
|
|
443
|
+
return 0
|
|
444
|
+
|
|
445
|
+
if args.command == "tui":
|
|
446
|
+
try:
|
|
447
|
+
import textual # noqa: F401
|
|
448
|
+
from offline_docker.tui import run_tui
|
|
449
|
+
except ImportError:
|
|
450
|
+
print(
|
|
451
|
+
'TUI требует Textual. Установите: pip install -e ".[tui]"',
|
|
452
|
+
file=sys.stderr,
|
|
453
|
+
)
|
|
454
|
+
return 1
|
|
455
|
+
return run_tui(
|
|
456
|
+
project=_resolve_project_path(args.project),
|
|
457
|
+
config_file=args.config,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
action = _resolve_action_name(args)
|
|
461
|
+
if action in DOCKER_ACTIONS:
|
|
462
|
+
docker_cmd.require_docker()
|
|
463
|
+
|
|
464
|
+
ctx = _load_ctx(args)
|
|
465
|
+
|
|
466
|
+
try:
|
|
467
|
+
run_action(action, ctx, **_action_opts(args))
|
|
468
|
+
except docker_cmd.CommandError as exc:
|
|
469
|
+
print(str(exc), file=sys.stderr)
|
|
470
|
+
return exc.returncode
|
|
471
|
+
except SystemExit as exc:
|
|
472
|
+
code = exc.code
|
|
473
|
+
if code is None:
|
|
474
|
+
return 0
|
|
475
|
+
if isinstance(code, int):
|
|
476
|
+
return code
|
|
477
|
+
return 1
|
|
478
|
+
except ValueError as exc:
|
|
479
|
+
print(str(exc), file=sys.stderr)
|
|
480
|
+
return 1
|
|
481
|
+
|
|
482
|
+
return 0
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
if __name__ == "__main__":
|
|
486
|
+
raise SystemExit(main())
|