Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,974 changes: 1,849 additions & 125 deletions Cargo.lock

Large diffs are not rendered by default.

21 changes: 20 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ console = { version = "0.15.11" }
env_logger = { version = "0.11.5" }
log = { version = "0.4.22" }
openssl = { version = "0.10.75", optional = true }
rand = { version = "0.8" }
rand = { version = "0.10" }
reqwest = { version = "0.12.28", default-features = false, features = [
"json",
"multipart",
Expand Down Expand Up @@ -63,6 +63,25 @@ self_update = { version = "0.43.1", features = [
] }
md5 = "0.8.0"
ulid = "1.2.1"
keyring-core = {version = "1.0.0", features = ["sample"]}
whoami = "2.1.2"

[target.'cfg(target_os = "windows")'.dependencies]
windows-native-keyring-store = "1.1.0"

[target.'cfg(not(any(target_os = "ios", target_os = "android")))'.dependencies]
db-keystore = { version = "0.4.3" }

[target.'cfg(target_os = "macos")'.dependencies]
apple-native-keyring-store = { version = "1.0.0", features = ["keychain"] }

[target.'cfg(target_os = "linux")'.dependencies]
linux-keyutils-keyring-store = { version = "1.0.0" }

[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
zbus-secret-service-keyring-store = { version = "1.0.0", features = ["crypto-rust"] }



[features]
default = ["reqwest/default-tls"] # link against system library
Expand Down
2 changes: 2 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@
tokio-console
fenixToolChain.rust-analyzer
fenixToolChain.rustfmt
#keyutils
#gnome-keyring
Comment on lines +201 to +202

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this leftover code? I assume this means commented out?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is commented out, to be easily activated if someone wants to test things wrt keystore. I can also remove it, but it is only part of the development shell.

];
};
})
Expand Down
1 change: 1 addition & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub async fn execute_cmd(opts: MainOpts) -> Result<(), CmdError> {

SubCommand::Dataset(input) => input.exec(ctx).await?,
SubCommand::Job(input) => input.exec(ctx).await?,
SubCommand::Logout(input) => input.exec(&ctx).await?,
};
Ok(())
}
Expand Down
10 changes: 10 additions & 0 deletions src/cli/cmd.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod dataset;
pub mod job;
pub mod login;
pub mod logout;
pub mod project;
pub mod update;
#[cfg(feature = "user-doc")]
Expand Down Expand Up @@ -71,6 +72,9 @@ pub enum CmdError {

#[snafu(display("Job - {}", source))]
Job { source: job::Error },

#[snafu(display("Logout - {}", source))]
Logout { source: logout::Error },
}

impl From<job::Error> for CmdError {
Expand Down Expand Up @@ -109,6 +113,12 @@ impl From<login::Error> for CmdError {
}
}

impl From<logout::Error> for CmdError {
fn from(source: logout::Error) -> Self {
CmdError::Logout { source }
}
}

