1pub mod apply;
8pub mod buildkit;
9pub mod cache;
10pub mod containerd;
11pub mod docker;
12pub mod mirror;
13pub mod operations;
14pub mod plan;
15pub mod redact;
16pub mod reference;
17pub mod registry;
18pub mod report;
19pub mod runtime;
20
21pub use apply::{
22 apply_mirror_plan, MirrorApplyError, MirrorApplyReport, MIRROR_APPLY_SCHEMA_VERSION,
23};
24pub use buildkit::{
25 BuildPlatform, BuilderDriver, BuilderNode, BuilderNodeStatus, BuildkitAdapter,
26 BuildkitDiagnosticDetails, BuildkitDiscovery, BuildkitNodeDiagnosticDetails,
27 BuildxBuilderSelector, BuildxBuilderSelectorError, SelectedBuilder,
28};
29pub use cache::{
30 BuildxCacheQuery, CacheQueryStatus, ContainerdCacheQuery, DockerCacheQuery, NativeCacheOwner,
31 NativeCacheRecord, NativeCacheRecordKind, NativeCacheStatus,
32 NATIVE_CACHE_STATUS_SCHEMA_VERSION,
33};
34pub use containerd::{
35 parse_effective_config, parse_hosts_toml, read_hosts_toml, ContainerdAdapter, ContainerdConfig,
36 ContainerdDiagnosticDetails, ContainerdDiscovery, ContainerdParseError, ContainerdVersions,
37 LegacyRegistryWarning, RegistryHost, RegistryHostCapability, RegistryHostTls, RegistryHosts,
38};
39pub use docker::{
40 DockerAdapter, DockerContext, DockerContextKind, DockerDaemonArchitecture, DockerDaemonOs,
41 DockerDiagnosticDetails, DockerDiscovery, DockerInfo, DockerParseError, DockerVersion,
42};
43pub use mirror::{
44 builtin_mirror_policy, plan_buildkit_mirrors, plan_containerd_mirrors, plan_docker_mirrors,
45 BuildkitMirrorPlanRequest, ContainerdMirrorPlanRequest, DockerMirrorPlanRequest,
46 MirrorPlanError, BUILTIN_DOCKER_HUB_MIRRORS, DOCKER_HUB_BENCHMARK_IMAGE,
47 DOCKER_HUB_BENCHMARK_PLATFORM,
48};
49pub use plan::{
50 ActivationRequirement, BuildkitTargetDriver, DockerTargetKind, EffectiveResolution,
51 Fingerprint, MirrorChange, MirrorPlan, MirrorPlanBundle, MirrorPlanDraft, MirrorPlanTarget,
52 NativeCandidateFingerprint, NativeConfigCandidate, NativeConfigFormat, NativeConfigSnapshot,
53 NativeInputFingerprint, NativeInputState, PlanApplicability, PlanError, PlanWarning,
54 PlannedCapability, PlannedMirrorEndpoint, RequiredPrivilege, ValidationStep,
55 MAX_NATIVE_CONFIG_BYTES, MIRROR_PLAN_SCHEMA_VERSION,
56};
57pub use reference::{
58 ImageReference, ImageSelector, ImageTag, OciDigest, OciPlatform, ReferenceError, RegistryName,
59 RepositoryName,
60};
61pub use registry::{
62 diagnose_registry, ApiCheck, ApiCheckStatus, BlobRangeCheck, BlobRangeStatus, ManifestCheck,
63 ManifestCheckStatus, ManifestKind, MirrorCheck, MirrorCheckStatus, RegistryDiagnosticOptions,
64 RegistryDiagnosticReport, RegistryDiagnosticStatus, RegistryEndpoint, RegistryLimits,
65 RegistryMethod, RegistryProtocolError, RegistryRequest, RegistryResponse, RegistryTransport,
66 RegistryTransportError, RegistryTransportFuture, ReqwestRegistryTransport,
67 DEFAULT_BLOB_SAMPLE_BYTES, DEFAULT_MAX_BODY_BYTES, DEFAULT_MAX_MANIFEST_BYTES,
68 DEFAULT_MAX_REDIRECTS, DEFAULT_MAX_REQUESTS, DEFAULT_REQUEST_TIMEOUT, DEFAULT_TOTAL_TIMEOUT,
69 REGISTRY_DIAGNOSTIC_SCHEMA_VERSION,
70};
71
72pub use redact::{
73 CommandPurpose, HeaderName, NativeProgram, RedactedCommand, RedactedHeader, RedactedOrigin,
74 RedactedUrl, RedactedUrlError, RedactedValue, REDACTED,
75};
76pub use report::{
77 Capability, CapabilityStatus, DiagnosticDetails, DiagnosticEvidence, DiagnosticReport,
78 DiagnosticStatus, Endpoint, EndpointScope, EndpointTransport, Privilege, RuntimeKind,
79 DIAGNOSTIC_SCHEMA_VERSION,
80};
81pub use runtime::{ForegroundCommand, ProbeCommand, RuntimeAdapter};
82
83pub(crate) fn parse_vendor_version(candidate: &str) -> Option<semver::Version> {
91 let candidate = candidate.trim().trim_matches(|character: char| {
92 matches!(
93 character,
94 ',' | ';' | ':' | '(' | ')' | '[' | ']' | '{' | '}' | '"' | '\''
95 )
96 });
97 let candidate = candidate.strip_prefix(['v', 'V']).unwrap_or(candidate);
98 if let Ok(version) = semver::Version::parse(candidate) {
99 return Some(version);
100 }
101
102 if let Some((upstream, _vendor)) = candidate.split_once('~') {
106 if let Ok(version) = semver::Version::parse(upstream) {
107 return Some(version);
108 }
109 }
110
111 let components = candidate.split('.').collect::<Vec<_>>();
112 if !(1..=3).contains(&components.len())
113 || components.iter().any(|component| {
114 component.is_empty() || !component.bytes().all(|byte| byte.is_ascii_digit())
115 })
116 {
117 return None;
118 }
119 let major = components[0].parse().ok()?;
120 let minor = components.get(1).unwrap_or(&"0").parse().ok()?;
121 let patch = components.get(2).unwrap_or(&"0").parse().ok()?;
122 Some(semver::Version::new(major, minor, patch))
123}
124
125pub(crate) fn classify_endpoint(raw: &str) -> (EndpointTransport, EndpointScope) {
129 let scheme = raw
130 .split_once(':')
131 .map(|(scheme, _)| scheme)
132 .unwrap_or_default()
133 .to_ascii_lowercase();
134 match scheme.as_str() {
135 "unix" => (EndpointTransport::LocalSocket, EndpointScope::Local),
136 "npipe" => (EndpointTransport::NamedPipe, EndpointScope::Local),
137 "tcp" => (EndpointTransport::Tcp, network_endpoint_scope(raw)),
138 "http" => (EndpointTransport::Http, network_endpoint_scope(raw)),
139 "https" => (EndpointTransport::Https, network_endpoint_scope(raw)),
140 "ssh" => (EndpointTransport::Ssh, EndpointScope::Remote),
141 _ => (EndpointTransport::Unknown, EndpointScope::Unknown),
142 }
143}
144
145fn network_endpoint_scope(raw: &str) -> EndpointScope {
146 let local = reqwest::Url::parse(raw)
147 .ok()
148 .and_then(|url| url.host_str().map(str::to_owned))
149 .is_some_and(|host| {
150 let host = host
151 .trim_start_matches('[')
152 .trim_end_matches(']')
153 .trim_end_matches('.');
154 host.eq_ignore_ascii_case("localhost")
155 || host
156 .parse::<std::net::IpAddr>()
157 .is_ok_and(|address| address.is_loopback())
158 });
159 if local {
160 EndpointScope::Local
161 } else {
162 EndpointScope::Remote
163 }
164}
165
166#[cfg(test)]
167mod shared_tests {
168 use super::*;
169
170 #[test]
171 fn vendor_versions_have_one_consistent_strict_conversion() {
172 for (raw, expected) in [
173 ("1", Some(semver::Version::new(1, 0, 0))),
174 ("V1.2", Some(semver::Version::new(1, 2, 0))),
175 ("v1.2.3", Some(semver::Version::new(1, 2, 3))),
176 (
177 "(v1.2.3-rc.1+vendor)",
178 semver::Version::parse("1.2.3-rc.1+vendor").ok(),
179 ),
180 ("1.7.22~ds1-1", Some(semver::Version::new(1, 7, 22))),
181 ("1.2.3.4", None),
182 ("release-1.2.3", None),
183 ("", None),
184 ] {
185 assert_eq!(parse_vendor_version(raw), expected, "candidate: {raw}");
186 }
187 }
188
189 #[test]
190 fn loopback_network_endpoints_are_local_but_ssh_is_remote() {
191 for raw in [
192 "tcp://127.0.0.1:2375",
193 "http://LOCALHOST:2375",
194 "https://[::1]:2376",
195 ] {
196 assert_eq!(classify_endpoint(raw).1, EndpointScope::Local, "{raw}");
197 }
198 assert_eq!(
199 classify_endpoint("tcp://192.0.2.10:2375").1,
200 EndpointScope::Remote
201 );
202 assert_eq!(
203 classify_endpoint("ssh://localhost").1,
204 EndpointScope::Remote
205 );
206 }
207}