flytrap/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg), deny(rustdoc::broken_intra_doc_links))]
2#![cfg_attr(not(feature = "regions"), allow(unused_imports))]
3
4//! Flytrap is a crate for reading the [Fly.io][] runtime [environment][].
5//!
6//! [Fly.io]: https://fly.io/
7//! [environment]: https://fly.io/docs/reference/runtime-environment/
8//!
9//! - Read Fly.io [environment variables][env-vars] like `$FLY_PUBLIC_IP` into a `struct`
10//! - Query Fly.io [internal DNS][dns] addresses like `top3.nearest.of.<app>.internal`
11//! - Query the Fly.io [machines API][]
12//! - Parse Fly.io [request headers][] like `Fly-Client-IP` (into an [`IpAddr`][std::net::IpAddr])
13//! - Turn Fly.io [region][regions] codes like `ord` into names like “Chicago” and lat/long coordinates
14//!
15//! [env-vars]: https://fly.io/docs/reference/runtime-environment/#environment-variables
16//! [dns]: https://fly.io/docs/reference/private-networking/#fly-internal-addresses
17//! [machines API]: https://fly.io/docs/machines/api/
18//! [request headers]: https://fly.io/docs/reference/runtime-environment/#request-headers
19//! [regions]: https://fly.io/docs/reference/regions/
20//!
21//! A [demo app][] is available at [**flytrap.fly.dev**](https://flytrap.fly.dev) which shows this crate’s capabilities.
22//!
23//! [demo app]: https://github.com/silverlyra/flytrap/blob/main/examples/server.rs
24//!
25//! ## Usage
26//!
27//! ### Placement
28//!
29//! The [`Placement`] type gives access to Fly.io runtime [environment
30//! variables][env-vars] like `$FLY_PUBLIC_IP` and `$FLY_REGION`.
31//!
32//! ```no_run
33//! use flytrap::{Placement, Machine};
34//!
35//! fn main() -> Result<(), Box<dyn std::error::Error>> {
36//! let runtime = Placement::current()?;
37//!
38//! println!("Fly.io app: {}", runtime.app);
39//! println!(" region: {}", runtime.location);
40//!
41//! if let Some(Machine{ id, memory: Some(memory), image: Some(image), .. }) = runtime.machine {
42//! println!(" machine: {id} ({memory} MB) running {image}");
43//! }
44//!
45//! if let Some(public_ip) = runtime.public_ip {
46//! println!(" public IP: {}", public_ip);
47//! }
48//! println!("private IP: {}", runtime.private_ip);
49//!
50//! Ok(())
51//! }
52//! ```
53//!
54//! #### Regions
55//!
56//! Flytrap models Fly.io [regions][] as an `enum`:
57//!
58//! ```no_run
59//! use flytrap::{City, Placement, Region};
60//!
61//! fn main() -> Result<(), Box<dyn std::error::Error>> {
62//! let runtime = Placement::current()?;
63//! let region = runtime.region().unwrap_or(Region::Guadalajara);
64//!
65//! show(region);
66//! Ok(())
67//! }
68//!
69//! fn show(region: Region) {
70//! let City { name, country, geo } = region.city;
71//! println!("Running in {name} ({country}) @ {}, {}", geo.x(), geo.y());
72//! }
73//! ```
74//!
75//! Regions implement [`Ord`], and sort geographically:
76//!
77//! ```rust
78//! # fn main() {
79//! use flytrap::Region::*;
80//!
81//! let mut regions = [Bucharest, Chicago, HongKong, Johannesburg,
82//! LosAngeles, Madrid, Santiago, Tokyo];
83//! regions.sort();
84//!
85//! assert_eq!(regions, [LosAngeles, Chicago, Santiago, Madrid,
86//! Bucharest, Johannesburg, HongKong, Tokyo]);
87//! # }
88//! ```
89//!
90//! ### DNS queries
91//!
92//! Create a [`Resolver`] in order to query the Fly.io [`.internal` DNS zone][dns].
93//!
94//! ```no_run
95//! use flytrap::Resolver;
96//!
97//! #[tokio::main]
98//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
99//! let resolver = Resolver::new()?;
100//!
101//! // Discover all instances of the currently-running app
102//! let peers = resolver.current()?.peers().await?;
103//!
104//! for peer in peers {
105//! println!("peer {} in {} @ {}", peer.id, peer.location, peer.private_ip);
106//! }
107//!
108//! Ok(())
109//! }
110//! ```
111//!
112//! ### Machines API requests
113//!
114//! Create an [`api::Client`] to send requests to the [machines API][]
115//!
116#![cfg_attr(feature = "api", doc = "```no_run")]
117#![cfg_attr(not(feature = "api"), doc = "```ignore")]
118//! use std::env;
119//! use flytrap::api::Client;
120//!
121//! #[tokio::main]
122//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
123//! let token = env::var("FLY_API_TOKEN")?;
124//! let client = Client::new(token);
125//!
126//! // Discover other instances of the currently-running app
127//! let peers = client.peers().await?;
128//!
129//! for peer in peers {
130//! println!("peer {} in {} is {:?}", peer.name, peer.location, peer.state);
131//! }
132//!
133//! Ok(())
134//! }
135//! ```
136//!
137//! ### HTTP headers
138//!
139//! The [`http`][http] module contains typed [`Header`][headers] implementations of
140//! the HTTP [request headers][] added by Fly.io edge proxies, like
141//! [`Fly-Client-IP`][client-ip].
142//!
143//! [http]: https://docs.rs/flytrap/latest/flytrap/http/index.html
144//! [client-ip]: https://docs.rs/flytrap/latest/flytrap/http/struct.FlyClientIp.html
145//!
146//! ## Features
147//!
148//! Flytrap’s compilation can be controlled through a number of [Cargo features][].
149//!
150//! [Cargo features]: https://doc.rust-lang.org/cargo/reference/features.html
151//!
152//! - **`api`**: Enable the [client][`api`] for the Fly.io [machines API][]
153//! - **`dns`**: Enable [`Resolver`] for querying Fly.io [internal DNS][dns], via [`hickory-dns`][hickory] ⭐
154//! - **`detect`**: Enable automatic [`Resolver`] setup for Wireguard VPN clients, via [`if-addrs`][if-addrs] ⭐️
155//! - **`environment`**: Enable code which reads Fly.io environment variables like `$FLY_PUBLIC_IP` ⭐️
156//! - **`http`**: Enable types for HTTP [`headers`][headers] like [`Fly-Client-IP`][http::FlyClientIp] ⭐️
157//! - **`nightly`**: Enable code which is only accepted by nightly Rust toolchains
158//! - **`regions`**: Enable the [`Region`] type and [`RegionDetails`] structures ⭐️
159//! - **`serde`**: Enable [Serde][serde] `#[derive(Deserialize, Serialize)]` on this crate’s types
160//! - **`system-resolver`**: Enable the [`Resolver::system()`][Resolver::system] constructor, which reads `/etc/resolv.conf`
161//!
162//! _(Features marked with a ⭐️ are enabled by default.)_
163//!
164//! [headers]: https://docs.rs/headers/latest/headers/trait.Header.html
165//! [hickory]: https://lib.rs/crates/hickory-resolver
166//! [if-addrs]: https://lib.rs/crates/if-addrs
167//! [serde]: https://serde.rs/
168
169#[cfg(feature = "api")]
170#[cfg_attr(docsrs, doc(cfg(feature = "api")))]
171pub mod api;
172#[cfg(feature = "dns")]
173mod app;
174mod error;
175#[cfg(feature = "http")]
176#[cfg_attr(docsrs, doc(cfg(feature = "http")))]
177pub mod http;
178mod placement;
179#[cfg(feature = "regions")]
180mod region;
181#[cfg(feature = "dns")]
182mod resolver;
183
184#[cfg(feature = "dns")]
185#[cfg_attr(docsrs, doc(cfg(feature = "dns")))]
186pub use app::AppResolver;
187
188pub use error::Error;
189
190#[cfg(feature = "environment")]
191#[cfg_attr(docsrs, doc(cfg(feature = "environment")))]
192pub use placement::hosted;
193#[cfg(any(feature = "detect", feature = "environment"))]
194#[cfg_attr(docsrs, doc(cfg(any(feature = "detect", feature = "environment"))))]
195pub use placement::private_address;
196pub use placement::{Machine, Placement};
197
198#[cfg(feature = "regions")]
199#[cfg_attr(docsrs, doc(cfg(feature = "regions")))]
200pub use region::{City, Location, Region, RegionCode, RegionDetails, RegionError};
201#[cfg(feature = "dns")]
202#[cfg_attr(docsrs, doc(cfg(feature = "dns")))]
203pub use resolver::{dns_server_address, Instance, Node, Peer, Resolver};
204
205#[cfg(not(feature = "regions"))]
206pub type Location = String;
207#[cfg(not(feature = "regions"))]
208pub type Region = String;