Skip to content

Commit

Permalink
WIP: Added wick install (#388)
Browse files Browse the repository at this point in the history
* feat: added `wick install`

* fix: using batch and ps1 files vs links on windows

* ci: increment wick version

* test: added wick install test for local app

---------

Co-authored-by: fawadasaurus <[email protected]>
  • Loading branch information
jsoverson and fawadasaurus authored Jul 31, 2023
1 parent d79c68b commit 3158048
Show file tree
Hide file tree
Showing 31 changed files with 3,209 additions and 126 deletions.
4 changes: 3 additions & 1 deletion Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ lazy_static = "1.4"
liquid = "0.26"
liquid-json = "0.5"
markup-converter = "0.2"
mslnk = "0.1.8"
nkeys = "0.2"
nom = "7.1"
normpath = "1.1"
Expand Down
4 changes: 2 additions & 2 deletions crates/bins/wick/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "wick-cli"
version = "0.10.0"
version = "0.11.0"
edition = "2021"
default-run = "wick"
license = "Elastic-2.0"
Expand All @@ -13,7 +13,7 @@ cross = ["openssl/vendored"]
console = [
"wick-logger/console",
"tokio/full",
"tokio/tracing"
"tokio/tracing",
] # Build with RUSTFLAGS="--cfg tokio_unstable"
mem-profiler = ["dhat"]

Expand Down
5 changes: 5 additions & 0 deletions crates/bins/wick/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub(crate) mod config;
pub(crate) mod install;
pub(crate) mod invoke;
pub(crate) mod key;
pub(crate) mod list;
Expand Down Expand Up @@ -55,6 +56,10 @@ pub(crate) enum CliCommand {
#[clap(subcommand, name = "new")]
New(new::SubCommands),

/// Install a wick app to the local system.
#[clap(name = "install")]
Install(install::Options),

/// Show information about wick's configuration or manifest details.
#[clap(subcommand, name = "show")]
Show(show::SubCommands),
Expand Down
182 changes: 182 additions & 0 deletions crates/bins/wick/src/commands/install.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
use std::path::PathBuf;

use anyhow::Result;
use clap::Args;
use structured_output::StructuredOutput;
use wick_config::WickConfiguration;
use wick_package::WickPackage;

use crate::options::reconcile_fetch_options;

#[derive(Debug, Clone, Args)]
#[clap(rename_all = "kebab-case")]
#[group(skip)]
pub(crate) struct Options {
#[clap(flatten)]
pub(crate) oci: crate::oci::Options,

/// Alternate path to install to.
#[clap(long = "path", action)]
pub(crate) path: Option<PathBuf>,

/// Path or OCI url to application manifest file.
#[clap(action)]
application: String,
}

#[allow(clippy::unused_async)]
pub(crate) async fn handle(
opts: Options,
settings: wick_settings::Settings,
span: tracing::Span,
) -> Result<StructuredOutput> {
let xdg = wick_xdg::Settings::new();
let bin_dir = opts.path.unwrap_or_else(|| xdg.global().root().join("bin"));
std::fs::create_dir_all(&bin_dir)?;

span.in_scope(|| info!(app = opts.application, path = %bin_dir.display(), "installing wick app"));

let oci_opts = reconcile_fetch_options(&opts.application, &settings, opts.oci, false, None);
let app_as_path = PathBuf::from(&opts.application);
let package = if app_as_path.exists() {
WickPackage::from_path(&app_as_path).await?
} else {
crate::oci::pull(opts.application, oci_opts).await?
};

let path = package.path();
let config = WickConfiguration::fetch(path.to_string_lossy(), Default::default())
.await?
.into_inner();

let config = match config {
WickConfiguration::App(config) => config,
_ => anyhow::bail!(
"{} is not a wick application, it's a {} configuration",
path.display(),
config.kind()
),
};

let bin_path = bin_dir.join(config.name());

#[cfg(not(target_os = "windows"))]
{
std::fs::write(&bin_path, make_sh(path.to_str().unwrap()))?;

use std::os::unix::fs::PermissionsExt;

let mut perms = std::fs::metadata(path)?.permissions();
perms.set_mode(0o755);

info!(target=%path.to_string_lossy(),bin=%bin_path.to_string_lossy(), "installing");
std::fs::set_permissions(&bin_path, perms)?;
}
#[cfg(target_os = "windows")]
{
let mut ps1_path = bin_path.clone();
ps1_path.set_extension("ps1");
let mut cmd_path = bin_path.clone();
cmd_path.set_extension("cmd");
info!(target=%path.to_string_lossy(),cmd=%cmd_path.to_string_lossy(),ps1=%ps1_path.to_string_lossy(), "installing");
std::fs::write(&cmd_path, make_bat(&path.to_str().unwrap()))?;
std::fs::write(&ps1_path, make_ps1(&path.to_str().unwrap()))?;
}

let text = format!("installed {} to {}", config.name(), bin_path.display());
let json = serde_json::json!({
"name": config.name(),
"path": bin_path,
});

let output = StructuredOutput::new(text, json);

Ok(output)
}

#[cfg(not(target_os = "windows"))]
fn make_sh(target: &str) -> String {
format!(
r#"#!/bin/sh
basename=$(echo "$0" | sed -e 's,\\\\,/,g')
basedir=$(dirname "$basename")
case uname in
CYGWIN|MINGW|MSYS) basedir=cygpath -w "$basedir";;
esac
app_path="{}"
if [ -x "$basedir/wick" ]; then
exec "$basedir/wick" "run" "$app_path" "$@"
else
exec wick "run" "$app_path" "$@"
fi
"#,
target
)
}

#[cfg(target_os = "windows")]
fn make_ps1(target: &str) -> String {
format!(
r#"#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {{
# Fix case when both the Windows and Linux builds of wick
# are installed in the same directory
$exe=".exe"
}}
$app_path="{}"
$ret=0
if (Test-Path "$basedir\wick$exe") {{
# Support pipeline input
if ($MyInvocation.ExpectingInput) {{
$input | & "$basedir\wick$exe" "run" "$app_path" $args
}} else {{
& "$basedir\wick$exe" "run" "$app_path" $args
}}
$ret=$LASTEXITCODE
}} else {{
# Support pipeline input
if ($MyInvocation.ExpectingInput) {{
$input | & "wick$exe" "run" "$app_path" $args
}} else {{
& "wick$exe" "run" "$app_path" $args
}}
$ret=$LASTEXITCODE
}}
exit $ret
"#,
target,
)
}

