Skip to content

Commit

Permalink
First pass refresh of hab CLI UI with musl/libc fix.
Browse files Browse the repository at this point in the history
hab CLI
-------

This change is part spike and experiment and part functional. It is a
first attempt at unifying the `hab` CLI output and formatting to be more
consisten and easier to scan as a newcomer (in the short term) and
experienced user (in the long term).

Most of the commands have the following narrative, much like an essay:

* An intro/goal/summary of the objective
* A middle with the concrete steps to achieve the objective
* A final summary/conclusion to confirm success

Here is an example of a package install invocation:

```
> hab install core/redis
» Installing core/redis
↓ Downloading core/glibc/2.22/20160427193532
    16.21 MB / 16.21 MB \ [========================================] 100.00 % 2.98 MB/s
✓ Installed core/glibc/2.22/20160427193532
↓ Downloading core/linux-headers/4.3/20160427193435
    798.63 KB / 798.63 KB | [======================================] 100.00 % 1.56 MB/s
✓ Installed core/linux-headers/4.3/20160427193435
↓ Downloading core/redis/3.0.7/20160427222845
    1.46 MB / 1.46 MB | [==========================================] 100.00 % 3.28 MB/s
✓ Installed core/redis/3.0.7/20160427222845
★ Install of core/redis complete with 3 packages installed.
```

musl/libc/ioctl
---------------

In the course of building in a progress bar, an issue was found with in the
`libc` crate relating to the types of ioctl contants when used by musl (more
detail in: rust-lang/libc#289). Unfortunately, this
meant vendoring libc so that it would be used as a transitive dependency in the
crates that generated the errors (currently this is the `pbr` crate, which is
completely correct in tits implementation). Without this temporary measure, the
`hab` CLI won't build as-is with the musl target. The other option is to
feature-disable the progress bar until the upstream is resolved, but it really
hampers the far superior user experience that a progress bar like this
provides.

Signed-off-by: Fletcher Nichol <fnichol@nichol.ca>
  • Loading branch information
fnichol committed May 16, 2016
1 parent f8b2546 commit 166f276
Show file tree
Hide file tree
Showing 95 changed files with 15,278 additions and 148 deletions.
2 changes: 1 addition & 1 deletion components/.cargo/config
Original file line number Diff line number Diff line change
@@ -1 +1 @@
paths = ["sodiumoxide"]
paths = ["sodiumoxide", "../vendor/libc"]
39 changes: 26 additions & 13 deletions components/common/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions components/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ version = "0.5.0"
authors = ["Adam Jacob <adam@chef.io>", "Jamie Winsor <reset@chef.io>", "Fletcher Nichol <fnichol@chef.io>", "Joshua Timberman <joshua@chef.io>", "Dave Parfitt <dparfitt@chef.io>"]

[dependencies]
ansi_term = "*"
log = "*"
openssl = "*"
pbr = "*"
regex = "*"
rustc-serialize = "*"
time = "*"
Expand Down
40 changes: 40 additions & 0 deletions components/common/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,43 @@
// open source license such as the Apache 2.0 License.

pub mod package;

use std::io::{self, Write};

use pbr;
use depot_client::DisplayProgress;

/// A moving progress bar to track progress of a sized event, similar to wget, curl, npm, etc.
///
/// This is designed to satisfy a generic behavior which sets the size of the task (usually a
/// number of bytes representing the total download/upload/transfer size) and will be a generic
/// writer (i.e. implementing the `Write` trait) as a means to increase progress towards
/// completion.
pub struct ProgressBar {
bar: pbr::ProgressBar,
}

impl Default for ProgressBar {
fn default() -> Self {
ProgressBar { bar: pbr::ProgressBar::new(0) }
}
}

impl DisplayProgress for ProgressBar {
fn size(&mut self, size: u64) {
self.bar = pbr::ProgressBar::new(size);
self.bar.set_units(pbr::Units::Bytes);
self.bar.show_tick = true;
self.bar.message(" ");
}
}

impl Write for ProgressBar {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.bar.write(buf)
}

fn flush(&mut self) -> io::Result<()> {
self.bar.flush()
}
}
68 changes: 43 additions & 25 deletions components/common/src/command/package/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,18 @@
//! * Unpack it
//!
use std::fs;
use std::path::{Path, PathBuf};
use std::str::FromStr;

