dockerls 1.0.0__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.
- dockerls/__init__.py +31 -0
- dockerls/application/__init__.py +0 -0
- dockerls/application/dto/__init__.py +3 -0
- dockerls/application/dto/analysis.py +257 -0
- dockerls/application/services/__init__.py +0 -0
- dockerls/application/services/alternatives_lookup.py +167 -0
- dockerls/application/services/composite_repository.py +106 -0
- dockerls/application/services/cross_validation.py +233 -0
- dockerls/application/services/ecosystems.py +350 -0
- dockerls/application/services/fallback_scanner.py +97 -0
- dockerls/application/services/hardening_analysis.py +174 -0
- dockerls/application/services/migration.py +297 -0
- dockerls/application/services/progress.py +56 -0
- dockerls/application/services/remediation.py +321 -0
- dockerls/application/services/scan_history_store.py +88 -0
- dockerls/application/services/scanner_factory.py +88 -0
- dockerls/application/services/source_registry.py +151 -0
- dockerls/application/services/tag_history_store.py +76 -0
- dockerls/application/services/teardown.py +50 -0
- dockerls/application/services/verdict.py +298 -0
- dockerls/application/services/version_discovery.py +108 -0
- dockerls/application/use_cases/__init__.py +0 -0
- dockerls/application/use_cases/analyze_dockerfile.py +103 -0
- dockerls/application/use_cases/analyze_image.py +173 -0
- dockerls/application/use_cases/build_image.py +1795 -0
- dockerls/application/use_cases/compare_images.py +91 -0
- dockerls/application/use_cases/fleet_scan.py +240 -0
- dockerls/application/use_cases/recommend_images.py +1078 -0
- dockerls/application/use_cases/registry_audit.py +133 -0
- dockerls/application/use_cases/search_images.py +23 -0
- dockerls/application/use_cases/upgrade_base.py +167 -0
- dockerls/cache/__init__.py +0 -0
- dockerls/cache/sqlite_cache.py +184 -0
- dockerls/cli/__init__.py +0 -0
- dockerls/cli/analysis_baseline.py +98 -0
- dockerls/cli/app.py +294 -0
- dockerls/cli/commands/__init__.py +0 -0
- dockerls/cli/commands/advisor.py +262 -0
- dockerls/cli/commands/alternatives.py +291 -0
- dockerls/cli/commands/analyze.py +429 -0
- dockerls/cli/commands/analyze_dockerfile.py +104 -0
- dockerls/cli/commands/base_cmd.py +244 -0
- dockerls/cli/commands/base_image.py +551 -0
- dockerls/cli/commands/build.py +1300 -0
- dockerls/cli/commands/cache_cmd.py +104 -0
- dockerls/cli/commands/compare.py +177 -0
- dockerls/cli/commands/controls.py +110 -0
- dockerls/cli/commands/doctor.py +566 -0
- dockerls/cli/commands/export.py +81 -0
- dockerls/cli/commands/fleet.py +159 -0
- dockerls/cli/commands/health.py +84 -0
- dockerls/cli/commands/login.py +53 -0
- dockerls/cli/commands/policy_cmd.py +111 -0
- dockerls/cli/commands/provenance_cmd.py +162 -0
- dockerls/cli/commands/recommend.py +761 -0
- dockerls/cli/commands/registry_audit_cmd.py +103 -0
- dockerls/cli/commands/sbom.py +144 -0
- dockerls/cli/commands/search.py +86 -0
- dockerls/cli/commands/verify.py +115 -0
- dockerls/cli/commands/version.py +12 -0
- dockerls/cli/commands/vex_cmd.py +117 -0
- dockerls/cli/dependencies.py +530 -0
- dockerls/cli/image_names.py +79 -0
- dockerls/cli/options.py +42 -0
- dockerls/cli/progress.py +145 -0
- dockerls/cli/publish_prompt.py +123 -0
- dockerls/cli/rendering.py +214 -0
- dockerls/cli/runtime.py +65 -0
- dockerls/cli/scan_failure.py +71 -0
- dockerls/cli/text.py +39 -0
- dockerls/cli/validators.py +35 -0
- dockerls/cli/vulnerability_view.py +154 -0
- dockerls/domain/__init__.py +0 -0
- dockerls/domain/entities/__init__.py +75 -0
- dockerls/domain/entities/declared_metadata.py +147 -0
- dockerls/domain/entities/dockerfile_analysis.py +318 -0
- dockerls/domain/entities/image.py +109 -0
- dockerls/domain/entities/image_facts.py +137 -0
- dockerls/domain/entities/recommendation.py +33 -0
- dockerls/domain/entities/scan_result.py +129 -0
- dockerls/domain/entities/vulnerability.py +232 -0
- dockerls/domain/interfaces/__init__.py +17 -0
- dockerls/domain/interfaces/cache_store.py +18 -0
- dockerls/domain/interfaces/dockerfile_validator.py +99 -0
- dockerls/domain/interfaces/eol_checker.py +11 -0
- dockerls/domain/interfaces/image_repository.py +15 -0
- dockerls/domain/interfaces/scanner.py +15 -0
- dockerls/domain/security_controls.py +362 -0
- dockerls/domain/value_objects/__init__.py +49 -0
- dockerls/domain/value_objects/attack_surface.py +198 -0
- dockerls/domain/value_objects/base_recipe.py +600 -0
- dockerls/domain/value_objects/base_upgrade.py +292 -0
- dockerls/domain/value_objects/build_labels.py +99 -0
- dockerls/domain/value_objects/build_policy.py +412 -0
- dockerls/domain/value_objects/confidence.py +156 -0
- dockerls/domain/value_objects/fleet.py +174 -0
- dockerls/domain/value_objects/gate.py +327 -0
- dockerls/domain/value_objects/hardening.py +303 -0
- dockerls/domain/value_objects/image_reference.py +90 -0
- dockerls/domain/value_objects/inheritance.py +352 -0
- dockerls/domain/value_objects/network_policy.py +280 -0
- dockerls/domain/value_objects/production_readiness.py +145 -0
- dockerls/domain/value_objects/provenance.py +211 -0
- dockerls/domain/value_objects/recipe_diff.py +188 -0
- dockerls/domain/value_objects/registry_audit.py +195 -0
- dockerls/domain/value_objects/registry_target.py +225 -0
- dockerls/domain/value_objects/remediation_score.py +62 -0
- dockerls/domain/value_objects/scan_history.py +183 -0
- dockerls/domain/value_objects/scan_plan.py +193 -0
- dockerls/domain/value_objects/scanner_db.py +143 -0
- dockerls/domain/value_objects/security_score.py +160 -0
- dockerls/domain/value_objects/security_tier.py +122 -0
- dockerls/domain/value_objects/tag_history.py +180 -0
- dockerls/domain/value_objects/tool_release.py +253 -0
- dockerls/domain/value_objects/tristate.py +47 -0
- dockerls/domain/value_objects/vex.py +249 -0
- dockerls/exit_codes.py +23 -0
- dockerls/exporters/__init__.py +0 -0
- dockerls/exporters/base.py +17 -0
- dockerls/exporters/csv_exporter.py +85 -0
- dockerls/exporters/factory.py +31 -0
- dockerls/exporters/html_exporter.py +105 -0
- dockerls/exporters/json_exporter.py +19 -0
- dockerls/exporters/markdown_exporter.py +84 -0
- dockerls/exporters/sarif_exporter.py +245 -0
- dockerls/infrastructure/__init__.py +0 -0
- dockerls/infrastructure/config/__init__.py +0 -0
- dockerls/infrastructure/config/policy_file.py +165 -0
- dockerls/infrastructure/config/settings.py +197 -0
- dockerls/infrastructure/database/__init__.py +0 -0
- dockerls/infrastructure/database/models.py +78 -0
- dockerls/infrastructure/dockerfile_validator.py +1899 -0
- dockerls/infrastructure/evidence.py +99 -0
- dockerls/infrastructure/hashing.py +165 -0
- dockerls/infrastructure/logging/__init__.py +0 -0
- dockerls/infrastructure/logging/setup.py +98 -0
- dockerls/infrastructure/network/__init__.py +0 -0
- dockerls/infrastructure/network/guarded_client.py +107 -0
- dockerls/infrastructure/network/host_guard.py +117 -0
- dockerls/infrastructure/redaction.py +135 -0
- dockerls/infrastructure/templates/hardening/alpine.dockerfile +44 -0
- dockerls/infrastructure/templates/hardening/debian.dockerfile +45 -0
- dockerls/infrastructure/templates/hardening/distroless.dockerfile +34 -0
- dockerls/infrastructure/templates/hardening/go-alpine.dockerfile +52 -0
- dockerls/infrastructure/templates/hardening/go-debian.dockerfile +54 -0
- dockerls/infrastructure/templates/hardening/go-distroless.dockerfile +43 -0
- dockerls/infrastructure/templates/hardening/go-scratch.dockerfile +48 -0
- dockerls/infrastructure/templates/hardening/go.dockerfile +50 -0
- dockerls/infrastructure/templates/hardening/gradle-alpine.dockerfile +51 -0
- dockerls/infrastructure/templates/hardening/gradle.dockerfile +52 -0
- dockerls/infrastructure/templates/hardening/java-alpine.dockerfile +50 -0
- dockerls/infrastructure/templates/hardening/java-debian.dockerfile +50 -0
- dockerls/infrastructure/templates/hardening/java-distroless.dockerfile +39 -0
- dockerls/infrastructure/templates/hardening/java-ubuntu.dockerfile +54 -0
- dockerls/infrastructure/templates/hardening/java.dockerfile +60 -0
- dockerls/infrastructure/templates/hardening/maven-alpine.dockerfile +56 -0
- dockerls/infrastructure/templates/hardening/maven.dockerfile +57 -0
- dockerls/infrastructure/templates/hardening/node-alpine.dockerfile +47 -0
- dockerls/infrastructure/templates/hardening/node-debian.dockerfile +54 -0
- dockerls/infrastructure/templates/hardening/node-distroless.dockerfile +46 -0
- dockerls/infrastructure/templates/hardening/node-ubuntu.dockerfile +63 -0
- dockerls/infrastructure/templates/hardening/node.dockerfile +61 -0
- dockerls/infrastructure/templates/hardening/php-alpine.dockerfile +45 -0
- dockerls/infrastructure/templates/hardening/php-debian.dockerfile +45 -0
- dockerls/infrastructure/templates/hardening/php-ubuntu.dockerfile +49 -0
- dockerls/infrastructure/templates/hardening/php.dockerfile +44 -0
- dockerls/infrastructure/templates/hardening/python-alpine.dockerfile +51 -0
- dockerls/infrastructure/templates/hardening/python-debian.dockerfile +54 -0
- dockerls/infrastructure/templates/hardening/python-distroless.dockerfile +51 -0
- dockerls/infrastructure/templates/hardening/python-ubuntu.dockerfile +60 -0
- dockerls/infrastructure/templates/hardening/python.dockerfile +58 -0
- dockerls/infrastructure/templates/hardening/ruby-alpine.dockerfile +48 -0
- dockerls/infrastructure/templates/hardening/ruby-debian.dockerfile +50 -0
- dockerls/infrastructure/templates/hardening/rust-alpine.dockerfile +50 -0
- dockerls/infrastructure/templates/hardening/rust-debian.dockerfile +48 -0
- dockerls/infrastructure/templates/hardening/rust-scratch.dockerfile +44 -0
- dockerls/infrastructure/templates/hardening/rust.dockerfile +54 -0
- dockerls/infrastructure/templates/hardening/ubuntu.dockerfile +49 -0
- dockerls/infrastructure/toolchain/__init__.py +0 -0
- dockerls/infrastructure/toolchain/db_metadata.py +115 -0
- dockerls/infrastructure/toolchain/installer.py +435 -0
- dockerls/integrations/__init__.py +0 -0
- dockerls/integrations/dhi/__init__.py +0 -0
- dockerls/integrations/dhi/catalog.py +457 -0
- dockerls/integrations/dhi/definition.py +151 -0
- dockerls/integrations/dhi/repository.py +238 -0
- dockerls/integrations/dockerhub/__init__.py +0 -0
- dockerls/integrations/dockerhub/client.py +318 -0
- dockerls/integrations/dockerhub/urls.py +75 -0
- dockerls/integrations/endoflife/__init__.py +0 -0
- dockerls/integrations/endoflife/checker.py +216 -0
- dockerls/integrations/engine/__init__.py +0 -0
- dockerls/integrations/engine/batch.py +197 -0
- dockerls/integrations/engine/client.py +330 -0
- dockerls/integrations/engine/locator.py +96 -0
- dockerls/integrations/exploitdb/__init__.py +0 -0
- dockerls/integrations/exploitdb/client.py +271 -0
- dockerls/integrations/grype/__init__.py +0 -0
- dockerls/integrations/grype/scanner.py +336 -0
- dockerls/integrations/registry/__init__.py +0 -0
- dockerls/integrations/registry/hardened.py +284 -0
- dockerls/integrations/registry/inspector.py +420 -0
- dockerls/integrations/registry/oci.py +259 -0
- dockerls/integrations/registry/private.py +79 -0
- dockerls/integrations/registry/urls.py +36 -0
- dockerls/integrations/scan_errors.py +67 -0
- dockerls/integrations/scan_target.py +57 -0
- dockerls/integrations/signing/__init__.py +0 -0
- dockerls/integrations/signing/cosign.py +484 -0
- dockerls/integrations/threat_intel/__init__.py +0 -0
- dockerls/integrations/threat_intel/client.py +290 -0
- dockerls/integrations/trivy/__init__.py +0 -0
- dockerls/integrations/trivy/cache_pool.py +176 -0
- dockerls/integrations/trivy/scanner.py +447 -0
- dockerls/utils/__init__.py +0 -0
- dockerls/utils/auth.py +108 -0
- dockerls/utils/executables.py +39 -0
- dockerls/utils/ignore_file.py +130 -0
- dockerls/utils/rate_limit.py +130 -0
- dockerls/utils/resources.py +183 -0
- dockerls/utils/retry.py +31 -0
- dockerls/utils/safe_yaml.py +166 -0
- dockerls/utils/subprocess_runner.py +216 -0
- dockerls/utils/validation.py +72 -0
- dockerls-1.0.0.dist-info/METADATA +563 -0
- dockerls-1.0.0.dist-info/RECORD +230 -0
- dockerls-1.0.0.dist-info/WHEEL +5 -0
- dockerls-1.0.0.dist-info/entry_points.txt +2 -0
- dockerls-1.0.0.dist-info/licenses/LICENSE +21 -0
- dockerls-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""`dockerls base` -- as bases do seu Dockerfile, conferidas contra o registry.
|
|
2
|
+
|
|
3
|
+
O `analyze-dockerfile` lia o seu projeto e não media nada; o `recommend` media
|
|
4
|
+
e nunca olhava o seu projeto. Este comando é a ponte: ele lê os `FROM`,
|
|
5
|
+
pergunta ao registry qual digest cada tag aponta **agora**, e diz quais bases
|
|
6
|
+
apodreceram.
|
|
7
|
+
|
|
8
|
+
Aplicar por padrão é escolha deliberada, e `--dry-run` existe para quem quer
|
|
9
|
+
ver antes. O motivo é o caso que este comando existe para pegar: uma base
|
|
10
|
+
fixada num digest velho não avisa ninguém, não quebra nada, e continua entrando
|
|
11
|
+
em produção build após build. Um comando que só relata delega ao esquecimento
|
|
12
|
+
exatamente o problema que o esquecimento causou.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import json
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
import typer
|
|
22
|
+
from loguru import logger
|
|
23
|
+
from rich.console import Console
|
|
24
|
+
|
|
25
|
+
from dockerls.cli.dependencies import build_host_guard, build_registry_credentials
|
|
26
|
+
from dockerls.cli.options import OutputFormat, parse_output_format
|
|
27
|
+
from dockerls.cli.text import safe
|
|
28
|
+
from dockerls.domain.value_objects.base_upgrade import BaseStatus
|
|
29
|
+
from dockerls.exit_codes import EXIT_ERROR, EXIT_OK, EXIT_POLICY
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from dockerls.application.services.alternatives_lookup import (
|
|
33
|
+
AlternativeFailure,
|
|
34
|
+
AlternativeSuggestion,
|
|
35
|
+
)
|
|
36
|
+
from dockerls.application.use_cases.upgrade_base import UpgradeBaseResult
|
|
37
|
+
|
|
38
|
+
Alternative = AlternativeSuggestion | AlternativeFailure
|
|
39
|
+
|
|
40
|
+
console = Console()
|
|
41
|
+
|
|
42
|
+
#: Cor por estado. `UNRESOLVED` é amarelo, não verde: não medir não é estar em
|
|
43
|
+
#: dia.
|
|
44
|
+
_COLORS = {
|
|
45
|
+
BaseStatus.PINNED_CURRENT: "green",
|
|
46
|
+
BaseStatus.PINNED_STALE: "red",
|
|
47
|
+
BaseStatus.UNPINNED: "yellow",
|
|
48
|
+
BaseStatus.UNRESOLVED: "yellow",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def base(
|
|
53
|
+
path: str = typer.Argument(".", help="Directory containing a Dockerfile, or the file itself"),
|
|
54
|
+
dry_run: bool = typer.Option(
|
|
55
|
+
False, "--dry-run", help="Show what would change without writing to the file"
|
|
56
|
+
),
|
|
57
|
+
alternatives: bool = typer.Option(
|
|
58
|
+
False,
|
|
59
|
+
"--alternatives",
|
|
60
|
+
help=(
|
|
61
|
+
"Beyond refreshing the digest, look for a safer base for each FROM and "
|
|
62
|
+
"measure both. Requires a scanner and takes minutes"
|
|
63
|
+
),
|
|
64
|
+
),
|
|
65
|
+
output_format: str = typer.Option(
|
|
66
|
+
OutputFormat.TABLE.value, "--format", "-f", help="Output format: table or json"
|
|
67
|
+
),
|
|
68
|
+
no_color: bool = typer.Option(False, "--no-color", help="Disable colored output"),
|
|
69
|
+
) -> None:
|
|
70
|
+
"""Check the Dockerfile bases against the registry and refresh their digests."""
|
|
71
|
+
if no_color:
|
|
72
|
+
console.no_color = True
|
|
73
|
+
fmt = parse_output_format(output_format)
|
|
74
|
+
asyncio.run(_base(path, apply=not dry_run, output_format=fmt, with_alternatives=alternatives))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def _base(
|
|
78
|
+
path: str, *, apply: bool, output_format: OutputFormat, with_alternatives: bool = False
|
|
79
|
+
) -> None:
|
|
80
|
+
# Import tardio: montar o inspector puxa configuração e cliente HTTP, e o
|
|
81
|
+
# `--help` deste comando não precisa de nada disso.
|
|
82
|
+
from dockerls.application.services.tag_history_store import TagHistoryStore
|
|
83
|
+
from dockerls.application.use_cases.upgrade_base import UpgradeBaseUseCase
|
|
84
|
+
from dockerls.cli.dependencies import build_cache
|
|
85
|
+
from dockerls.integrations.registry.inspector import RegistryInspector
|
|
86
|
+
|
|
87
|
+
inspector = RegistryInspector(
|
|
88
|
+
guard=build_host_guard(), credentials=build_registry_credentials()
|
|
89
|
+
)
|
|
90
|
+
# O histórico é um extra sobre o diagnóstico: se o cache não abrir, o
|
|
91
|
+
# comando continua sem ele em vez de falhar por causa de um enfeite.
|
|
92
|
+
try:
|
|
93
|
+
history = TagHistoryStore(build_cache())
|
|
94
|
+
except Exception as e: # pragma: no cover - abrir o cache é o caminho instável
|
|
95
|
+
logger.debug(f"Tag history unavailable: {e}")
|
|
96
|
+
history = TagHistoryStore(None)
|
|
97
|
+
try:
|
|
98
|
+
result = await UpgradeBaseUseCase(inspector, history).execute(path, apply=apply)
|
|
99
|
+
finally:
|
|
100
|
+
await inspector.close()
|
|
101
|
+
|
|
102
|
+
suggestions = await _alternatives_for(result) if with_alternatives else []
|
|
103
|
+
|
|
104
|
+
if output_format == OutputFormat.JSON:
|
|
105
|
+
payload = result.to_dict()
|
|
106
|
+
if with_alternatives:
|
|
107
|
+
payload["alternatives"] = [s.to_dict() for s in suggestions]
|
|
108
|
+
console.print(json.dumps(payload, indent=2, ensure_ascii=False), soft_wrap=True)
|
|
109
|
+
else:
|
|
110
|
+
_render(result, apply=apply)
|
|
111
|
+
if with_alternatives:
|
|
112
|
+
_render_alternatives(suggestions)
|
|
113
|
+
|
|
114
|
+
if result.error:
|
|
115
|
+
raise typer.Exit(EXIT_ERROR)
|
|
116
|
+
# Reprova quando sobrou base desatualizada: em `--dry-run` isso é o portão
|
|
117
|
+
# de CI, e depois de aplicar só sobra o que não deu para corrigir.
|
|
118
|
+
raise typer.Exit(EXIT_POLICY if result.needs_action and not result.applied else EXIT_OK)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _render(result: UpgradeBaseResult, *, apply: bool) -> None:
|
|
122
|
+
if result.error:
|
|
123
|
+
console.print(f"[red]Error:[/red] {safe(result.error)}")
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
console.print(f"[bold]{safe(result.dockerfile)}[/bold]\n")
|
|
127
|
+
for finding in result.findings:
|
|
128
|
+
color = _COLORS.get(finding.status, "white")
|
|
129
|
+
stage = f" [dim](stage {safe(finding.base.stage)})[/dim]" if finding.base.stage else ""
|
|
130
|
+
console.print(
|
|
131
|
+
f" line {finding.base.line} [{color}]{finding.status}[/{color}]{stage}\n"
|
|
132
|
+
f" {safe(finding.base.reference)}"
|
|
133
|
+
)
|
|
134
|
+
console.print(f" [dim]{safe(finding.explain())}[/dim]")
|
|
135
|
+
historico = result.history_for(finding.base)
|
|
136
|
+
if historico is not None and historico.moves:
|
|
137
|
+
console.print(f" [dim]history: {safe(historico.explain())}[/dim]")
|
|
138
|
+
if finding.proposed_reference:
|
|
139
|
+
alvo = (
|
|
140
|
+
f"ARG {safe(finding.base.digest_arg)}"
|
|
141
|
+
if finding.base.digest_arg
|
|
142
|
+
else f"line {finding.base.line}"
|
|
143
|
+
)
|
|
144
|
+
console.print(
|
|
145
|
+
f" [green]->[/green] {safe(finding.proposed_reference)} [dim]({alvo})[/dim]"
|
|
146
|
+
)
|
|
147
|
+
console.print()
|
|
148
|
+
|
|
149
|
+
resumo = []
|
|
150
|
+
if result.outdated:
|
|
151
|
+
resumo.append(f"{len(result.outdated)} outdated")
|
|
152
|
+
if result.unpinned:
|
|
153
|
+
resumo.append(f"{len(result.unpinned)} without a digest")
|
|
154
|
+
if resumo:
|
|
155
|
+
console.print(f"[bold]{', '.join(resumo)}[/bold]\n")
|
|
156
|
+
|
|
157
|
+
if result.applied:
|
|
158
|
+
console.print(
|
|
159
|
+
f"[green]{result.applied} update(s) written to {safe(result.dockerfile)}.[/green]"
|
|
160
|
+
)
|
|
161
|
+
console.print(
|
|
162
|
+
"[dim]Rebuild and scan before publishing: changing the base digest "
|
|
163
|
+
"changes the image, and only a scan tells you whether for the better.[/dim]"
|
|
164
|
+
)
|
|
165
|
+
elif result.needs_action:
|
|
166
|
+
acao = "Nothing was written (--dry-run)." if not apply else "Nothing could be written."
|
|
167
|
+
console.print(f"[yellow]{acao}[/yellow]")
|
|
168
|
+
else:
|
|
169
|
+
console.print("[green]Every base is at the digest its tag points to today.[/green]")
|
|
170
|
+
|
|
171
|
+
if result.unresolved:
|
|
172
|
+
console.print(
|
|
173
|
+
f"[yellow]{len(result.unresolved)} base(s) could not be queried on the "
|
|
174
|
+
"registry -- that is an absent answer, not confirmation that they are "
|
|
175
|
+
"up to date.[/yellow]"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
async def _alternatives_for(result: UpgradeBaseResult) -> list[Alternative]:
|
|
180
|
+
"""Uma alternativa medida para cada base distinta do Dockerfile.
|
|
181
|
+
|
|
182
|
+
Cada base é consultada uma vez: um Dockerfile multi-estágio costuma repetir
|
|
183
|
+
a mesma imagem, e escanear a mesma coisa três vezes só gastaria minutos
|
|
184
|
+
para imprimir a mesma linha.
|
|
185
|
+
"""
|
|
186
|
+
from dockerls.application.services.alternatives_lookup import best_alternative
|
|
187
|
+
from dockerls.cli.dependencies import build_analyze_use_case, build_recommend_use_case
|
|
188
|
+
|
|
189
|
+
referencias: list[str] = []
|
|
190
|
+
for finding in result.findings:
|
|
191
|
+
reference = f"{finding.base.name}:{finding.base.tag or 'latest'}"
|
|
192
|
+
if finding.base.name and reference not in referencias:
|
|
193
|
+
referencias.append(reference)
|
|
194
|
+
if not referencias:
|
|
195
|
+
return []
|
|
196
|
+
|
|
197
|
+
console.print(
|
|
198
|
+
"\n[dim]Measuring alternatives: each base is scanned alongside the "
|
|
199
|
+
"candidates, because a comparison between a measurement and a reputation is "
|
|
200
|
+
"not a comparison.[/dim]"
|
|
201
|
+
)
|
|
202
|
+
analyzer = await build_analyze_use_case()
|
|
203
|
+
recommender = await build_recommend_use_case()
|
|
204
|
+
return [
|
|
205
|
+
await best_alternative(reference, analyzer=analyzer, recommender=recommender)
|
|
206
|
+
for reference in referencias
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _render_alternatives(suggestions: list[Alternative]) -> None:
|
|
211
|
+
from dockerls.application.services.alternatives_lookup import AlternativeFailure
|
|
212
|
+
|
|
213
|
+
console.print("\n[bold]Measured alternatives[/bold]\n")
|
|
214
|
+
for item in suggestions:
|
|
215
|
+
if isinstance(item, AlternativeFailure):
|
|
216
|
+
# Não medir nunca vira "não há nada melhor": são frases diferentes
|
|
217
|
+
# e levam a decisões diferentes.
|
|
218
|
+
console.print(f" [yellow]?[/yellow] {safe(item.reference)}")
|
|
219
|
+
console.print(f" [dim]{safe(item.reason)}[/dim]")
|
|
220
|
+
continue
|
|
221
|
+
cor = "green" if item.improves else "yellow"
|
|
222
|
+
console.print(f" {safe(item.reference)}")
|
|
223
|
+
console.print(
|
|
224
|
+
f" [{cor}]-> {safe(item.plan.to_pinned_reference or item.plan.to_reference)}[/{cor}]"
|
|
225
|
+
)
|
|
226
|
+
console.print(
|
|
227
|
+
f" [dim]CRITICAL {item.plan.critical_delta:+d}, "
|
|
228
|
+
f"HIGH {item.plan.high_delta:+d}, score {item.plan.score_delta:+.1f}[/dim]"
|
|
229
|
+
)
|
|
230
|
+
for troca in item.plan.trade_offs:
|
|
231
|
+
console.print(f" [yellow]![/yellow] [dim]{safe(troca)}[/dim]")
|
|
232
|
+
if not item.improves:
|
|
233
|
+
console.print(
|
|
234
|
+
" [dim]the best-placed candidate does not improve on what was "
|
|
235
|
+
"measured; reported anyway, because hiding what came out worse would "
|
|
236
|
+
"turn the list into an argument.[/dim]"
|
|
237
|
+
)
|
|
238
|
+
console.print()
|
|
239
|
+
|
|
240
|
+
console.print(
|
|
241
|
+
"[dim]Nothing here is applied: switching base family is an architecture "
|
|
242
|
+
"decision, not a digest refresh. `base` writes digests; swapping the image "
|
|
243
|
+
"is yours.[/dim]"
|
|
244
|
+
)
|