#[cfg(target_os = "windows")]
fn make_bat(target: &str) -> String {
format!(
r#"@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
ECHO "%dp0%\wick.exe"
IF EXIST "%dp0%\wick.exe" (
SET "_prog=%dp0%\wick.exe"
) ELSE (
SET "_prog=wick"
SET PATHEXT=%PATHEXT:;.WICK;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "run" "{}" %*
"#,
target
)
}
11 changes: 10 additions & 1 deletion crates/bins/wick/src/commands/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,16 @@ pub(crate) async fn handle(
match val {
Ok(result) => {
let result: Value = result.into();
lines.push(result.to_string());
if let Value::String(v) = &result {
if opts.raw_output {
lines.push(v.clone());
} else {
lines.push(result.to_string());
}
} else {
lines.push(result.to_string());
}

json.push(result);
}
Err(e) => error!("Error: {}", e),
Expand Down
43 changes: 3 additions & 40 deletions crates/bins/wick/src/commands/registry/pull.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use anyhow::Result;
use clap::Args;
use structured_output::StructuredOutput;
use tracing::Instrument;
use wick_oci_utils::OciOptions;

use crate::options::get_auth_for_scope;
use crate::oci::pull;
use crate::options::reconcile_fetch_options;
#[derive(Debug, Clone, Args)]
#[clap(rename_all = "kebab-case")]
#[group(skip)]
Expand All @@ -27,50 +27,13 @@ pub(crate) struct Options {
pub(crate) oci_opts: crate::oci::Options,
}

pub(crate) async fn pull(reference: String, oci_opts: OciOptions) -> Result<wick_package::WickPackage, anyhow::Error> {
let pull_result = match wick_package::WickPackage::pull(&reference, &oci_opts).await {
Ok(pull_result) => pull_result,
Err(e) => {
if let wick_package::Error::Oci(wick_oci_utils::error::OciError::WouldOverwrite(files)) = &e {
warn!("Pulling {} will overwrite the following files", &reference);
for file in files {
warn!("{}", file.display());
}
error!("Refusing to overwrite files, pass --force to ignore.");
return Err(anyhow!("Pull failed"));
}
error!("Failed to pull {}: {}", &reference, e);
return Err(anyhow!("Pull failed"));
}
};
Ok(pull_result)
}

#[allow(clippy::unused_async)]
pub(crate) async fn handle(
opts: Options,
settings: wick_settings::Settings,
span: tracing::Span,
) -> Result<StructuredOutput> {
let configured_creds = settings
.credentials
.iter()
.find(|c| opts.reference.starts_with(&c.scope));

let (username, password) = get_auth_for_scope(
configured_creds,
opts.oci_opts.username.as_deref(),
opts.oci_opts.password.as_deref(),
);

let mut oci_opts = wick_oci_utils::OciOptions::default();
oci_opts
.set_allow_insecure(opts.oci_opts.insecure_registries)
.set_allow_latest(true)
.set_username(username)
.set_password(password)
.set_overwrite(opts.force)
.set_cache_dir(opts.output);
let oci_opts = reconcile_fetch_options(&opts.reference, &settings, opts.oci_opts, opts.force, Some(opts.output));

span.in_scope(|| debug!(options=?oci_opts, reference= opts.reference, "pulling reference"));

Expand Down
6 changes: 2 additions & 4 deletions crates/bins/wick/src/commands/show/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ pub(crate) async fn handle(
_span: tracing::Span,
) -> Result<StructuredOutput> {
let settings_text = serde_yaml::to_string(&settings)?;
let settings_json = serde_json::to_string(&settings)?;

let xdg = wick_xdg::Settings::new();

let env_text = serde_yaml::to_string(&xdg)?;
let env_json = serde_json::to_string(&xdg)?;

let json = serde_json::json!({
"settings": settings_json,
"env": env_json,
"settings": &settings,
"env": &xdg,
});

let text = format!(
Expand Down
1 change: 1 addition & 0 deletions crates/bins/wick/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ async fn async_main(cli: Cli, settings: wick_settings::Settings) -> Result<Struc
commands::rpc::SubCommands::Stats(cmd) => commands::rpc::stats::handle(cmd, settings, span).await,
},
CliCommand::Query(cmd) => commands::query::handle(cmd, settings, span).await,
CliCommand::Install(cmd) => commands::install::handle(cmd, settings, span).await,
CliCommand::New(cmd) => match cmd {
new::SubCommands::Component(cmd) => match cmd {
new::component::SubCommands::Http(cmd) => new::component::http::handle(cmd, settings, span).await,
Expand Down
22 changes: 22 additions & 0 deletions crates/bins/wick/src/oci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,25 @@ impl From<Options> for wick_config::FetchOptions {
fetch_options
}
}

pub(crate) async fn pull(
reference: String,
oci_opts: wick_oci_utils::OciOptions,
) -> Result<wick_package::WickPackage, anyhow::Error> {
let pull_result = match wick_package::WickPackage::pull(&reference, &oci_opts).await {
Ok(pull_result) => pull_result,
Err(e) => {
if let wick_package::Error::Oci(wick_oci_utils::error::OciError::WouldOverwrite(files)) = &e {
warn!("Pulling {} will overwrite the following files", &reference);
for file in files {
warn!("{}", file.display());
}
error!("Refusing to overwrite files, pass --force to ignore.");
return Err(anyhow!("Pull failed"));
}
error!("Failed to pull {}: {}", &reference, e);
return Err(anyhow!("Pull failed"));
}
};
Ok(pull_result)
}
Loading

0 comments on commit 3158048

Please sign in to comment.