Skip to content

Commit

Permalink
Client publish improvement and yank confirmation (#290)
Browse files Browse the repository at this point in the history
- added a client method `sign_with_keyring_and_publish` ("keyring"
feature required), making it easier for other crates that use the
`warg-client` enable publishing
- when `warg publish yank`, adds a confirmation and more description
#289 #264
  • Loading branch information
calvinrp authored May 15, 2024
1 parent a87e0e4 commit 14b98ac
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 2 deletions.
2 changes: 1 addition & 1 deletion crates/client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ pub struct Config {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub namespace_map_path: Option<PathBuf>,

/// List of creds availabe in keyring
/// List of creds available in keyring
#[serde(default, skip_serializing_if = "IndexSet::is_empty")]
pub keys: IndexSet<String>,

Expand Down
65 changes: 64 additions & 1 deletion crates/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::storage::PackageInfo;
use anyhow::{anyhow, Context, Result};
use bytes::Bytes;
use futures_util::{Stream, StreamExt, TryStreamExt};
use indexmap::IndexMap;
use indexmap::{IndexMap, IndexSet};
use reqwest::{Body, IntoUrl};
use secrecy::Secret;
use semver::{Version, VersionReq};
Expand Down Expand Up @@ -71,6 +71,8 @@ where
ignore_federation_hints: bool,
auto_accept_federation_hints: bool,
disable_interactive: bool,
keyring_backend: Option<String>,
keys: IndexSet<String>,
}

impl<R: RegistryStorage, C: ContentStorage, N: NamespaceMapStorage> Client<R, C, N> {
Expand All @@ -86,6 +88,8 @@ impl<R: RegistryStorage, C: ContentStorage, N: NamespaceMapStorage> Client<R, C,
ignore_federation_hints: bool,
auto_accept_federation_hints: bool,
disable_interactive: bool,
keyring_backend: Option<String>,
keys: IndexSet<String>,
) -> ClientResult<Self> {
let api = api::Client::new(url, auth_token)?;
Ok(Self {
Expand All @@ -96,6 +100,8 @@ impl<R: RegistryStorage, C: ContentStorage, N: NamespaceMapStorage> Client<R, C,
ignore_federation_hints,
auto_accept_federation_hints,
disable_interactive,
keyring_backend,
keys,
})
}

Expand Down Expand Up @@ -311,6 +317,47 @@ impl<R: RegistryStorage, C: ContentStorage, N: NamespaceMapStorage> Client<R, C,
res
}

/// Submits the provided publish information or, if not provided, loads from client
/// storage. Uses the keyring to retrieve a key and sign.
///
/// If there's no publishing information in client storage, an error is returned.
///
/// Returns the identifier of the record that was published.
///
/// Use `wait_for_publish` to wait for the record to transition to the `published` state.
#[cfg(feature = "keyring")]
pub async fn sign_with_keyring_and_publish(
&self,
publish_info: Option<PublishInfo>,
) -> ClientResult<RecordId> {
let publish_info = if let Some(publish_info) = publish_info {
publish_info
} else {
self.registry
.load_publish()
.await?
.ok_or(ClientError::NotPublishing)?
};

let registry_domain = self
.get_warg_registry(publish_info.name.namespace())
.await?;
let signing_key = keyring::Keyring::new(
self.keyring_backend
.as_deref()
.unwrap_or(keyring::Keyring::DEFAULT_BACKEND),
)?
.get_signing_key(
registry_domain.map(|domain| domain.to_string()).as_deref(),
&self.keys,
Some(&self.url().to_string()),
)?;

let res = self.publish_with_info(&signing_key, publish_info).await;
self.registry.store_publish(None).await?;
res
}

/// Submits the provided publish information.
///
/// Any publish information in client storage is ignored.
Expand Down Expand Up @@ -1344,6 +1391,12 @@ impl FileSystemClient {
let disable_interactive =
cfg!(not(feature = "cli-interactive")) || config.disable_interactive;

let (keyring_backend, keys) = if cfg!(feature = "keyring") {
(config.keyring_backend.clone(), config.keys.clone())
} else {
(None, IndexSet::new())
};

#[cfg(feature = "keyring")]
if auth_token.is_none() && config.keyring_auth {
auth_token = crate::keyring::Keyring::from_config(config)?.get_auth_token(&url)?
Expand All @@ -1358,6 +1411,8 @@ impl FileSystemClient {
config.ignore_federation_hints,
config.auto_accept_federation_hints,
disable_interactive,
keyring_backend,
keys,
)?))
}

Expand Down Expand Up @@ -1399,6 +1454,12 @@ impl FileSystemClient {
let disable_interactive =
cfg!(not(feature = "cli-interactive")) || config.disable_interactive;

let (keyring_backend, keys) = if cfg!(feature = "keyring") {
(config.keyring_backend.clone(), config.keys.clone())
} else {
(None, IndexSet::new())
};

#[cfg(feature = "keyring")]
if auth_token.is_none() && config.keyring_auth {
auth_token =
Expand All @@ -1414,6 +1475,8 @@ impl FileSystemClient {
config.ignore_federation_hints,
config.auto_accept_federation_hints,
disable_interactive,
keyring_backend,
keys,
)
}

Expand Down
15 changes: 15 additions & 0 deletions src/commands/publish.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::CommonOptions;
use anyhow::{anyhow, bail, Context, Result};
use clap::{Args, Subcommand};
use dialoguer::{theme::ColorfulTheme, Confirm};
use futures::TryStreamExt;
use itertools::Itertools;
use std::{future::Future, path::PathBuf, time::Duration};
Expand Down Expand Up @@ -275,6 +276,20 @@ pub struct PublishYankCommand {
impl PublishYankCommand {
/// Executes the command.
pub async fn exec(self) -> Result<()> {
if !Confirm::with_theme(&ColorfulTheme::default())
.with_prompt(format!(
"`Yank` revokes a version, making it unavailable. It is permanent and cannot be reversed.
Yank `{version}` of `{package}`?",
version = &self.version,
package = &self.name,
))
.default(false)
.interact()?
{
println!("Aborted and did not yank.");
return Ok(());
}

let config = self.common.read_config()?;
let client = self.common.create_client(&config)?;
let registry_domain = client.get_warg_registry(self.name.namespace()).await?;
Expand Down

0 comments on commit 14b98ac

Please sign in to comment.