Skip to content

Commit

Permalink
added run command, tweaked logging
Browse files Browse the repository at this point in the history
  • Loading branch information
jsoverson committed May 21, 2021
1 parent b65baec commit 903e40d
Show file tree
Hide file tree
Showing 9 changed files with 304 additions and 16 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ futures="0.3.13"
serde_json = "1.0.64"
serdeconv = "0.4.0"
thiserror = "1.0.24"
oci-distribution = "0.6"
itertools = "0.10.0"
unsafe-io = "= 0.6.2"

[dev-dependencies]

Expand Down
15 changes: 9 additions & 6 deletions src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub mod load;
pub use load::LoadCli;
pub mod run;
pub mod start;
use structopt::{clap::AppSettings, StructOpt};

use crate::{error::VinoError, logger::Logger};
Expand All @@ -25,7 +25,7 @@ fn parse_write_style(spec: &str) -> std::result::Result<WriteStyle, VinoError> {

#[derive(StructOpt, Debug, Clone)]
#[structopt(
global_settings(&[AppSettings::ColoredHelp, AppSettings::VersionlessSubcommands]),
global_settings(&[AppSettings::VersionlessSubcommands]),
name = "vino", about = "Vino host runtime")]
pub struct Cli {
#[structopt(flatten)]
Expand All @@ -34,9 +34,12 @@ pub struct Cli {

#[derive(Debug, Clone, StructOpt)]
pub enum CliCommand {
/// Manage contents of local wasmcloud cache
#[structopt(name = "load")]
Load(LoadCli),
/// Start a host with a manifest and schematics
#[structopt(name = "start")]
Start(start::StartCommand),
/// Run a Vino component on its own
#[structopt(name = "run")]
Run(run::RunCli),
}

#[derive(StructOpt, Debug, Clone)]
Expand Down
105 changes: 105 additions & 0 deletions src/commands/run.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use crate::Result;
use anyhow::Context;
use std::{collections::HashMap, path::Path};
use structopt::StructOpt;
use wasmcloud_host::{deserialize, Actor, HostBuilder};

use crate::{commands::init_logger, oci::fetch_oci_bytes, util::generate_run_manifest};

use super::LoggingOpts;

#[derive(Debug, Clone, StructOpt)]
#[structopt(rename_all = "kebab-case")]
pub struct RunCli {
#[structopt(flatten)]
pub logging: LoggingOpts,

/// Turn on info logging
#[structopt(long = "info")]
pub info: bool,

/// Allows the use of HTTP registry connections to these registries
#[structopt(long = "allowed-insecure")]
pub allowed_insecure: Vec<String>,

/// Input filename or URL
#[structopt()]
actor_ref: String,

/// JSON data
#[structopt(default_value = "\"\"")]
data: String,
}

pub(crate) async fn handle_command(command: RunCli) -> Result<String> {
let mut logging = command.logging.clone();
if !(command.info || command.logging.trace || command.logging.debug) {
logging.quiet = true;
}
init_logger(&logging)?;

let mut host_builder = HostBuilder::new();
host_builder = host_builder.oci_allow_latest();

if !command.allowed_insecure.is_empty() {
host_builder = host_builder.oci_allow_insecure(command.allowed_insecure.clone());
}

let host = host_builder.build();
let actor_ref = command.actor_ref.to_string();

let actor = fetch_actor(actor_ref.to_string(), true, command.allowed_insecure).await?;

let json_string = command.data;
let json: HashMap<String, serde_json::value::Value> =
serde_json::from_str(&json_string).context("Could not deserialized JSON input data")?;

info!("Starting host");
match host.start().await {
Ok(_) => {
info!("Loading dynamic manifest");
let hm = generate_run_manifest(actor_ref, actor, &json)?;
host.apply_manifest(hm).await?;
debug!("Manifest applied, executing component");
let raw_result = host.request("dynamic", json).await?;
debug!("Raw result: {:?}", raw_result);
let msg_result: HashMap<String, Vec<u8>> = deserialize(&raw_result)?;
let result: serde_json::Value = msg_result
.iter()
.map(|(k, v)| {
(
k.to_string(),
deserialize(&v).unwrap_or_else(|e| {
serde_json::Value::String(format!(
"Error deserializing output for port {}: {}",
k,
e.to_string()
))
}),
)
})
.collect();
println!("{}", result);
info!("Done");
host.stop().await;
}
Err(e) => {
error!("Failed to start host: {}", e);
}
}
Ok("Done".to_string())
}

async fn fetch_actor(
actor_ref: String,
allow_latest: bool,
allowed_insecure: Vec<String>,
) -> Result<Actor> {
let p = Path::new(&actor_ref);
if p.exists() {
Ok(wasmcloud_host::Actor::from_file(p)?)
} else {
let actor_bytes = fetch_oci_bytes(&actor_ref, allow_latest, &allowed_insecure).await?;
Ok(wasmcloud_host::Actor::from_slice(&actor_bytes)?)
}
}
9 changes: 5 additions & 4 deletions src/commands/load.rs → src/commands/start.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{fs::File, io::Read, path::PathBuf};

use anyhow::Result;
use crate::Result;
use futures::try_join;
use structopt::StructOpt;
use wasmcloud_host::{HostBuilder, HostManifest};
Expand All @@ -10,7 +10,8 @@ use crate::{commands::init_logger, error::VinoError};
use super::LoggingOpts;

#[derive(Debug, Clone, StructOpt)]
pub struct LoadCli {
#[structopt(rename_all = "kebab-case")]
pub struct StartCommand {
#[structopt(flatten)]
pub logging: LoggingOpts,

Expand Down Expand Up @@ -71,11 +72,11 @@ pub struct LoadCli {
pub allowed_insecure: Vec<String>,

/// Specifies a manifest file to apply to the host once started
#[structopt(long = "manifest", short = "m", parse(from_os_str))]
#[structopt(parse(from_os_str))]
pub manifest: Option<PathBuf>,
}

pub(crate) async fn handle_command(command: LoadCli) -> Result<String, VinoError> {
pub(crate) async fn handle_command(command: StartCommand) -> Result<String> {
init_logger(&command.logging)?;

if let Some(ref manifest_file) = command.manifest {
Expand Down
16 changes: 16 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ pub enum VinoError {
ConfigurationError,
#[error("file not found {0}")]
FileNotFound(String),
#[error("Configuration disallows fetching artifacts with the :latest tag ({0})")]
LatestDisallowed(String),
#[error("Could not fetch '{0}': {1}")]
OciFetchFailure(String, String),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
Expand All @@ -18,3 +22,15 @@ impl From<WasmCloudError> for VinoError {
VinoError::Other(anyhow!(e))
}
}

impl From<std::io::Error> for VinoError {
fn from(e: std::io::Error) -> Self {
VinoError::Other(anyhow!(e))
}
}

impl From<nkeys::error::Error> for VinoError {
fn from(e: nkeys::error::Error) -> Self {
VinoError::Other(anyhow!(e))
}
}
2 changes: 1 addition & 1 deletion src/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl Logger {
}

if opts.quiet {
set_level(&mut builder, log::LevelFilter::Off);
set_level(&mut builder, log::LevelFilter::Error);
} else if opts.trace {
set_level(&mut builder, log::LevelFilter::Trace);
} else if opts.debug {
Expand Down
15 changes: 10 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
pub(crate) mod commands;
pub(crate) mod error;
pub(crate) mod logger;
pub(crate) mod oci;
pub(crate) mod util;

use anyhow::Result;

use commands::load;
use commands::{get_args, CliCommand};
use error::VinoError;

pub type Result<T> = anyhow::Result<T, VinoError>;
pub type Error = VinoError;

#[macro_use]
extern crate log;
Expand All @@ -15,16 +18,18 @@ async fn main() -> Result<()> {
let cli = get_args();

let res = match cli.command {
CliCommand::Load(loadcmd) => load::handle_command(loadcmd).await,
CliCommand::Start(cmd) => commands::start::handle_command(cmd).await,
CliCommand::Run(cmd) => commands::run::handle_command(cmd).await,
};

std::process::exit(match res {
Ok(out) => {
println!("{}", out);
info!("{}", out);
0
}
Err(e) => {
eprintln!("Error: {}", e);
println!("Run with --info, --debug, or --trace for more information.");
1
}
});
Expand Down
98 changes: 98 additions & 0 deletions src/oci.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::{error::VinoError, Result};
use anyhow::Context;
use std::env::temp_dir;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::str::FromStr;

pub(crate) const OCI_VAR_USER: &str = "OCI_REGISTRY_USER";
pub(crate) const OCI_VAR_PASSWORD: &str = "OCI_REGISTRY_PASSWORD";
const PROVIDER_ARCHIVE_MEDIA_TYPE: &str = "application/vnd.wasmcloud.provider.archive.layer.v1+par";
const WASM_MEDIA_TYPE: &str = "application/vnd.module.wasm.content.layer.v1+wasm";
const OCI_MEDIA_TYPE: &str = "application/vnd.oci.image.layer.v1.tar";

pub(crate) async fn fetch_oci_bytes(
img: &str,
allow_latest: bool,
allowed_insecure: &[String],
) -> Result<Vec<u8>> {
if !allow_latest && img.ends_with(":latest") {
return Err(VinoError::LatestDisallowed(img.to_string()));
}
let cf = cached_file(img);
if !cf.exists() {
let img =
oci_distribution::Reference::from_str(img).context("Could not parse OCI reference")?;
let auth = if let Ok(u) = std::env::var(OCI_VAR_USER) {
if let Ok(p) = std::env::var(OCI_VAR_PASSWORD) {
oci_distribution::secrets::RegistryAuth::Basic(u, p)
} else {
oci_distribution::secrets::RegistryAuth::Anonymous
}
} else {
oci_distribution::secrets::RegistryAuth::Anonymous
};

let protocol =
oci_distribution::client::ClientProtocol::HttpsExcept(allowed_insecure.to_vec());
let config = oci_distribution::client::ClientConfig { protocol };
let mut c = oci_distribution::Client::new(config);
let imgdata = pull(&mut c, &img, &auth).await;

match imgdata {
Ok(imgdata) => {
let mut f = std::fs::File::create(cf).context("Could not create cache file")?;
let content = imgdata
.layers
.iter()
.map(|l| l.data.clone())
.flatten()
.collect::<Vec<_>>();
f.write_all(&content)
.context("Could not write actor bytes contents to cache file")?;
f.flush()
.context("Failed while flushing data to cache file")?;
Ok(content)
}
Err(e) => {
error!("Failed to fetch OCI bytes: {}", e);
Err(VinoError::OciFetchFailure(img.to_string(), e.to_string()))
}
}
} else {
let mut buf = vec![];
let mut f = std::fs::File::open(cached_file(img))
.context(format!("Could not open cache file {}", img))?;
f.read_to_end(&mut buf)
.context("Failed reading to the end of cache file")?;
Ok(buf)
}
}

fn cached_file(img: &str) -> PathBuf {
let path = temp_dir();
let path = path.join("wasmcloud_ocicache");
let _ = ::std::fs::create_dir_all(&path);
// should produce a file like wasmcloud_azurecr_io_kvcounter_v1.bin
let img = img.replace(":", "_");
let img = img.replace("/", "_");
let img = img.replace(".", "_");
let mut path = path.join(img);
path.set_extension("bin");

path
}

async fn pull(
client: &mut oci_distribution::Client,
img: &oci_distribution::Reference,
auth: &oci_distribution::secrets::RegistryAuth,
) -> Result<oci_distribution::client::ImageData> {
Ok(client
.pull(
&img,
&auth,
vec![PROVIDER_ARCHIVE_MEDIA_TYPE, WASM_MEDIA_TYPE, OCI_MEDIA_TYPE],
)
.await?)
}
Loading

0 comments on commit 903e40d

Please sign in to comment.