Skip to content

Commit

Permalink
feat: use RATTLER_AUTH_FILE and use proper reqwest clients in search (
Browse files Browse the repository at this point in the history
#994)

Co-authored-by: Ruben Arts <ruben.arts@hotmail.com>
  • Loading branch information
wolfv and ruben-arts authored Mar 19, 2024
1 parent 2dab03f commit 2d0e7df
Show file tree
Hide file tree
Showing 16 changed files with 156 additions and 33 deletions.
36 changes: 36 additions & 0 deletions docs/advanced/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,39 @@ On Linux, one can use `GNOME Keyring` (or just Keyring) to access credentials th

If you run on a server with none of the aforementioned keychains available, then pixi falls back to store the credentials in an _insecure_ JSON file.
This JSON file is located at `~/.rattler/credentials.json` and contains the credentials.

## Override the authentication storage

You can use the `RATTLER_AUTH_FILE` environment variable to override the default location of the credentials file.
When this environment variable is set, it provides the only source of authentication data that is used by pixi.

E.g.

```bash
export RATTLER_AUTH_FILE=$HOME/credentials.json
# You can also specify the file in the command line
pixi global install --auth-file $HOME/credentials.json ...
```

The JSON should follow the following format:

```json
{
"*.prefix.dev": {
"BearerToken": "your_token"
},
"otherhost.com": {
"BasicHttp": {
"username": "your_username",
"password": "your_password"
}
},
"conda.anaconda.org": {
"CondaToken": "your_token"
}
}
```

Note: if you use a wildcard in the host, any subdomain will match (e.g. `*.prefix.dev` also matches `repo.prefix.dev`).

Lastly you can set the authentication override file in the [global configuration file](./global_configuration.md).
6 changes: 6 additions & 0 deletions docs/advanced/global_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,10 @@ change_ps1 = true
# security risk and should only be used for testing purposes or internal networks.
# You can override this from the CLI with `--tls-no-verify`.
tls_no_verify = false

# Override from where the authentication information is loaded.
# Usually we try to use the keyring to load authentication data from, and only use a JSON
# file as fallback. This option allows you to force the use of a JSON file.
# Read more in the authentication section.
authentication_override_file = "/path/to/your/override.json"
```
14 changes: 8 additions & 6 deletions src/cli/global/common.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
use std::path::PathBuf;
use std::sync::Arc;

use indexmap::IndexMap;
use miette::IntoDiagnostic;
use rattler_conda_types::{
Channel, ChannelConfig, MatchSpec, PackageName, Platform, PrefixRecord, RepoDataRecord,
};
use rattler_networking::AuthenticationMiddleware;
use rattler_repodata_gateway::sparse::SparseRepoData;
use rattler_solve::{resolvo, SolverImpl, SolverTask};
use reqwest_middleware::ClientWithMiddleware;

use crate::{config::home_path, prefix::Prefix, repodata};
use crate::{
config::{home_path, Config},
prefix::Prefix,
repodata,
utils::reqwest::build_reqwest_clients,
};

/// Global binaries directory, default to `$HOME/.pixi/bin`
pub struct BinDir(pub PathBuf);
Expand Down Expand Up @@ -173,13 +176,12 @@ pub fn load_package_records(
/// The network client and the fetched sparse repodata
pub(super) async fn get_client_and_sparse_repodata(
channels: impl IntoIterator<Item = &'_ Channel>,
config: &Config,
) -> miette::Result<(
ClientWithMiddleware,
IndexMap<(Channel, Platform), SparseRepoData>,
)> {
let authenticated_client = reqwest_middleware::ClientBuilder::new(reqwest::Client::new())
.with_arc(Arc::new(AuthenticationMiddleware::default()))
.build();
let authenticated_client = build_reqwest_clients(Some(config)).1;
let platform_sparse_repodata =
repodata::fetch_sparse_repodata(channels, [Platform::current()], &authenticated_client)
.await?;
Expand Down
10 changes: 7 additions & 3 deletions src/cli/global/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use crate::config::Config;
use crate::config::{Config, ConfigCli};
use crate::install::execute_transaction;
use crate::{config, prefix::Prefix, progress::await_in_progress};
use clap::Parser;
Expand Down Expand Up @@ -43,6 +43,9 @@ pub struct Args {
/// By default, if no channel is provided, `conda-forge` is used.
#[clap(short, long)]
channel: Vec<String>,

#[clap(flatten)]
config: ConfigCli,
}

/// Create the environment activation script
Expand Down Expand Up @@ -231,7 +234,7 @@ pub(super) async fn create_executable_scripts(
/// Install a global command
pub async fn execute(args: Args) -> miette::Result<()> {
// Figure out what channels we are using
let config = Config::load_global();
let config = Config::with_cli_config(&args.config);
let channels = config.compute_channels(&args.channel).into_diagnostic()?;

// Find the MatchSpec we want to install
Expand All @@ -243,7 +246,8 @@ pub async fn execute(args: Args) -> miette::Result<()> {
.into_diagnostic()?;

// Fetch sparse repodata
let (authenticated_client, sparse_repodata) = get_client_and_sparse_repodata(&channels).await?;
let (authenticated_client, sparse_repodata) =
get_client_and_sparse_repodata(&channels, &config).await?;

// Install the package(s)
let mut executables = vec![];
Expand Down
1 change: 1 addition & 0 deletions src/cli/global/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub struct Args {
/// Specifies the package(s) that is to be removed.
#[arg(num_args = 1..)]
package: Vec<String>,

#[command(flatten)]
verbose: Verbosity,
}
Expand Down
3 changes: 2 additions & 1 deletion src/cli/global/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ pub async fn execute(args: Args) -> miette::Result<()> {
channels = channels.into_iter().unique().collect::<Vec<_>>();

// Fetch sparse repodata
let (authenticated_client, sparse_repodata) = get_client_and_sparse_repodata(&channels).await?;
let (authenticated_client, sparse_repodata) =
get_client_and_sparse_repodata(&channels, &config).await?;

let records = load_package_records(package_matchspec, &sparse_repodata)?;
let package_record = records
Expand Down
10 changes: 7 additions & 3 deletions src/cli/global/upgrade_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use itertools::Itertools;
use miette::IntoDiagnostic;
use rattler_conda_types::{Channel, MatchSpec, ParseStrictness};

use crate::config::Config;
use crate::config::{Config, ConfigCli};

use super::{
common::{find_installed_package, get_client_and_sparse_repodata, load_package_records},
Expand All @@ -27,11 +27,14 @@ pub struct Args {
/// the package was installed from will always be used.
#[clap(short, long)]
channel: Vec<String>,

#[clap(flatten)]
config: ConfigCli,
}

pub async fn execute(args: Args) -> miette::Result<()> {
let packages = list_global_packages().await?;
let config = Config::load_global();
let config = Config::with_cli_config(&args.config);
let mut channels = config.compute_channels(&args.channel).into_diagnostic()?;

let mut installed_versions = HashMap::with_capacity(packages.len());
Expand All @@ -58,7 +61,8 @@ pub async fn execute(args: Args) -> miette::Result<()> {
channels = channels.into_iter().unique().collect::<Vec<_>>();

// Fetch sparse repodata
let (authenticated_client, sparse_repodata) = get_client_and_sparse_repodata(&channels).await?;
let (authenticated_client, sparse_repodata) =
get_client_and_sparse_repodata(&channels, &config).await?;

let mut upgraded = false;
for package_name in packages.iter() {
Expand Down
18 changes: 15 additions & 3 deletions src/cli/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use clap::Parser;
use itertools::Itertools;
use miette::IntoDiagnostic;
use rattler_conda_types::{GenericVirtualPackage, Platform};
use rattler_networking::authentication_storage;
use rattler_virtual_packages::VirtualPackage;
use serde::Serialize;
use serde_with::serde_as;
Expand Down Expand Up @@ -352,15 +353,26 @@ pub async fn execute(args: Args) -> miette::Result<()> {
.map(GenericVirtualPackage::from)
.collect::<Vec<_>>();

let config = project
.map(|p| p.config().clone())
.unwrap_or_else(config::Config::load_global);

let auth_file = config
.authentication_override_file()
.map(|x| x.to_owned())
.unwrap_or_else(|| {
authentication_storage::backends::file::FileStorage::default()
.path
.clone()
});

let info = Info {
platform: Platform::current().to_string(),
virtual_packages,
version: env!("CARGO_PKG_VERSION").to_string(),
cache_dir: Some(config::get_cache_dir()?),
cache_size,
auth_dir: rattler_networking::authentication_storage::backends::file::FileStorage::default(
)
.path,
auth_dir: auth_file,
project_info,
environments_info,
};
Expand Down
3 changes: 2 additions & 1 deletion src/cli/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ pub struct Args {
}

pub async fn execute(args: Args) -> miette::Result<()> {
let project = Project::load_or_else_discover(args.manifest_path.as_deref())?;
let project =
Project::load_or_else_discover(args.manifest_path.as_deref())?.with_cli_config(args.config);
let environment_name = args
.environment
.map_or_else(|| EnvironmentName::Default, EnvironmentName::Named);
Expand Down
4 changes: 2 additions & 2 deletions src/cli/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ where
}

pub async fn execute(args: Args) -> miette::Result<()> {
let mut project = Project::load_or_else_discover(args.manifest_path.as_deref())?
.with_cli_config(args.config.clone());
let mut project =
Project::load_or_else_discover(args.manifest_path.as_deref())?.with_cli_config(args.config);
let deps = args.deps;
let spec_type = if args.host {
SpecType::Host
Expand Down
4 changes: 2 additions & 2 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ pub struct Args {
/// When running the sigints are ignored and child can react to them. As it pleases.
pub async fn execute(args: Args) -> miette::Result<()> {
// Load the project
let project = Project::load_or_else_discover(args.manifest_path.as_deref())?
.with_cli_config(args.config.clone());
let project =
Project::load_or_else_discover(args.manifest_path.as_deref())?.with_cli_config(args.config);

// Sanity check of prefix location
verify_prefix_location_unchanged(project.default_environment().dir().as_path())?;
Expand Down
16 changes: 9 additions & 7 deletions src/cli/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ use indexmap::IndexMap;
use itertools::Itertools;
use miette::IntoDiagnostic;
use rattler_conda_types::{Channel, PackageName, Platform, RepoDataRecord};
use rattler_networking::AuthenticationMiddleware;
use rattler_repodata_gateway::sparse::SparseRepoData;
use regex::Regex;

use strsim::jaro;
use tokio::task::spawn_blocking;

use crate::config::Config;
use crate::utils::reqwest::build_reqwest_clients;
use crate::{progress::await_in_progress, repodata::fetch_sparse_repodata, Project};

/// Search a package, output will list the latest version of package
Expand Down Expand Up @@ -137,12 +137,14 @@ pub async fn execute(args: Args) -> miette::Result<()> {

let package_name_filter = args.package;

let authenticated_client = reqwest_middleware::ClientBuilder::new(reqwest::Client::new())
.with_arc(Arc::new(AuthenticationMiddleware::default()))
.build();
let repo_data = Arc::new(
fetch_sparse_repodata(channels.iter(), [args.platform], &authenticated_client).await?,
);
let client = if let Some(project) = project.as_ref() {
project.authenticated_client().clone()
} else {
build_reqwest_clients(None).1
};

let repo_data =
Arc::new(fetch_sparse_repodata(channels.iter(), [args.platform], &client).await?);

// When package name filter contains * (wildcard), it will search and display a list of packages matching this filter
if package_name_filter.contains('*') {
Expand Down
4 changes: 2 additions & 2 deletions src/cli/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,8 @@ async fn start_nu_shell(
}

pub async fn execute(args: Args) -> miette::Result<()> {
let project = Project::load_or_else_discover(args.manifest_path.as_deref())?
.with_cli_config(args.config.clone());
let project =
Project::load_or_else_discover(args.manifest_path.as_deref())?.with_cli_config(args.config);
let environment_name = args
.environment
.map_or_else(|| EnvironmentName::Default, EnvironmentName::Named);
Expand Down
31 changes: 31 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ pub struct ConfigCli {
/// Do not verify the TLS certificate of the server.
#[arg(long, action = ArgAction::SetTrue)]
tls_no_verify: bool,

/// Path to the file containing the authentication token.
#[arg(long, env = "RATTLER_AUTH_FILE")]
auth_file: Option<PathBuf>,
}

#[derive(Parser, Debug, Default, Clone)]
Expand All @@ -91,6 +95,10 @@ pub struct Config {
#[serde(default)]
change_ps1: Option<bool>,

/// Path to the file containing the authentication token.
#[serde(default)]
authentication_override_file: Option<PathBuf>,

/// If set to true, pixi will not verify the TLS certificate of the server.
#[serde(default)]
tls_no_verify: Option<bool>,
Expand All @@ -106,6 +114,7 @@ impl From<ConfigCli> for Config {
fn from(cli: ConfigCli) -> Self {
Self {
tls_no_verify: if cli.tls_no_verify { Some(true) } else { None },
authentication_override_file: cli.auth_file,
..Default::default()
}
}
Expand Down Expand Up @@ -157,6 +166,13 @@ impl Config {
merged_config
}

/// Load the global config and layer the given cli config on top of it.
pub fn with_cli_config(cli: &ConfigCli) -> Config {
let mut config = Config::load_global();
config.merge_config(&cli.clone().into());
config
}

/// Load the config from the given path pixi folder and merge it with the global config.
pub fn load(p: &Path) -> miette::Result<Config> {
let local_config = p.join(consts::CONFIG_FILE);
Expand Down Expand Up @@ -190,6 +206,10 @@ impl Config {
self.tls_no_verify = other.tls_no_verify;
}

if other.authentication_override_file.is_some() {
self.authentication_override_file = other.authentication_override_file.clone();
}

self.loaded_from.extend(other.loaded_from.iter().cloned());
}

Expand All @@ -215,6 +235,11 @@ impl Config {
self.change_ps1.unwrap_or(true)
}

/// Retrieve the value for the auth_file field.
pub fn authentication_override_file(&self) -> Option<&PathBuf> {
self.authentication_override_file.as_ref()
}

pub fn channel_config(&self) -> &ChannelConfig {
&self.channel_config
}
Expand Down Expand Up @@ -255,16 +280,22 @@ mod tests {
fn test_config_from_cli() {
let cli = ConfigCli {
tls_no_verify: true,
auth_file: None,
};
let config = Config::from(cli);
assert_eq!(config.tls_no_verify, Some(true));

let cli = ConfigCli {
tls_no_verify: false,
auth_file: Some(PathBuf::from("path.json")),
};

let config = Config::from(cli);
assert_eq!(config.tls_no_verify, None);
assert_eq!(
config.authentication_override_file,
Some(PathBuf::from("path.json"))
);
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions src/snapshots/pixi__config__tests__config_merge.snap
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Config {
change_ps1: Some(
true,
),
authentication_override_file: None,
tls_no_verify: Some(
false,
),
Expand Down
Loading

0 comments on commit 2d0e7df

Please sign in to comment.