Skip to content

Commit

Permalink
Merge pull request #4315 from wasmerio/4309-add-tty-aware-output-to-t…
Browse files Browse the repository at this point in the history
…he-wasmer-package-and-wasmer-container-commands

Add TTY aware output to the wasmer package and wasmer container commands
  • Loading branch information
dynamite-bud authored Nov 16, 2023
2 parents dd031ad + b698015 commit 3fe59ef
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 44 deletions.
35 changes: 31 additions & 4 deletions lib/cli/src/commands/container/unpack.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::path::PathBuf;

use anyhow::Context;
use dialoguer::console::{style, Emoji};
use indicatif::ProgressBar;
use std::path::PathBuf;

/// Extract contents of a container to a directory.
#[derive(clap::Parser, Debug)]
Expand All @@ -13,13 +14,31 @@ pub struct PackageUnpack {
#[clap(long)]
overwrite: bool,

/// Run the unpack command without any output
#[clap(long)]
pub quiet: bool,

/// Path to the package.
package_path: PathBuf,
}

static PACKAGE_EMOJI: Emoji<'_, '_> = Emoji("📦 ", "");
static EXTRACTED_TO_EMOJI: Emoji<'_, '_> = Emoji("📂 ", "");

impl PackageUnpack {
pub(crate) fn execute(&self) -> Result<(), anyhow::Error> {
eprintln!("Unpacking...");
// Setup the progress bar
let pb = if self.quiet {
ProgressBar::hidden()
} else {
ProgressBar::new_spinner()
};

pb.println(format!(
"{} {}Unpacking...",
style("[1/2]").bold().dim(),
PACKAGE_EMOJI
));

let pkg = webc::compat::Container::from_disk(&self.package_path).with_context(|| {
format!(
Expand All @@ -35,7 +54,14 @@ impl PackageUnpack {
pkg.unpack(outdir, self.overwrite)
.with_context(|| "could not extract package".to_string())?;

eprintln!("Extracted package contents to '{}'", self.out_dir.display());
pb.println(format!(
"{} {}Extracted package contents to '{}'",
style("[2/2]").bold().dim(),
EXTRACTED_TO_EMOJI,
self.out_dir.display()
));

pb.finish();

Ok(())
}
Expand All @@ -61,6 +87,7 @@ mod tests {
out_dir: dir.path().to_owned(),
overwrite: false,
package_path,
quiet: true,
};

cmd.execute().unwrap();
Expand Down
48 changes: 45 additions & 3 deletions lib/cli/src/commands/package/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::path::PathBuf;

use anyhow::Context;
use dialoguer::console::{style, Emoji};
use indicatif::ProgressBar;
use std::path::PathBuf;

/// Build a container from a package manifest.
#[derive(clap::Parser, Debug)]
Expand All @@ -10,17 +11,39 @@ pub struct PackageBuild {
#[clap(short = 'o', long)]
out: Option<PathBuf>,

/// Run the publish command without any output
#[clap(long)]
pub quiet: bool,

/// Path of the package or wasmer.toml manifest.
///
/// Defaults to current directory.
package: Option<PathBuf>,
}

static READING_MANIFEST_EMOJI: Emoji<'_, '_> = Emoji("📖 ", "");
static CREATING_OUTPUT_DIRECTORY_EMOJI: Emoji<'_, '_> = Emoji("📁 ", "");
static WRITING_PACKAGE_EMOJI: Emoji<'_, '_> = Emoji("📦 ", "");
static SPARKLE: Emoji<'_, '_> = Emoji("✨ ", ":-)");

impl PackageBuild {
pub(crate) fn execute(&self) -> Result<(), anyhow::Error> {
let manifest_path = self.manifest_path()?;
let pkg = webc::wasmer_package::Package::from_manifest(manifest_path)?;

// Setup the progress bar
let pb = if self.quiet {
ProgressBar::hidden()
} else {
ProgressBar::new_spinner()
};

pb.println(format!(
"{} {}Reading manifest...",
style("[1/3]").bold().dim(),
READING_MANIFEST_EMOJI
));

let manifest = pkg
.manifest()
.wapm()
Expand All @@ -29,6 +52,13 @@ impl PackageBuild {

let pkgname = manifest.name.replace('/', "-");
let name = format!("{}-{}.webc", pkgname, manifest.version,);

pb.println(format!(
"{} {}Creating output directory...",
style("[2/3]").bold().dim(),
CREATING_OUTPUT_DIRECTORY_EMOJI
));

let out_path = if let Some(p) = &self.out {
if p.is_dir() {
p.join(name)
Expand All @@ -53,10 +83,21 @@ impl PackageBuild {
}

let data = pkg.serialize()?;

pb.println(format!(
"{} {}Writing package...",
style("[3/3]").bold().dim(),
WRITING_PACKAGE_EMOJI
));

std::fs::write(&out_path, &data)
.with_context(|| format!("could not write contents to '{}'", out_path.display()))?;

eprintln!("Package written to '{}'", out_path.display());
pb.finish_with_message(format!(
"{} Package written to '{}'",
SPARKLE,
out_path.display()
));

Ok(())
}
Expand Down Expand Up @@ -126,6 +167,7 @@ description = "hello"
let cmd = PackageBuild {
package: Some(path.to_owned()),
out: Some(path.to_owned()),
quiet: true,
};

cmd.execute().unwrap();
Expand Down
140 changes: 103 additions & 37 deletions lib/cli/src/commands/package/download.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::{io::Write, path::PathBuf};

use anyhow::Context;
use futures::StreamExt;
use dialoguer::console::{style, Emoji};
use indicatif::{ProgressBar, ProgressStyle};
use std::path::PathBuf;
use tempfile::NamedTempFile;
use wasmer_registry::wasmer_env::WasmerEnv;
use wasmer_wasix::runtime::resolver::PackageSpecifier;

Expand All @@ -20,20 +21,49 @@ pub struct PackageDownload {
#[clap(short = 'o', long)]
out_path: PathBuf,

/// Run the download command without any output
#[clap(long)]
pub quiet: bool,

/// The package to download.
/// Can be:
/// * a pakage specifier: `namespace/package[@vesion]`
/// * a URL
package: PackageSpecifier,
}

static CREATING_OUTPUT_DIRECTORY_EMOJI: Emoji<'_, '_> = Emoji("📁 ", "");
static DOWNLOADING_PACKAGE_EMOJI: Emoji<'_, '_> = Emoji("🌐 ", "");
static RETRIEVING_PACKAGE_INFORMATION_EMOJI: Emoji<'_, '_> = Emoji("📜 ", "");
static VALIDATING_PACKAGE_EMOJI: Emoji<'_, '_> = Emoji("🔍 ", "");
static WRITING_PACKAGE_EMOJI: Emoji<'_, '_> = Emoji("📦 ", "");

impl PackageDownload {
pub(crate) fn execute(&self) -> Result<(), anyhow::Error> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(self.run())
}
let total_steps = if self.validate { 5 } else { 4 };
let mut step_num = 1;

// Setup the progress bar
let pb = if self.quiet {
ProgressBar::hidden()
} else {
ProgressBar::new_spinner()
};

pb.set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})")
.unwrap()
.progress_chars("#>-"));

pb.println(format!(
"{} {}Creating output directory...",
style(format!("[{}/{}]", step_num, total_steps))
.bold()
.dim(),
CREATING_OUTPUT_DIRECTORY_EMOJI,
));

step_num += 1;

async fn run(&self) -> Result<(), anyhow::Error> {
if let Some(parent) = self.out_path.parent() {
match parent.metadata() {
Ok(m) => {
Expand All @@ -52,6 +82,16 @@ impl PackageDownload {
}
};

pb.println(format!(
"{} {}Retrieving package information...",
style(format!("[{}/{}]", step_num, total_steps))
.bold()
.dim(),
RETRIEVING_PACKAGE_INFORMATION_EMOJI
));

step_num += 1;

let (full_name, version, api_endpoint, token) = match &self.package {
PackageSpecifier::Registry { full_name, version } => {
let endpoint = self.env.registry_endpoint()?;
Expand Down Expand Up @@ -90,30 +130,45 @@ impl PackageDownload {
.pirita_url
.context("registry does provide a container download container download URL")?;

let client = reqwest::Client::new();
let client = reqwest::blocking::Client::new();
let mut b = client
.get(&download_url)
.get(download_url)
.header(http::header::ACCEPT, "application/webc");
if let Some(token) = token {
b = b.header(http::header::AUTHORIZATION, format!("Bearer {token}"));
};

eprintln!("Downloading package...");
pb.println(format!(
"{} {}Downloading package...",
style(format!("[{}/{}]", step_num, total_steps))
.bold()
.dim(),
DOWNLOADING_PACKAGE_EMOJI
));

step_num += 1;

let res = b
.send()
.await
.context("http request failed")?
.error_for_status()
.context("http request failed with non-success status code")?;

let tmp_path = self.out_path.with_extension("webc_tmp");
let mut tmpfile = std::fs::File::create(&tmp_path).with_context(|| {
format!(
"could not create temporary file at '{}'",
tmp_path.display()
)
})?;
let webc_total_size = res
.headers()
.get(http::header::CONTENT_LENGTH)
.and_then(|t| t.to_str().ok())
.and_then(|t| t.parse::<u64>().ok())
.unwrap_or_default();

if webc_total_size == 0 {
anyhow::bail!("Package is empty");
}

// Set the length of the progress bar
pb.set_length(webc_total_size);

let mut tmpfile = NamedTempFile::new_in(self.out_path.parent().unwrap())?;

let ty = res
.headers()
Expand All @@ -127,36 +182,46 @@ impl PackageDownload {
);
}

let mut body = res.bytes_stream();

while let Some(res) = body.next().await {
let chunk = res.context("could not read response body")?;
// Yes, we are mixing async and sync code here, but since this is
// a top-level command, this can't interfere with other tasks.
tmpfile
.write_all(&chunk)
.context("could not write to temporary file")?;
}
std::io::copy(&mut pb.wrap_read(res), &mut tmpfile)
.context("could not write downloaded data to temporary file")?;

tmpfile.sync_all()?;
std::mem::drop(tmpfile);
tmpfile.as_file_mut().sync_all()?;

if self.validate {
eprintln!("Validating package...");
webc::compat::Container::from_disk(&tmp_path)
if !self.quiet {
println!(
"{} {}Validating package...",
style(format!("[{}/{}]", step_num, total_steps))
.bold()
.dim(),
VALIDATING_PACKAGE_EMOJI
);
}

step_num += 1;

webc::compat::Container::from_disk(tmpfile.path())
.context("could not parse downloaded file as a package - invalid download?")?;
eprintln!("Downloaded package is valid!");
}

std::fs::rename(&tmp_path, &self.out_path).with_context(|| {
tmpfile.persist(&self.out_path).with_context(|| {
format!(
"could not move temporary file from '{}' to '{}'",
tmp_path.display(),
"could not persist temporary file to '{}'",
self.out_path.display()
)
})?;

eprintln!("Package downloaded to '{}'", self.out_path.display());
pb.println(format!(
"{} {}Package downloaded to '{}'",
style(format!("[{}/{}]", step_num, total_steps))
.bold()
.dim(),
WRITING_PACKAGE_EMOJI,
self.out_path.display()
));

// We're done, so finish the progress bar
pb.finish();

Ok(())
}
Expand All @@ -180,6 +245,7 @@ mod tests {
validate: true,
out_path: out_path.clone(),
package: "wasmer/[email protected]".parse().unwrap(),
quiet: true,
};

cmd.execute().unwrap();
Expand Down

0 comments on commit 3fe59ef

Please sign in to comment.