diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2233067 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +target/ +Dockerfile \ No newline at end of file diff --git a/.gitea/workflows/check.yml b/.gitea/workflows/check.yml index b790ff0..ae2d001 100644 --- a/.gitea/workflows/check.yml +++ b/.gitea/workflows/check.yml @@ -1,14 +1,18 @@ name: Run Check Script on: push: + branches: + - master pull_request: jobs: check: - runs-on: rust-cargo + runs-on: docker + container: + image: hub.nationtech.io/harmony/harmony_composer:latest@sha256:eb0406fcb95c63df9b7c4b19bc50ad7914dd8232ce98e9c9abef628e07c69386 steps: - name: Checkout code uses: actions/checkout@v4 - + - name: Run check script run: bash check.sh diff --git a/.gitea/workflows/harmony_composer.yaml b/.gitea/workflows/harmony_composer.yaml new file mode 100644 index 0000000..6c7bc0f --- /dev/null +++ b/.gitea/workflows/harmony_composer.yaml @@ -0,0 +1,46 @@ +name: Compile and package harmony_composer +on: + push: + branches: + - master + +jobs: + package_harmony_composer: + container: + image: hub.nationtech.io/harmony/harmony_composer:latest@sha256:eb0406fcb95c63df9b7c4b19bc50ad7914dd8232ce98e9c9abef628e07c69386 + runs-on: dind + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Build for Linux x86_64 + run: cargo build --release --bin harmony_composer --target x86_64-unknown-linux-gnu + + - name: Build for Windows x86_64 GNU + run: cargo build --release --bin harmony_composer --target x86_64-pc-windows-gnu + + - uses: actions/upload-artifact@v3 + with: + name: binaries + path: | + target/x86_64-unknown-linux-gnu/release/harmony_composer + target/x86_64-pc-windows-gnu/release/harmony_composer.exe + + - name: Setup log into hub.nationtech.io + uses: docker/login-action@v3 + with: + registry: hub.nationtech.io + username: ${{ secrets.HUB_BOT_USER }} + password: ${{ secrets.HUB_BOT_PASSWORD }} + + # TODO: build ARM images and MacOS binaries (or other targets) too + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: hub.nationtech.io/harmony/harmony_composer:latest diff --git a/Cargo.lock b/Cargo.lock index 58c8700..57fce72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,6 +295,51 @@ dependencies = [ "cipher", ] +[[package]] +name = "bollard" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af706e9dc793491dd382c99c22fde6e9934433d4cc0d6a4b34eb2cdc57a5c917" +dependencies = [ + "base64 0.22.1", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "http 1.3.1", + "http-body-util", + "hyper 1.6.0", + "hyper-named-pipe", + "hyper-util", + "hyperlocal", + "log", + "pin-project-lite", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror 2.0.12", + "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-stubs" +version = "1.48.2-rc.28.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cdf0fccd5341b38ae0be74b74410bdd5eceeea8876dc149a13edfe57e3b259" +dependencies = [ + "serde", + "serde_json", + "serde_repr", + "serde_with", +] + [[package]] name = "bstr" version = "1.12.0" @@ -324,6 +369,55 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "camino" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84982c6c0ae343635a3a4ee6dedef965513735c8b183caa7289fa6e27399ebd4" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-util-schemas" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63d2780ac94487eb9f1fea7b0d56300abc9eb488800854ca217f102f5caccca" +dependencies = [ + "semver", + "serde", + "serde-untagged", + "serde-value", + "thiserror 1.0.69", + "toml", + "unicode-xid", + "url", +] + +[[package]] +name = "cargo_metadata" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7835cfc6135093070e95eb2b53e5d9b5c403dc3a6be6040ee026270aa82502" +dependencies = [ + "camino", + "cargo-platform", + "cargo-util-schemas", + "semver", + "serde", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "cassowary" version = "0.3.0" @@ -669,6 +763,12 @@ dependencies = [ "cipher", ] +[[package]] +name = "current_platform" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74858bcfe44b22016cb49337d7b6f04618c58e5dbfdef61b06b8c434324a0bc" + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -755,6 +855,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -980,6 +1081,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + [[package]] name = "errno" version = "0.3.11" @@ -1391,7 +1502,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", @@ -1410,7 +1521,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", @@ -1476,6 +1587,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "harmony_composer" +version = "0.1.0" +dependencies = [ + "bollard", + "cargo_metadata", + "clap", + "current_platform", + "env_logger", + "futures-util", + "log", + "serde_json", + "tokio", +] + [[package]] name = "harmony_macros" version = "0.1.0" @@ -1513,6 +1639,12 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.3" @@ -1769,6 +1901,21 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper 1.6.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + [[package]] name = "hyper-rustls" version = "0.27.5" @@ -1850,6 +1997,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "hyperlocal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +dependencies = [ + "hex", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -1993,6 +2155,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.9.0" @@ -2000,7 +2173,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.3", + "serde", ] [[package]] @@ -2338,7 +2512,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown", + "hashbrown 0.15.3", ] [[package]] @@ -3688,6 +3862,9 @@ name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] [[package]] name = "serde" @@ -3698,6 +3875,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-untagged" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299d9c19d7d466db4ab10addd5703e4c615dec2a5a16dbbafe191045e87ee66e" +dependencies = [ + "erased-serde", + "serde", + "typeid", +] + [[package]] name = "serde-value" version = "0.7.0" @@ -3741,6 +3929,26 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_tokenstream" version = "0.2.2" @@ -3765,13 +3973,30 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.9.0", + "serde", + "serde_derive", + "serde_json", + "time", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.9.0", "itoa", "ryu", "serde", @@ -4315,6 +4540,47 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.9.0", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tower" version = "0.5.2" @@ -4440,6 +4706,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.18.0" @@ -4487,6 +4759,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -5024,6 +5302,15 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" diff --git a/Cargo.toml b/Cargo.toml index 96d630a..970300d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "opnsense-config-xml", "harmony_cli", "k3d", + "harmony_composer", ] [workspace.package] @@ -23,7 +24,12 @@ log = "0.4.22" env_logger = "0.11.5" derive-new = "0.7.0" async-trait = "0.1.82" -tokio = { version = "1.40.0", features = ["io-std", "fs", "macros", "rt-multi-thread"] } +tokio = { version = "1.40.0", features = [ + "io-std", + "fs", + "macros", + "rt-multi-thread", +] } cidr = { features = ["serde"], version = "0.2" } russh = "0.45.0" russh-keys = "0.45.0" @@ -35,7 +41,7 @@ serde_yaml = "0.9.34" serde-value = "0.7.0" http = "1.2.0" inquire = "0.7.5" -convert_case = "0.8.0" +convert_case = "0.8.0" chrono = "0.4" [workspace.dependencies.uuid] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b7b690d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM docker.io/rust:1.87.0 AS build + +WORKDIR /app + +COPY . . + +RUN cargo build --release --bin harmony_composer + +FROM docker.io/rust:1.87.0 + +WORKDIR /app + +RUN rustup target add x86_64-pc-windows-gnu +RUN rustup target add x86_64-unknown-linux-gnu +RUN rustup component add rustfmt + +RUN apt update + +# TODO: Consider adding more supported targets +# nodejs for checkout action, docker for building containers, mingw for cross-compiling for windows +RUN apt install -y nodejs docker.io mingw-w64 + +COPY --from=build /app/target/release/harmony_composer . + +ENTRYPOINT ["/app/harmony_composer"] \ No newline at end of file diff --git a/harmony_composer/Cargo.toml b/harmony_composer/Cargo.toml new file mode 100644 index 0000000..6d8c5be --- /dev/null +++ b/harmony_composer/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "harmony_composer" +edition = "2024" +version.workspace = true +readme.workspace = true +license.workspace = true + +[dependencies] +clap = { version = "4.5.35", features = ["derive"] } +tokio.workspace = true +env_logger.workspace = true +log.workspace = true +bollard = "0.19.0" +current_platform = "0.2.0" +futures-util = "0.3.31" +serde_json = "1.0.140" +cargo_metadata = "0.20.0" diff --git a/harmony_composer/src/main.rs b/harmony_composer/src/main.rs new file mode 100644 index 0000000..0bdb7be --- /dev/null +++ b/harmony_composer/src/main.rs @@ -0,0 +1,327 @@ +use bollard::models::ContainerCreateBody; +use bollard::query_parameters::{ + CreateContainerOptionsBuilder, ListContainersOptionsBuilder, LogsOptions, + RemoveContainerOptions, StartContainerOptions, WaitContainerOptions, +}; +use bollard::secret::HostConfig; +use cargo_metadata::{Artifact, Message, MetadataCommand}; +use clap::{Args, Parser, Subcommand}; +use futures_util::StreamExt; +use log::info; +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use tokio::fs; + +#[derive(Parser)] +#[command(version, about, long_about = None, flatten_help = true, propagate_version = true)] +struct GlobalArgs { + #[arg(long, default_value = "harmony")] + harmony_path: String, + + #[arg(long)] + compile_method: Option, + + #[arg(long)] + compile_platform: Option, + + #[command(subcommand)] + command: Option, +} + +#[derive(Subcommand, Clone, Debug)] +enum Commands { + Check(CheckArgs), + Compile, + Deploy(DeployArgs), + All(AllArgs), +} + +#[derive(Args, Clone, Debug)] +struct CheckArgs { + #[arg(long, default_value = "check.sh")] + check_script_path: String, +} + +#[derive(Args, Clone, Debug)] +struct DeployArgs { + #[arg(long, default_value_t = false)] + staging: bool, + + #[arg(long, default_value_t = false)] + prod: bool, + + #[arg(long, default_value_t = false)] + smoke_test: bool, +} + +#[derive(Args, Clone, Debug)] +struct AllArgs { + #[command(flatten)] + check: CheckArgs, + + #[command(flatten)] + deploy: DeployArgs, +} + +#[tokio::main] +async fn main() { + env_logger::init(); + let cli_args = GlobalArgs::parse(); + + let harmony_path = Path::new(&cli_args.harmony_path) + .try_exists() + .expect("couldn't check if path exists"); + + let harmony_bin_path: PathBuf; + match harmony_path { + true => { + harmony_bin_path = compile_harmony( + cli_args.compile_method, + cli_args.compile_platform, + cli_args.harmony_path.clone(), + ) + .await + } + false => todo!("implement autodetect code"), + } + + match cli_args.command { + Some(command) => match command { + Commands::Check(args) => { + let check_script_str = + format!("{}/{}", cli_args.harmony_path, args.check_script_path); + + let check_script = Path::new(&check_script_str); + + match check_script + .try_exists() + .expect("couldn't check if path exists") + { + true => (), + false => todo!("implement couldn't find path logic"), + }; + + let check_output = Command::new(check_script) + .output() + .expect("failed to run check script"); + info!( + "check stdout: {}, check stderr: {}", + String::from_utf8(check_output.stdout).expect("couldn't parse from utf8"), + String::from_utf8(check_output.stderr).expect("couldn't parse from utf8") + ); + } + Commands::Deploy(args) => { + if args.staging { + todo!("implement staging deployment"); + } + + if args.prod { + todo!("implement prod deployment"); + } + let deploy_output = Command::new(harmony_bin_path) + .arg("-y") + .arg("-a") + .output() + .expect("failed to run harmony deploy"); + println!( + "deploy output: {}", + String::from_utf8(deploy_output.stdout).expect("couldn't parse from utf8") + ); + } + Commands::All(_args) => todo!( + "take all previous match arms and turn them into separate functions, and call them all one after the other" + ), + Commands::Compile => return, + }, + None => todo!("run interactively, ask for info on CLI"), + } +} + +#[derive(Clone, Debug, clap::ValueEnum)] +enum CompileMethod { + LocalCargo, + Docker, +} + +async fn compile_harmony( + method: Option, + platform: Option, + harmony_location: String, +) -> PathBuf { + let platform = match platform { + Some(p) => p, + None => current_platform::CURRENT_PLATFORM.to_string(), + }; + + let cargo_exists = Command::new("which") + .arg("cargo") + .status() + .expect("couldn't get `which cargo` status") + .success(); + + let method = match method { + Some(m) => m, + None => { + if cargo_exists { + return compile_cargo(platform, harmony_location).await; + } else { + return compile_docker(platform, harmony_location).await; + } + } + }; + + match method { + CompileMethod::LocalCargo => return compile_cargo(platform, harmony_location).await, + CompileMethod::Docker => return compile_docker(platform, harmony_location).await, + }; +} + +// TODO: make sure this works with cargo workspaces +async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf { + let metadata = MetadataCommand::new() + .manifest_path(format!("{}/Cargo.toml", harmony_location)) + .exec() + .unwrap(); + + let mut cargo_build = Command::new("cargo") + .current_dir(&harmony_location) + .args(vec![ + "build", + format!("--target={}", platform).as_str(), + "--release", + "--message-format=json-render-diagnostics", + ]) + .stdout(Stdio::piped()) + .spawn() + .expect("run cargo command failed"); + + let mut artifacts: Vec = vec![]; + let reader = std::io::BufReader::new(cargo_build.stdout.take().unwrap()); + for message in cargo_metadata::Message::parse_stream(reader) { + match message.unwrap() { + Message::CompilerMessage(_msg) => (), + Message::CompilerArtifact(artifact) => { + if artifact.manifest_path + == metadata + .root_package() + .expect("failed to get root package") + .manifest_path + { + println!("{:?}", artifact); + artifacts.push(artifact); + } + } + Message::BuildScriptExecuted(_script) => (), + Message::BuildFinished(finished) => { + println!("{:?}", finished); + } + _ => (), // Unknown message + } + } + + let bin = artifacts + .last() + .expect("no binaries built") + .filenames + .first() + .expect("couldn't get filename"); + + let bin_out; + if let Some(ext) = bin.extension() { + bin_out = PathBuf::from(format!("{}/harmony.{}", harmony_location, ext)); + let _copy_res = fs::copy(&bin, &bin_out).await; + } else { + bin_out = PathBuf::from(format!("{}/harmony", harmony_location)); + let _copy_res = fs::copy(&bin, &bin_out).await; + } + return bin_out; +} + +async fn compile_docker(platform: String, harmony_location: String) -> PathBuf { + let docker_client = + bollard::Docker::connect_with_local_defaults().expect("couldn't connect to docker"); + + let mut filters = HashMap::new(); + filters.insert(String::from("name"), vec![String::from("harmony_build")]); + let list_containers_options = ListContainersOptionsBuilder::new() + .all(true) + .filters(&filters) + .build(); + + let containers = &docker_client + .list_containers(Some(list_containers_options)) + .await + .expect("list containers failed"); + + if containers.len() > 0 { + docker_client + .remove_container("harmony_build", None::) + .await + .expect("failed to remove container"); + } + + let options = Some( + CreateContainerOptionsBuilder::new() + .name("harmony_build") + .build(), + ); + + let config = ContainerCreateBody { + image: Some("hub.nationtech.io/harmony/harmony_composer".to_string()), + working_dir: Some("/mnt".to_string()), + cmd: Some(vec![ + format!("--compile-platform={}", platform), + format!("--harmony-path=/mnt"), + "compile".to_string(), + ]), + host_config: Some(HostConfig { + binds: Some(vec![format!("{}:/mnt", harmony_location)]), + ..Default::default() + }), + ..Default::default() + }; + + docker_client + .create_container(options, config) + .await + .expect("couldn't create container"); + + docker_client + .start_container("harmony_build", None::) + .await + .expect("couldn't start container"); + + let wait_options = WaitContainerOptions { + condition: "not-running".to_string(), + }; + let mut wait = docker_client + .wait_container("harmony_build", Some(wait_options)) + .boxed(); + + let mut logs_stream = docker_client.logs( + "harmony_build", + Some(LogsOptions { + follow: true, + stdout: true, + stderr: true, + tail: "all".to_string(), + ..Default::default() + }), + ); + + while let Some(l) = logs_stream.next().await { + let l_str = l.expect("couldn't unwrap logoutput").to_string(); + println!("{}", l_str); + } + + // wait until container is no longer running + while let Some(_) = wait.next().await {} + + // hack that should be cleaned up + if platform.contains("windows") { + return PathBuf::from(format!("{}/harmony.exe", harmony_location)); + } else { + return PathBuf::from(format!("{}/harmony", harmony_location)); + } +}