Skip to content

Commit

Permalink
Improved package init and publish flow (#269)
Browse files Browse the repository at this point in the history
When publishing either a package `Release` or `Init`, a new error
`ConflictPendingPublish(RecordId)` may be returned.

This instructs the client that it could wait for the conflicting pending
record (on the same `LogId` package) to complete and then retry to
publish.

If a registry service offers key management and needs to sign the `Init`
record, this enables a smoother experience and messaging on the CLI.
Where the registry would return the `RecordId` of the registry signed
`Init` record as a `ConflictPendingPublish` error, the CLI would wait
for that record to publish and then retry. Since the package is already
initialized, an error message would be presented that indicates that the
package was initialized but not with the `Init` record that you signed.

Also, added a feature flag for `cli-interactive` for the `warg-client`
and a config flag to disable dialoguer interactive prompting even when
that feature is enabled.

Also, added paramenter to the `warg download` subcommand `--output` to write the downloaded `.wasm` file to a path in addition to the local cache.
  • Loading branch information
calvinrp authored May 7, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 85f8ea1 commit 3074330
Showing 16 changed files with 376 additions and 1,233 deletions.
1,145 changes: 23 additions & 1,122 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -31,8 +31,6 @@ p256 = { workspace = true }
rand_core = { workspace = true }
url = { workspace = true }
# TODO: remove these demo-related dependencies
wasmtime = "10.0"
wasmtime-wasi = "10.0"
reqwest.workspace = true
warg-api.workspace = true
ptree.workspace = true
@@ -56,8 +54,9 @@ wit-parser = "0.13.1"
testresult = "0.3.0"

[features]
default = []
default = ["cli-interactive"]
postgres = ["warg-server/postgres"]
cli-interactive = ["warg-client/cli-interactive"]

[workspace]
members = ["crates/server"]
@@ -66,7 +65,7 @@ members = ["crates/server"]
version = "0.5.0-dev"
authors = ["The Warg Registry Project Developers"]
edition = "2021"
rust-version = "1.66.0"
rust-version = "1.76.0"
license = "Apache-2.0 WITH LLVM-exception"
homepage = "https://warg.io/"
repository = "https://github.com/bytecodealliance/registry"
3 changes: 2 additions & 1 deletion crates/api/src/v1/content.rs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ use crate::Status;
use indexmap::IndexMap;
use serde::{de::Unexpected, Deserialize, Serialize, Serializer};
use std::borrow::Cow;
use std::str::FromStr;
use thiserror::Error;
use warg_crypto::hash::AnyHash;

@@ -95,7 +96,7 @@ impl<'de> Deserialize<'de> for ContentError {
match RawError::<String>::deserialize(deserializer)? {
RawError::NotFound { status: _, ty, id } => match ty {
EntityType::ContentDigest => Ok(Self::ContentDigestNotFound(
id.parse::<AnyHash>().map_err(|_| {
AnyHash::from_str(&id).map_err(|_| {
serde::de::Error::invalid_value(Unexpected::Str(&id), &"a valid digest")
})?,
)),
3 changes: 2 additions & 1 deletion crates/api/src/v1/fetch.rs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ use crate::Status;
use indexmap::IndexMap;
use serde::{de::Unexpected, Deserialize, Serialize, Serializer};
use std::borrow::Cow;
use std::str::FromStr;
use thiserror::Error;
use warg_crypto::hash::AnyHash;
use warg_protocol::{
@@ -187,7 +188,7 @@ impl<'de> Deserialize<'de> for FetchError {
RawError::CheckpointNotFound { id, .. } => Ok(Self::CheckpointNotFound(id)),
RawError::NotFound { status: _, ty, id } => match ty {
EntityType::Log => Ok(Self::LogNotFound(
id.parse::<AnyHash>()
AnyHash::from_str(&id)
.map_err(|_| {
serde::de::Error::invalid_value(Unexpected::Str(&id), &"a valid log id")
})?
26 changes: 23 additions & 3 deletions crates/api/src/v1/package.rs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ use crate::Status;
use indexmap::IndexMap;
use serde::{de::Unexpected, Deserialize, Serialize, Serializer};
use std::borrow::Cow;
use std::str::FromStr;
use thiserror::Error;
use warg_crypto::hash::AnyHash;
use warg_protocol::{
@@ -133,6 +134,9 @@ pub enum PackageError {
/// The operation was not supported by the registry.
#[error("the requested operation is not supported: {0}")]
NotSupported(String),
/// The package was rejected by the registry, due to a conflict with a pending publish.
#[error("the package conflicts with pending publish of record `{0}`")]
ConflictPendingPublish(RecordId),
/// The package was rejected by the registry.
#[error("the package was rejected by the registry: {0}")]
Rejection(String),
@@ -152,7 +156,7 @@ impl PackageError {
match self {
Self::Unauthorized { .. } => 401,
Self::LogNotFound(_) | Self::RecordNotFound(_) | Self::NamespaceNotDefined(_) => 404,
Self::NamespaceImported(_) => 409,
Self::NamespaceImported(_) | Self::ConflictPendingPublish(_) => 409,
Self::RecordNotSourcing => 405,
Self::Rejection(_) => 422,
Self::NotSupported(_) => 501,
@@ -243,6 +247,12 @@ impl Serialize for PackageError {
id: Cow::Borrowed(namespace),
}
.serialize(serializer),
Self::ConflictPendingPublish(record_id) => RawError::Conflict {
status: Status::<409>,
ty: EntityType::Record,
id: Cow::Borrowed(record_id),
}
.serialize(serializer),
Self::RecordNotSourcing => RawError::RecordNotSourcing::<()> {
status: Status::<405>,
}
@@ -277,14 +287,14 @@ impl<'de> Deserialize<'de> for PackageError {
}
RawError::NotFound { status: _, ty, id } => match ty {
EntityType::Log => Ok(Self::LogNotFound(
id.parse::<AnyHash>()
AnyHash::from_str(&id)
.map_err(|_| {
serde::de::Error::invalid_value(Unexpected::Str(&id), &"a valid log id")
})?
.into(),
)),
EntityType::Record => Ok(Self::RecordNotFound(
id.parse::<AnyHash>()
AnyHash::from_str(&id)
.map_err(|_| {
serde::de::Error::invalid_value(
Unexpected::Str(&id),
@@ -301,6 +311,16 @@ impl<'de> Deserialize<'de> for PackageError {
},
RawError::Conflict { status: _, ty, id } => match ty {
EntityType::NamespaceImport => Ok(Self::NamespaceImported(id.into_owned())),
EntityType::Record => Ok(Self::ConflictPendingPublish(
AnyHash::from_str(&id)
.map_err(|_| {
serde::de::Error::invalid_value(
Unexpected::Str(&id),
&"a valid record id",
)
})?
.into(),
)),
_ => Err(serde::de::Error::invalid_value(
Unexpected::Enum,
&"a valid entity type",
5 changes: 3 additions & 2 deletions crates/client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -10,8 +10,9 @@ homepage = { workspace = true }
repository = { workspace = true}

[features]
default = []
default = ["cli-interactive"]
native-tls-vendored = ["reqwest/native-tls-vendored"]
cli-interactive = ["dep:dialoguer"]

[dependencies]
warg-crypto = { workspace = true }
@@ -24,7 +25,7 @@ clap = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true }
dialoguer = { workspace = true }
dialoguer = { workspace = true, optional = true }
tokio-util = { workspace = true }
tempfile = { workspace = true }
reqwest = { workspace = true }
5 changes: 5 additions & 0 deletions crates/client/src/config.rs
Original file line number Diff line number Diff line change
@@ -122,6 +122,10 @@ pub struct Config {
/// Auto accept registry hint or ask the user to confirm
#[serde(default)]
pub auto_accept_federation_hints: bool,

/// Disable interactive prompts.
#[serde(default)]
pub disable_interactive: bool,
}

impl Config {
@@ -202,6 +206,7 @@ impl Config {
keyring_auth: self.keyring_auth,
ignore_federation_hints: self.ignore_federation_hints,
auto_accept_federation_hints: self.auto_accept_federation_hints,
disable_interactive: self.disable_interactive,
};

serde_json::to_writer_pretty(
18 changes: 14 additions & 4 deletions crates/client/src/depsolve.rs
Original file line number Diff line number Diff line change
@@ -45,11 +45,16 @@ impl LockListBuilder {
}

#[async_recursion]
async fn parse_package<R: RegistryStorage, C: ContentStorage, N: NamespaceMapStorage>(
async fn parse_package<R, C, N>(
&mut self,
client: &Client<R, C, N>,
mut bytes: &[u8],
) -> Result<()> {
) -> Result<()>
where
R: RegistryStorage,
C: ContentStorage,
N: NamespaceMapStorage,
{
let mut parser = Parser::new(0);
let mut imports: Vec<String> = Vec::new();
loop {
@@ -157,11 +162,16 @@ impl LockListBuilder {

/// List of deps for building
#[async_recursion]
pub async fn build_list<R: RegistryStorage, C: ContentStorage, N: NamespaceMapStorage>(
pub async fn build_list<R, C, N>(
&mut self,
client: &Client<R, C, N>,
info: &PackageInfo,
) -> Result<()> {
) -> Result<()>
where
R: RegistryStorage,
C: ContentStorage,
N: NamespaceMapStorage,
{
let release = info.state.releases().last();
if let Some(r) = release {
let state = &r.state;
Loading

0 comments on commit 3074330

Please sign in to comment.