use ansi_term::Colour::{Blue, Green, Yellow};
use hcore::crypto::{artifact, SigKeyPair};
use hcore::crypto::keys::parse_name_with_rev;
use hcore::fs::CACHE_ARTIFACT_PATH;
use hcore::package::{PackageArchive, PackageIdent, PackageInstall};
use depot_core::data_object;
use depot_client;

use command::ProgressBar;
use error::Result;

pub fn start<P: ?Sized>(url: &str, ident_or_archive: &str, cache_key_path: &P) -> Result<()>
Expand Down Expand Up @@ -67,31 +68,40 @@ pub fn from_url<I, P: ?Sized>(url: &str,
where I: AsRef<PackageIdent>,
P: AsRef<Path>
{
println!("Installing {}", ident.as_ref());
println!("{}",
Yellow.bold().paint(format!("» Installing {}", ident.as_ref())));
let pkg_data = try!(depot_client::show_package(url, ident.as_ref()));
try!(fs::create_dir_all(CACHE_ARTIFACT_PATH));
for dep in &pkg_data.tdeps {
try!(install_from_depot(url, &dep, dep.as_ref(), cache_key_path.as_ref()));
}
try!(install_from_depot(url,
&pkg_data.ident,
ident.as_ref(),
cache_key_path.as_ref()));
println!("{}",
Blue.paint(format!("★ Install of {} complete with {} packages installed.",
ident.as_ref(),
1 + &pkg_data.tdeps.len())));
Ok(pkg_data)
}

pub fn from_archive<P1: ?Sized, P2: ?Sized>(url: &str, path: &P1, cache_key_path: &P2) -> Result<()>
where P1: AsRef<Path>,
P2: AsRef<Path>
{
println!("Installing from {}", path.as_ref().display());
println!("{}",
Yellow.bold().paint(format!("» Installing {}", path.as_ref().display())));
let mut archive = PackageArchive::new(PathBuf::from(path.as_ref()));
let ident = try!(archive.ident());
try!(fs::create_dir_all(CACHE_ARTIFACT_PATH));
for dep in try!(archive.tdeps()) {
let tdeps = try!(archive.tdeps());
for dep in &tdeps {
try!(install_from_depot(url, &dep, dep.as_ref(), cache_key_path.as_ref()));
}
try!(install_from_archive(url, archive, &ident, cache_key_path.as_ref()));
println!("{}",
Blue.paint(format!("★ Install of {} complete with {} packages installed.",
&ident,
1 + &tdeps.len())));
Ok(())
}

Expand All @@ -103,21 +113,27 @@ fn install_from_depot<P: AsRef<PackageIdent>>(url: &str,
match PackageInstall::load(ident.as_ref(), None) {
Ok(_) => {
if given_ident.fully_qualified() {
println!("Package {} already installed", ident.as_ref());
println!("{} {}", Green.paint("→ Using"), ident.as_ref());
} else {
println!("Package that satisfies {} ({}) already installed",
println!("{} {} which satisfies {}",
Green.paint("→ Using"),
given_ident,
ident.as_ref());
}
}
Err(_) => {
println!("{} {}",
Green.bold().paint("↓ Downloading"),
ident.as_ref());
let mut progress = ProgressBar::default();
let mut archive = try!(depot_client::fetch_package(url,
ident.as_ref(),
CACHE_ARTIFACT_PATH));
CACHE_ARTIFACT_PATH,
Some(&mut progress)));
let ident = try!(archive.ident());
try!(verify(url, &archive, &ident, cache_key_path));
try!(archive.unpack());
println!("Installed {}", ident);
println!("{} {}", Green.bold().paint("✓ Installed"), ident.as_ref());
}
}
Ok(())
Expand All @@ -130,12 +146,15 @@ fn install_from_archive(url: &str,
-> Result<()> {
match PackageInstall::load(ident.as_ref(), None) {
Ok(_) => {
println!("Package {} already installed", ident);
println!("{} {}", Green.paint("→ Using"), ident);
}
Err(_) => {
println!("{} {} from cache",
Green.bold().paint("← Extracting"),
ident);
try!(verify(url, &archive, &ident, cache_key_path));
try!(archive.unpack());
println!("Installed {}", ident);
println!("{} {}", Green.bold().paint("✓ Installed"), ident);
}
}
Ok(())
Expand All @@ -148,21 +167,20 @@ fn verify(url: &str,
ident: &PackageIdent,
cache_key_path: &Path)
-> Result<()> {
let name_with_rev = try!(artifact::artifact_signer(&archive.path));
if let Err(_) = SigKeyPair::get_public_key_path(&name_with_rev, cache_key_path) {
// we don't have the key locally, so try and download it before verification
println!("Can't find {} origin key in local key cache, fetching it from the depot",
&name_with_rev);
let (name, rev) = try!(parse_name_with_rev(&name_with_rev));
try!(depot_client::get_origin_key(url,
&name,
&rev,
cache_key_path.to_string_lossy().as_ref()));
let nwr = try!(artifact::artifact_signer(&archive.path));
if let Err(_) = SigKeyPair::get_public_key_path(&nwr, cache_key_path) {
println!("{} {} public origin key",
Green.bold().paint("↓ Downloading"),
&nwr);
let (name, rev) = try!(parse_name_with_rev(&nwr));
let mut progress = ProgressBar::default();
try!(depot_client::fetch_origin_key(url, &name, &rev, cache_key_path, Some(&mut progress)));
println!("{} {} public origin key",
Green.bold().paint("☑ Cached"),
&nwr);
}

try!(archive.verify(&cache_key_path));
println!("Successful verification of {} signed by {}",
&ident,
&name_with_rev);
info!("Verified {} signed by {}", &ident, &nwr);
Ok(())
}
2 changes: 2 additions & 0 deletions components/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
extern crate habitat_core as hcore;
extern crate habitat_depot_core as depot_core;
extern crate habitat_depot_client as depot_client;
extern crate ansi_term;
#[macro_use]
extern crate log;
extern crate openssl;
extern crate pbr;
extern crate regex;
extern crate rustc_serialize;
#[cfg(test)]
Expand Down
4 changes: 2 additions & 2 deletions components/core/src/crypto/artifact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub fn get_archive_reader<P: AsRef<Path>>(src: &P) -> Result<BufReader<File>> {
}

/// verify the crypto signature of a .hart file
pub fn verify<P1: ?Sized, P2: ?Sized>(src: &P1, cache_key_path: &P2) -> Result<()>
pub fn verify<P1: ?Sized, P2: ?Sized>(src: &P1, cache_key_path: &P2) -> Result<(String, String)>
where P1: AsRef<Path>,
P2: AsRef<Path>
{
Expand Down Expand Up @@ -158,7 +158,7 @@ pub fn verify<P1: ?Sized, P2: ?Sized>(src: &P1, cache_key_path: &P2) -> Result<(
debug!("Expected hash {}", expected_hash);
debug!("My hash {}", computed_hash);
if computed_hash == expected_hash {
Ok(())
Ok((pair.name_with_rev(), expected_hash))
} else {
let msg = format!("Habitat artifact is invalid, \
hashes don't match (expected: {}, computed: {})",
Expand Down
Loading

0 comments on commit 166f276

Please sign in to comment.