impl From<dataset::Error> for CmdError {
fn from(source: dataset::Error) -> Self {
CmdError::Dataset { source }
Expand Down
29 changes: 29 additions & 0 deletions src/cli/cmd/logout.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use super::Context;
use crate::httpclient::Error as HttpError;
use crate::{cli::sink::Error as SinkError, data::simple_message::SimpleMessage};
use clap::Parser;
use snafu::{ResultExt, Snafu};

/// Performs a logout by removing the stored token from the keystore.
///
#[derive(Parser, Debug, PartialEq)]
pub struct Input {}

#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("An http error occurred: {}", source))]
HttpClient { source: HttpError },

#[snafu(display("Error writing data: {}", source))]
WriteResult { source: SinkError },
}

impl Input {
pub async fn exec(&self, ctx: &Context) -> Result<(), Error> {
ctx.client.clear_token().await.context(HttpClientSnafu)?;
let message = "Logout complete.".to_string();
ctx.write_result(&SimpleMessage { message })
.await
.context(WriteResultSnafu)
}
}
12 changes: 9 additions & 3 deletions src/cli/complete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,15 @@ async fn resolve_project_id(client: &Client, id: ProjectId) -> Option<String> {
/// Complete a job session launcher id
pub fn complete_job_launcher_id(current: &ffi::OsStr) -> Vec<CompletionCandidate> {
make_sync_completer(current, async |client, opts| {
let Ok(launchers) = client.list_launchers().await else {
eprintln!("Completions failed: Error getting list of launchers");
return vec![];
let launchers = match client.list_launchers().await {
Err(msg) => {
eprintln!(
"Completions failed: Error getting list of launchers: {}",
msg
);
return vec![];
}
Ok(res) => res,
};
let mut result: Vec<CompletionCandidate> = vec![];
let project_ctx = opts.get_project_context().ok().flatten();
Expand Down
29 changes: 26 additions & 3 deletions src/cli/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
renku_url::RenkuUrl,
},
httpclient::{Client, Error as ClientError, proxy},
project_config::RenkuProjectConfig,
};

use super::cmd::*;
Expand Down Expand Up @@ -107,15 +108,34 @@ impl CommonOpts {
}
}

/// Return the project context.
///
/// The project context is a project identifier that commands can
/// use to scope their actions to that project. It is read via the
/// following strategy (first wins):
///
/// - use the option if specified
/// - read $CWD/.renku/config.toml
/// - use environment variable RENKU_CLI_PROJECT_CONTEXT
pub fn get_project_context(&self) -> Result<Option<ProjectId>, ProjectIdParseError> {
if self.project_context.is_some() {
Ok(self.project_context.clone())
} else {
fn get_from_env() -> Result<Option<ProjectId>, ProjectIdParseError> {
match std::env::var("RENKU_CLI_PROJECT_CONTEXT").ok() {
Some(id) => ProjectId::parse(&id).map(Some),
None => Ok(None),
}
}
if self.project_context.is_some() {
Ok(self.project_context.clone())
} else {
match RenkuProjectConfig::read_current_dir() {
Ok(None) => get_from_env(),
Ok(Some(cfg)) => Ok(Some(ProjectId::Id(cfg.project.id))),
Err(err) => {
log::warn!("Error getting project config: {}", err);
get_from_env()
}
}
}
}
}

Expand Down Expand Up @@ -144,6 +164,9 @@ pub enum SubCommand {

#[command()]
Job(job::Input),

#[command()]
Logout(logout::Input),
}

/// This is the command line interface to the Renku platform. Main
Expand Down
26 changes: 19 additions & 7 deletions src/httpclient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@
//! TODO

pub mod auth;
mod cache;
pub mod data;
pub mod keystore;
pub mod proxy;

use crate::data::project_id::ProjectId;
use crate::data::renku_url::RenkuUrl;

use self::data::*;
use auth::{Response, UserCode};
use keystore::{KeyringStore, Keystore};
use openidconnect::OAuth2TokenResponse;
use regex::Regex;
use reqwest::{Certificate, ClientBuilder, IntoUrl, RequestBuilder, Url};
Expand All @@ -56,6 +57,9 @@ fn display_bad_response(em: &Option<ErrorResponse>, body: &String) -> String {
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum Error {
#[snafu(display("Keystore error: {}", source))]
Keystore { source: keystore::Error },

#[snafu(display("An error was received from {}: {}", url, source))]
Http { source: reqwest::Error, url: String },

Expand Down Expand Up @@ -91,11 +95,9 @@ pub enum Error {

#[snafu(display("Error parsing url: {}", reason))]
ProjectUrlParse { reason: String },
#[snafu(transparent)]
Auth { source: auth::AuthError },

#[snafu(transparent)]
Cache { source: cache::Error },
Auth { source: auth::AuthError },
}

/// The renku http client.
Expand All @@ -106,6 +108,7 @@ pub struct Client {
client: reqwest::Client,
settings: Settings,
access_token: Option<String>,
keystore: KeyringStore,
}

#[derive(Debug)]
Expand Down Expand Up @@ -151,8 +154,12 @@ impl Client {
}
}

let auth_data = access_token
.or(cache::read_auth_token()?.map(|r| r.response.access_token().secret().clone()));
let keystore = keystore::KeyringStore::new(renku_url.clone()).context(KeystoreSnafu)?;

let auth_data = access_token.or(keystore
.read_token()
.context(KeystoreSnafu)?
.map(|r| r.response.access_token().secret().clone()));
let client = client_builder.build().context(ClientCreateSnafu)?;
Ok(Client {
client,
Expand All @@ -163,6 +170,7 @@ impl Client {
accept_invalid_certs,
base_url: renku_url,
},
keystore,
})
}

Expand Down Expand Up @@ -420,7 +428,7 @@ impl Client {

pub async fn complete_login_flow(&self, code: UserCode) -> Result<Response, Error> {
let r = auth::poll_tokens(code).await?;
cache::write_auth_token(&r).await?;
self.keystore.write_token(&r).context(KeystoreSnafu)?;
Ok(r)
}

Expand All @@ -429,4 +437,8 @@ impl Client {
let result = self.json_get::<Vec<SessionLauncher>>(path).await?;
Ok(result)
}

pub async fn clear_token(&self) -> Result<(), Error> {
self.keystore.clear().context(KeystoreSnafu)
}
}
91 changes: 0 additions & 91 deletions src/httpclient/cache.rs

This file was deleted.

Loading
Loading