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
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())