1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
//! Stable, collision-resistant host identity.
//!
//! Many agents and telemetry pipelines need a *stable* identifier for the
//! host they run on: one that survives restarts and upgrades but distinguishes
//! two otherwise-identical hosts. The obvious source on modern Linux is
//! `/etc/machine-id`, but relying on it alone is unreliable: cloned VMs share
//! IDs, LXC guests often inherit the host's ID, minimal container images
//! have no file at all, and systemd writes the literal string `uninitialized`
//! during early boot.
//!
//! `host-identity` exposes every known-good identity source as a composable
//! [`Source`] implementation. Consumers can either take the default
//! platform-appropriate chain or mix and match sources in any order to match
//! their own policy.
//!
//! # The common case
//!
//! ```no_run
//! // Local-only default chain: env override → platform sources →
//! // (container ID when applicable). No network calls.
//! let id = host_identity::resolve()?;
//! println!("{id}");
//! # Ok::<(), host_identity::Error>(())
//! ```
//!
//! When you want cloud-metadata endpoints in the chain as well, use
//! [`resolve_with_transport`] with your HTTP client of choice. That
//! chain is strictly richer than the local default: it includes every
//! cloud and Kubernetes source the feature set enabled, ordered so that
//! per-pod identity outranks per-container outranks per-instance
//! outranks per-host software state.
//!
//! # Identifier-based chains (config-driven)
//!
//! For operator-facing config files, build a chain from a list of
//! short string identifiers:
//!
//! ```
//! use host_identity::ids::{resolver_from_ids, source_ids};
//!
//! // Equivalent to:
//! // Resolver::new()
//! // .push(EnvOverride::new("HOST_IDENTITY"))
//! // .push(MachineIdFile::default())
//! // .push(DmiProductUuid::default())
//! let resolver = resolver_from_ids([
//! source_ids::ENV_OVERRIDE,
//! source_ids::MACHINE_ID,
//! source_ids::DMI,
//! ]).unwrap();
//! ```
//!
//! See [`ids`] for the full list of identifiers and the
//! `_with_transport` variant for cloud sources.
//!
//! # Auditing every source
//!
//! [`resolve_all`] and [`resolve_all_with_transport`] walk the same
//! chains without short-circuiting and return one [`ResolveOutcome`]
//! per source. Use them when you want to see what every source would
//! produce — operator diagnostics, debugging, cross-validation. To
//! audit a caller-chosen subset, build the resolver with exactly those
//! sources and call [`Resolver::resolve_all`]:
//!
//! ```no_run
//! use host_identity::Resolver;
//! use host_identity::sources::{MachineIdFile, DmiProductUuid};
//!
//! let outcomes = Resolver::new()
//! .push(MachineIdFile::default())
//! .push(DmiProductUuid::default())
//! .resolve_all();
//! for outcome in outcomes {
//! println!("{:?} → {:?}", outcome.source(), outcome.host_id());
//! }
//! ```
//!
//! # Mixing and matching
//!
//! Every built-in source is a public type that implements [`Source`]. Chain
//! them in any order, add your own, and pick the wrap strategy:
//!
//! ```no_run
//! use host_identity::{Resolver, Wrap};
//! use host_identity::sources::{EnvOverride, FileOverride, MachineIdFile, DmiProductUuid};
//!
//! let id = Resolver::new()
//! .push(EnvOverride::new("MY_APP_HOST_ID"))
//! .push(DmiProductUuid::default()) // SMBIOS first — stable across OS reinstalls
//! .push(MachineIdFile::default())
//! .push(FileOverride::new("/etc/my-app/host-id"))
//! .with_wrap(Wrap::UuidV5Namespaced)
//! .resolve()?;
//! # Ok::<(), host_identity::Error>(())
//! ```
//!
//! # Starting from the defaults
//!
//! [`Resolver::with_defaults`] pre-loads the platform's default chain. Use
//! [`Resolver::prepend`] to add higher-priority sources (e.g. your own
//! override) or [`Resolver::push`] to add fallbacks:
//!
//! ```no_run
//! use host_identity::Resolver;
//! use host_identity::sources::FileOverride;
//!
//! let id = Resolver::with_defaults()
//! .push(FileOverride::new("/etc/host-identity")) // last-resort fallback
//! .resolve()?;
//! # Ok::<(), host_identity::Error>(())
//! ```
//!
//! # Custom sources
//!
//! Implement [`Source`] directly, or wrap a closure with
//! [`sources::FnSource`]:
//!
//! ```no_run
//! use host_identity::sources::FnSource;
//! use host_identity::{Resolver, SourceKind};
//!
//! let custom = FnSource::new(SourceKind::custom("hsm"), || {
//! // Read from an HSM, a custom config file, an in-house identity
//! // service, etc.
//! Ok(Some("h-0abc1234".to_owned()))
//! });
//!
//! let id = Resolver::new().push(custom).resolve()?;
//! # Ok::<(), host_identity::Error>(())
//! ```
//!
//! # Cloud-metadata sources
//!
//! Each major cloud provider has a dedicated source behind an opt-in
//! feature flag. Sources are generic over a caller-supplied
//! [`transport::HttpTransport`]; the crate ships no HTTP client of its
//! own.
//!
//! | Source | Feature |
//! | ----------------------------------- | -------------- |
//! | [`sources::AwsImds`] | `aws` |
//! | [`sources::GcpMetadata`] | `gcp` |
//! | [`sources::AzureImds`] | `azure` |
//! | [`sources::DigitalOceanMetadata`] | `digitalocean` |
//! | [`sources::HetznerMetadata`] | `hetzner` |
//! | [`sources::OciMetadata`] | `oci` |
//! | [`sources::OpenStackMetadata`] | `openstack` |
//!
//! Implement [`transport::HttpTransport`] for your HTTP client of choice
//! (adapters are ~10 lines against `reqwest`, `ureq`, `hyper`, etc.), or
//! pass a closure — a blanket impl accepts any
//! `Fn(http::Request<Vec<u8>>) -> Result<http::Response<Vec<u8>>, E>`.
//! For providers the crate doesn't ship, implement
//! [`sources::CloudEndpoint`] on a zero-sized type and alias
//! [`sources::CloudMetadata`].
//!
//! # Kubernetes sources
//!
//! Feature `k8s` (no new dependencies) exposes
//! [`sources::KubernetesPodUid`] (from `/proc/self/mountinfo`),
//! [`sources::KubernetesServiceAccount`] (from the mounted SA secret),
//! and [`sources::KubernetesDownwardApi`] (any file projected by a
//! `downwardAPI` volume).
pub use Error;
pub use ;
pub use resolver_from_ids_with_transport;
pub use ;
pub use Resolver;
pub use ;
pub use ;
/// Resolve a stable host identity using the default chain for this platform.
///
/// Equivalent to `Resolver::with_defaults().resolve()`. This chain is
/// strictly local — no source makes network calls. Reach for
/// [`resolve_with_transport`] when you want cloud-metadata endpoints in
/// the chain, or [`Resolver`] when you need to reorder sources, add
/// custom ones, or choose a different wrap strategy.
///
/// # Errors
///
/// Returns [`Error::NoSource`] if every source in the default chain
/// returned `Ok(None)`, or propagates the first hard failure from a
/// source (see [`Source::probe`]).
/// Resolve a stable host identity using the default chain plus every
/// cloud-metadata and Kubernetes source enabled at compile time.
///
/// Equivalent to `Resolver::with_network_defaults(transport).resolve()`.
/// Requires a caller-supplied [`transport::HttpTransport`] — the crate
/// ships no HTTP client. The transport must be `Clone + 'static`; wrap
/// your client in `Arc` if needed.
///
/// Available only when at least one cloud feature is enabled (`aws`,
/// `gcp`, `azure`, `digitalocean`, `hetzner`, `oci`). See
/// [`Resolver::with_network_defaults`] for the full chain order.
///
/// # Errors
///
/// As [`resolve`]: [`Error::NoSource`] if every source returned
/// `Ok(None)`, or the first hard failure produced by a source in the
/// chain.
/// Walk the default local chain without short-circuiting and return every
/// source's [`ResolveOutcome`] in order.
///
/// Complement to [`resolve`]: same chain, same wrap strategy — but every
/// source is consulted regardless of whether earlier sources already
/// succeeded or failed. Useful for auditing which sources on this host
/// agree (or disagree) and for operator tooling that wants to present
/// the full picture.
///
/// To audit a caller-chosen subset instead of the defaults, build the
/// resolver directly: `Resolver::new().push(...).push(...).resolve_all()`.
/// Walk the network-enabled default chain without short-circuiting and
/// return every source's [`ResolveOutcome`] in order.
///
/// Complement to [`resolve_with_transport`]: same chain, same wrap
/// strategy — but every source is consulted. Useful for auditing which
/// cloud or Kubernetes metadata endpoints are reachable from this host,
/// and what each would yield.
///
/// Available only when at least one cloud feature is enabled.