Explicit proxy support (#337)

* Add proxy support

* Improve clap definition and check

* Include missing protocol

* Improve error
This commit is contained in:
Ana Hobden 2023-03-14 07:56:57 -07:00 committed by GitHub
parent a977370e74
commit c13b08987b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 75 additions and 18 deletions

13
Cargo.lock generated
View file

@ -1313,6 +1313,7 @@ dependencies = [
"serde_urlencoded", "serde_urlencoded",
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
"tokio-socks",
"tokio-util", "tokio-util",
"tower-service", "tower-service",
"url", "url",
@ -1823,6 +1824,18 @@ dependencies = [
"webpki", "webpki",
] ]
[[package]]
name = "tokio-socks"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0"
dependencies = [
"either",
"futures-util",
"thiserror",
"tokio",
]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.7" version = "0.7.7"

View file

@ -33,7 +33,7 @@ eyre = { version = "0.6.8", default-features = false, features = [ "track-caller
glob = { version = "0.3.0", default-features = false } glob = { version = "0.3.0", default-features = false }
nix = { version = "0.26.0", default-features = false, features = ["user", "fs", "process", "term"] } nix = { version = "0.26.0", default-features = false, features = ["user", "fs", "process", "term"] }
owo-colors = { version = "3.5.0", default-features = false, features = [ "supports-colors" ] } owo-colors = { version = "3.5.0", default-features = false, features = [ "supports-colors" ] }
reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls-native-roots", "stream"] } reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls-native-roots", "stream", "socks"] }
serde = { version = "1.0.144", default-features = false, features = [ "std", "derive" ] } serde = { version = "1.0.144", default-features = false, features = [ "std", "derive" ] }
serde_json = { version = "1.0.85", default-features = false, features = [ "std" ] } serde_json = { version = "1.0.85", default-features = false, features = [ "std" ] }
serde_with = { version = "2.0.1", default-features = false, features = [ "std", "macros" ] } serde_with = { version = "2.0.1", default-features = false, features = [ "std", "macros" ] }

View file

@ -13,11 +13,16 @@ Fetch a URL to the given path
pub struct FetchAndUnpackNix { pub struct FetchAndUnpackNix {
url: Url, url: Url,
dest: PathBuf, dest: PathBuf,
proxy: Option<Url>,
} }
impl FetchAndUnpackNix { impl FetchAndUnpackNix {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub async fn plan(url: Url, dest: PathBuf) -> Result<StatefulAction<Self>, ActionError> { pub async fn plan(
url: Url,
dest: PathBuf,
proxy: Option<Url>,
) -> Result<StatefulAction<Self>, ActionError> {
// TODO(@hoverbear): Check URL exists? // TODO(@hoverbear): Check URL exists?
// TODO(@hoverbear): Check tempdir exists // TODO(@hoverbear): Check tempdir exists
@ -30,7 +35,18 @@ impl FetchAndUnpackNix {
}, },
}; };
Ok(Self { url, dest }.into()) if let Some(proxy) = &proxy {
match proxy.scheme() {
"https" | "http" | "socks5" => (),
_ => {
return Err(ActionError::Custom(Box::new(
FetchUrlError::UnknownProxyScheme,
)))
},
};
}
Ok(Self { url, dest, proxy }.into())
} }
} }
@ -45,12 +61,17 @@ impl Action for FetchAndUnpackNix {
} }
fn tracing_span(&self) -> Span { fn tracing_span(&self) -> Span {
span!( let span = span!(
tracing::Level::DEBUG, tracing::Level::DEBUG,
"fetch_and_unpack_nix", "fetch_and_unpack_nix",
url = tracing::field::display(&self.url), url = tracing::field::display(&self.url),
proxy = tracing::field::Empty,
dest = tracing::field::display(self.dest.display()), dest = tracing::field::display(self.dest.display()),
) );
if let Some(proxy) = &self.proxy {
span.record("proxy", tracing::field::display(&proxy));
}
span
} }
fn execute_description(&self) -> Vec<ActionDescription> { fn execute_description(&self) -> Vec<ActionDescription> {
@ -59,11 +80,24 @@ impl Action for FetchAndUnpackNix {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { url, dest } = self; let bytes = match self.url.scheme() {
let bytes = match url.scheme() {
"https" | "http" => { "https" | "http" => {
let res = reqwest::get(url.clone()) let mut buildable_client = reqwest::Client::builder();
if let Some(proxy) = &self.proxy {
buildable_client =
buildable_client.proxy(reqwest::Proxy::all(proxy.clone()).map_err(|e| {
ActionError::Custom(Box::new(FetchUrlError::Reqwest(e)))
})?)
}
let client = buildable_client
.build()
.map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?;
let req = client
.get(self.url.clone())
.build()
.map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?;
let res = client
.execute(req)
.await .await
.map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?; .map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?;
res.bytes() res.bytes()
@ -71,9 +105,9 @@ impl Action for FetchAndUnpackNix {
.map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))? .map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?
}, },
"file" => { "file" => {
let buf = tokio::fs::read(url.path()) let buf = tokio::fs::read(self.url.path())
.await .await
.map_err(|e| ActionError::Read(PathBuf::from(url.path()), e))?; .map_err(|e| ActionError::Read(PathBuf::from(self.url.path()), e))?;
Bytes::from(buf) Bytes::from(buf)
}, },
_ => { _ => {
@ -85,7 +119,7 @@ impl Action for FetchAndUnpackNix {
// TODO(@Hoverbear): Pick directory // TODO(@Hoverbear): Pick directory
tracing::trace!("Unpacking tar.xz"); tracing::trace!("Unpacking tar.xz");
let dest_clone = dest.clone(); let dest_clone = self.dest.clone();
let decoder = xz2::read::XzDecoder::new(bytes.reader()); let decoder = xz2::read::XzDecoder::new(bytes.reader());
let mut archive = tar::Archive::new(decoder); let mut archive = tar::Archive::new(decoder);
@ -102,8 +136,6 @@ impl Action for FetchAndUnpackNix {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { url: _, dest: _ } = self;
Ok(()) Ok(())
} }
} }
@ -119,6 +151,8 @@ pub enum FetchUrlError {
), ),
#[error("Unarchiving error")] #[error("Unarchiving error")]
Unarchive(#[source] std::io::Error), Unarchive(#[source] std::io::Error),
#[error("Unknown url scheme")] #[error("Unknown url scheme, `file://`, `https://` and `http://` supported")]
UnknownUrlScheme, UnknownUrlScheme,
#[error("Unknown url scheme, `https://`, `socks5://`, and `http://` supported")]
UnknownProxyScheme,
} }

View file

@ -24,8 +24,11 @@ pub struct ProvisionNix {
impl ProvisionNix { impl ProvisionNix {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub async fn plan(settings: &CommonSettings) -> Result<StatefulAction<Self>, ActionError> { pub async fn plan(settings: &CommonSettings) -> Result<StatefulAction<Self>, ActionError> {
let fetch_nix = let fetch_nix = FetchAndUnpackNix::plan(
FetchAndUnpackNix::plan(settings.nix_package_url.clone(), PathBuf::from(SCRATCH_DIR)) settings.nix_package_url.clone(),
PathBuf::from(SCRATCH_DIR),
settings.proxy.clone(),
)
.await?; .await?;
let create_users_and_group = CreateUsersAndGroups::plan(settings.clone()) let create_users_and_group = CreateUsersAndGroups::plan(settings.clone())
.await .await

View file

@ -172,6 +172,10 @@ pub struct CommonSettings {
)] )]
pub(crate) nix_package_url: Url, pub(crate) nix_package_url: Url,
/// The proxy to use (if any), valid proxy bases are `https://$URL`, `http://$URL` and `socks5://$URL`
#[cfg_attr(feature = "cli", clap(long, env = "NIX_INSTALLER_PROXY"))]
pub(crate) proxy: Option<Url>,
/// Extra configuration lines for `/etc/nix.conf` /// Extra configuration lines for `/etc/nix.conf`
#[cfg_attr(feature = "cli", clap(long, action = ArgAction::Set, num_args = 0.., value_delimiter = ',', env = "NIX_INSTALLER_EXTRA_CONF", global = true))] #[cfg_attr(feature = "cli", clap(long, action = ArgAction::Set, num_args = 0.., value_delimiter = ',', env = "NIX_INSTALLER_EXTRA_CONF", global = true))]
pub extra_conf: Vec<String>, pub extra_conf: Vec<String>,
@ -272,6 +276,7 @@ impl CommonSettings {
nix_build_user_prefix: nix_build_user_prefix.to_string(), nix_build_user_prefix: nix_build_user_prefix.to_string(),
nix_build_user_id_base, nix_build_user_id_base,
nix_package_url: url.parse()?, nix_package_url: url.parse()?,
proxy: Default::default(),
extra_conf: Default::default(), extra_conf: Default::default(),
force: false, force: false,
#[cfg(feature = "diagnostics")] #[cfg(feature = "diagnostics")]
@ -291,6 +296,7 @@ impl CommonSettings {
nix_build_user_prefix, nix_build_user_prefix,
nix_build_user_id_base, nix_build_user_id_base,
nix_package_url, nix_package_url,
proxy,
extra_conf, extra_conf,
force, force,
#[cfg(feature = "diagnostics")] #[cfg(feature = "diagnostics")]
@ -326,6 +332,7 @@ impl CommonSettings {
"nix_package_url".into(), "nix_package_url".into(),
serde_json::to_value(nix_package_url)?, serde_json::to_value(nix_package_url)?,
); );
map.insert("proxy".into(), serde_json::to_value(proxy)?);
map.insert("extra_conf".into(), serde_json::to_value(extra_conf)?); map.insert("extra_conf".into(), serde_json::to_value(extra_conf)?);
map.insert("force".into(), serde_json::to_value(force)?); map.insert("force".into(), serde_json::to_value(force)?);