Skip to content

Commit 14b98ac

Browse files
authored
Client publish improvement and yank confirmation (#290)
- 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
1 parent a87e0e4 commit 14b98ac

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

crates/client/src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ pub struct Config {
107107
#[serde(default, skip_serializing_if = "Option::is_none")]
108108
pub namespace_map_path: Option<PathBuf>,
109109

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

crates/client/src/lib.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::storage::PackageInfo;
66
use anyhow::{anyhow, Context, Result};
77
use bytes::Bytes;
88
use futures_util::{Stream, StreamExt, TryStreamExt};
9-
use indexmap::IndexMap;
9+
use indexmap::{IndexMap, IndexSet};
1010
use reqwest::{Body, IntoUrl};
1111
use secrecy::Secret;
1212
use semver::{Version, VersionReq};
@@ -71,6 +71,8 @@ where
7171
ignore_federation_hints: bool,
7272
auto_accept_federation_hints: bool,
7373
disable_interactive: bool,
74+
keyring_backend: Option<String>,
75+
keys: IndexSet<String>,
7476
}
7577

7678
impl<R: RegistryStorage, C: ContentStorage, N: NamespaceMapStorage> Client<R, C, N> {
@@ -86,6 +88,8 @@ impl<R: RegistryStorage, C: ContentStorage, N: NamespaceMapStorage> Client<R, C,
8688
ignore_federation_hints: bool,
8789
auto_accept_federation_hints: bool,
8890
disable_interactive: bool,
91+
keyring_backend: Option<String>,
92+
keys: IndexSet<String>,
8993
) -> ClientResult<Self> {
9094
let api = api::Client::new(url, auth_token)?;
9195
Ok(Self {
@@ -96,6 +100,8 @@ impl<R: RegistryStorage, C: ContentStorage, N: NamespaceMapStorage> Client<R, C,
96100
ignore_federation_hints,
97101
auto_accept_federation_hints,
98102
disable_interactive,
103+
keyring_backend,
104+
keys,
99105
})
100106
}
101107

@@ -311,6 +317,47 @@ impl<R: RegistryStorage, C: ContentStorage, N: NamespaceMapStorage> Client<R, C,
311317
res
312318
}
313319

320+
/// Submits the provided publish information or, if not provided, loads from client
321+
/// storage. Uses the keyring to retrieve a key and sign.
322+
///
323+
/// If there's no publishing information in client storage, an error is returned.
324+
///
325+
/// Returns the identifier of the record that was published.
326+
///
327+
/// Use `wait_for_publish` to wait for the record to transition to the `published` state.
328+
#[cfg(feature = "keyring")]
329+
pub async fn sign_with_keyring_and_publish(
330+
&self,
331+
publish_info: Option<PublishInfo>,
332+
) -> ClientResult<RecordId> {
333+
let publish_info = if let Some(publish_info) = publish_info {
334+
publish_info
335+
} else {
336+
self.registry
337+
.load_publish()
338+
.await?
339+
.ok_or(ClientError::NotPublishing)?
340+
};
341+
342+
let registry_domain = self
343+
.get_warg_registry(publish_info.name.namespace())
344+
.await?;
345+
let signing_key = keyring::Keyring::new(
346+
self.keyring_backend
347+
.as_deref()
348+
.unwrap_or(keyring::Keyring::DEFAULT_BACKEND),
349+
)?
350+
.get_signing_key(
351+
registry_domain.map(|domain| domain.to_string()).as_deref(),
352+
&self.keys,
353+
Some(&self.url().to_string()),
354+
)?;
355+
356+
let res = self.publish_with_info(&signing_key, publish_info).await;
357+
self.registry.store_publish(None).await?;
358+
res
359+
}
360+
314361
/// Submits the provided publish information.
315362
///
316363
/// Any publish information in client storage is ignored.
@@ -1344,6 +1391,12 @@ impl FileSystemClient {
13441391
let disable_interactive =
13451392
cfg!(not(feature = "cli-interactive")) || config.disable_interactive;
13461393

1394+
let (keyring_backend, keys) = if cfg!(feature = "keyring") {
1395+
(config.keyring_backend.clone(), config.keys.clone())
1396+
} else {
1397+
(None, IndexSet::new())
1398+
};
1399+
13471400
#[cfg(feature = "keyring")]
13481401
if auth_token.is_none() && config.keyring_auth {
13491402
auth_token = crate::keyring::Keyring::from_config(config)?.get_auth_token(&url)?
@@ -1358,6 +1411,8 @@ impl FileSystemClient {
13581411
config.ignore_federation_hints,
13591412
config.auto_accept_federation_hints,
13601413
disable_interactive,
1414+
keyring_backend,
1415+
keys,
13611416
)?))
13621417
}
13631418

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

1457+
let (keyring_backend, keys) = if cfg!(feature = "keyring") {
1458+
(config.keyring_backend.clone(), config.keys.clone())
1459+
} else {
1460+
(None, IndexSet::new())
1461+
};
1462+
14021463
#[cfg(feature = "keyring")]
14031464
if auth_token.is_none() && config.keyring_auth {
14041465
auth_token =
@@ -1414,6 +1475,8 @@ impl FileSystemClient {
14141475
config.ignore_federation_hints,
14151476
config.auto_accept_federation_hints,
14161477
disable_interactive,
1478+
keyring_backend,
1479+
keys,
14171480
)
14181481
}
14191482

src/commands/publish.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::CommonOptions;
22
use anyhow::{anyhow, bail, Context, Result};
33
use clap::{Args, Subcommand};
4+
use dialoguer::{theme::ColorfulTheme, Confirm};
45
use futures::TryStreamExt;
56
use itertools::Itertools;
67
use std::{future::Future, path::PathBuf, time::Duration};
@@ -275,6 +276,20 @@ pub struct PublishYankCommand {
275276
impl PublishYankCommand {
276277
/// Executes the command.
277278
pub async fn exec(self) -> Result<()> {
279+
if !Confirm::with_theme(&ColorfulTheme::default())
280+
.with_prompt(format!(
281+
"`Yank` revokes a version, making it unavailable. It is permanent and cannot be reversed.
282+
Yank `{version}` of `{package}`?",
283+
version = &self.version,
284+
package = &self.name,
285+
))
286+
.default(false)
287+
.interact()?
288+
{
289+
println!("Aborted and did not yank.");
290+
return Ok(());
291+
}
292+
278293
let config = self.common.read_config()?;
279294
let client = self.common.create_client(&config)?;
280295
let registry_domain = client.get_warg_registry(self.name.namespace()).await?;

0 commit comments

Comments
 (0)