Skip to content

Commit

Permalink
add installation from archive
Browse files Browse the repository at this point in the history
  • Loading branch information
erratic-pattern committed May 5, 2023
1 parent 5c0cc69 commit b02971d
Show file tree
Hide file tree
Showing 13 changed files with 733 additions and 577 deletions.
788 changes: 483 additions & 305 deletions Cargo.lock

Large diffs are not rendered by default.

22 changes: 13 additions & 9 deletions crates/cloudtruth-installer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ edition = "2021"
license = "Apache-2.0"

[dependencies]
ci_info = "0.14.10"
bytes = "1.4"
ci_info = "0.14"
clap = { version = "4.0.0", features = ["derive", "env"] }
color-eyre = "0.6"
derive_more = "0.99.17"
is-terminal = "0.4.7"
itertools = "0.10.5"
octocrab = "0.21"
once_cell = "1.17.1"
powershell_script = "1.0.4"
reqwest = { version = "~0.9", default-features = false, features = ["default-tls-vendored"] }
derive_more = "0.99"
flate2 = "1.0"
futures-core = "0.3"
futures-util = "0.3"
is-terminal = "0.4"
octocrab = { git = "https://github.com/kallisti-dev/octocrab.git", branch = "asset-uploader-struct" }
once_cell = "1.17"
reqwest = { version = "~0.11", default-features = false, features = ["stream", "rustls-tls-native-roots"] }
serde_json = "1.0"
tar = "0.4"
tempfile = "3.4"
which = "4.4.0"
tokio = { version = "1.28", features = ["full"] }
which = "4.4"
7 changes: 7 additions & 0 deletions crates/cloudtruth-installer/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn main() {
// Make TARGET environment variable available to code at build-time
println!(
"cargo:rustc-env=TARGET={}",
std::env::var("TARGET").unwrap()
);
}
6 changes: 2 additions & 4 deletions crates/cloudtruth-installer/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ fn init_globals(cli: &Cli) {
pub struct Cli {
/// Subcommands
#[command(subcommand)]
command: Subcommand,
pub command: Subcommand,
/// Show verbose information
#[arg(global = true, short, long, default_value_t = false)]
verbose: bool,
Expand Down Expand Up @@ -79,10 +79,8 @@ pub enum Subcommand {

#[derive(Debug, clap::Args)]
pub struct InstallCommand {
/// Name of the program to install
name: String,
/// Version of the program to install (defaults to latest)
version: Option<String>,
pub version: Option<String>,
#[command(flatten)]
github_opts: GitHubOptions,
}
Expand Down
54 changes: 54 additions & 0 deletions crates/cloudtruth-installer/src/github.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use bytes::Bytes;
use color_eyre::{eyre::eyre, Result};
use futures_core::Stream;
use futures_util::StreamExt;
use std::path::Path;
use tokio::{fs::File, io::AsyncWriteExt};

/// Get tag name of latest GitHub release
pub async fn get_latest_version() -> Result<String> {
Ok(octocrab::instance()
.repos("cloudtruth", "cloudtruth-cli")
.releases()
.get_latest()
.await?
.tag_name)
}

/// Download asset from GitHub
async fn get_release_asset(
version: &str,
asset_name: &str,
) -> Result<impl Stream<Item = reqwest::Result<Bytes>>> {
let github = octocrab::instance();
let download_url = github
.repos("cloudtruth", "cloudtruth-cli")
.releases()
.get_by_tag(version)
.await?
.assets
.into_iter()
.find(|asset| asset.name == asset_name)
.map(|asset| asset.browser_download_url)
.ok_or_else(|| eyre!("Could not find release asset {asset_name} in release {version}"))?;
Ok(reqwest::get(download_url).await?.bytes_stream())
}

pub async fn download_release_asset(
version: &str,
asset_name: &str,
download_path: &Path,
) -> Result<()> {
let mut f = File::create(download_path).await?;
let mut stream = get_release_asset(version, asset_name).await?;
while let Some(chunk) = stream.next().await {
f.write_all(&chunk?).await?;
}
Ok(())
}

// Get package name for the current build target, version, and file extension
pub fn asset_name(version: &str, ext: &str) -> String {
const TARGET: &str = env!("TARGET");
format!("cloudtruth-{version}-{TARGET}.{ext}")
}
100 changes: 39 additions & 61 deletions crates/cloudtruth-installer/src/install.rs
Original file line number Diff line number Diff line change
@@ -1,65 +1,43 @@
use crate::InstallError;
use std::io;
use std::io::Write;
#[cfg(not(target_os = "windows"))]
#[rustfmt::skip]
use {
crate::version::binary_version,
std::fs,
std::process::Command,
std::str,
tempfile::tempdir,
};
use crate::{cli::InstallCommand, github, package_manager::choose_package_manager};

#[cfg(target_os = "windows")]
pub fn install_latest_version(quiet: bool) -> Result<(), InstallError> {
let text = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../install.ps1"));
let result = powershell_script::run(text);
match result {
Ok(output) => {
if !quiet {
if let Some(stdout_str) = output.stdout() {
io::stdout().write_all(stdout_str.as_bytes())?;
}
}
Ok(())
}
Err(err) => Err(InstallError::InstallFailed(err.to_string())),
}
}

#[cfg(not(target_os = "windows"))]
pub fn install_latest_version(quiet: bool) -> Result<(), InstallError> {
let filename = format!("cloudtruth-cli-install-{}.sh", binary_version());
let tempdir = tempdir()?;
let fullpath = tempdir.path().join(filename);
let fullname = fullpath.to_str().unwrap();
let text = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../install.sh"));
use color_eyre::Result;
use flate2::read::GzDecoder;
use std::{fs::File, path::Path};
use tar::Archive;
use tempfile::tempdir;

// write the install script to a file to a temporary directory
fs::write(fullname, text)?;

// attempt the chmod, and hope for success -- ignore failure
let _ = Command::new("chmod").arg("a+x").arg(fullname).output();
pub fn unpack_tar_gz(src_file_path: &Path, dest_file_path: &Path) -> Result<()> {
let tar_gz = File::open(src_file_path)?;
let tar = GzDecoder::new(tar_gz);
let mut archive = Archive::new(tar);
archive.unpack(dest_file_path)?;
Ok(())
}

// now, actually run the installation script
let result = Command::new(fullname).output();
match result {
Ok(output) => match output.status.success() {
true => {
if !quiet {
io::stdout().write_all(&output.stdout)?;
}
Ok(())
}
false => {
if !quiet {
io::stdout().write_all(&output.stdout)?;
}
let stderr = str::from_utf8(&output.stderr)?;
Err(InstallError::InstallFailed(stderr.to_string()))
}
},
Err(err) => Err(InstallError::FailedToRunInstall(err.to_string())),
}
pub async fn install(cmd: InstallCommand) -> Result<()> {
let pkg_manager = choose_package_manager();
let version = match cmd.version {
Some(version) => version,
None => github::get_latest_version().await?,
};
let tmp_dir = tempdir()?;
let ext = if let Some(pkg_manager) = &pkg_manager {
pkg_manager.package_ext()
} else if cfg!(target_os = "windows") {
"zip"
} else {
"tar.gz"
};
let asset_name = github::asset_name(&version, ext);
let download_path = tmp_dir.path().join(&asset_name);
github::download_release_asset(&version, &asset_name, &download_path).await?;
if let Some(pkg_manager) = pkg_manager {
pkg_manager.install(&download_path)?
} else if cfg!(target_os = "windows") {
todo!()
} else {
unpack_tar_gz(&download_path, tmp_dir.path())?;
println!("{:?}", tmp_dir.path());
};
Ok(())
}
41 changes: 0 additions & 41 deletions crates/cloudtruth-installer/src/install_errors.rs

This file was deleted.

26 changes: 16 additions & 10 deletions crates/cloudtruth-installer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
pub mod cli;
pub mod install;
pub mod install_errors;
pub mod package_manager;
pub mod version;
mod cli;
mod github;
mod install;
mod package_manager;

pub use cli::Cli;
pub use install::install_latest_version;
pub use install_errors::InstallError;
pub use package_manager::find_package_managers;
pub use version::{binary_version, get_latest_version};
use cli::Subcommand;
use color_eyre::Result;
use install::install;

macro_rules! verbose {
($($expr:tt)*) => { if $crate::cli::verbose() { println!($($expr)*)}}
}
pub(crate) use verbose;

pub async fn cloudtruth_installer_cli() -> Result<()> {
let cli = cli::parse();
#[allow(irrefutable_let_patterns)]
if let Subcommand::Install(install_cmd) = cli.command {
install(install_cmd).await?;
}
Ok(())
}
20 changes: 4 additions & 16 deletions crates/cloudtruth-installer/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
use cloudtruth_installer::{cli, find_package_managers, package_manager::PackageManagerBin};
use cloudtruth_installer::cloudtruth_installer_cli;

pub fn main() -> color_eyre::Result<()> {
#[tokio::main]
pub async fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
cli::parse();
let pkg_manager = if !cli::interactive() {
// When running non-interactive, take first available package manager
find_package_managers().next().unwrap()
} else {
let mut pkg_managers: Vec<PackageManagerBin> = find_package_managers().collect();
if pkg_managers.len() == 1 {
pkg_managers.swap_remove(0)
} else {
// Prompt user for package manager choice
todo!()
}
};
println!("{}", pkg_manager);
cloudtruth_installer_cli().await?;
Ok(())
}
Loading

0 comments on commit b02971d

Please sign in to comment.