From 07e156dd3f80c04ccdb7dfd7d469f2dc0874faa8 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Tue, 21 Mar 2023 10:38:34 +0100 Subject: [PATCH] feat: Better configuration and description --- .drone.yml | 46 ---- .github/workflows/build-version.yaml | 2 +- ...iner-stable.yaml => container-latest.yaml} | 3 +- .github/workflows/container-version.yaml | 1 + Cargo.lock | 99 ++++++- Cargo.toml | 5 + README.md | 73 +++++- example/config.yaml | 23 ++ scripts/rename_releases.sh | 4 +- src/main.rs | 243 ++++++++++++++++-- 10 files changed, 400 insertions(+), 99 deletions(-) delete mode 100644 .drone.yml rename .github/workflows/{container-stable.yaml => container-latest.yaml} (94%) create mode 100644 example/config.yaml diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index bb32e67..0000000 --- a/.drone.yml +++ /dev/null @@ -1,46 +0,0 @@ ---- -kind: pipeline -type: kubernetes -name: Containeraztion latest -steps: -- name: Docker build - resources: - limits: - cpu: 100 - memory: 2048MiB - when: - branch: - - main - privileged: true - settings: - registry: git.badhouseplants.net - username: allanger - password: - from_secret: GITEA_TOKEN - repo: git.badhouseplants.net/badhouseplants/clever-install - tags: latest - platforms: - - linux/arm64 - - linux/amd64 - -steps: -- name: Docker build - image: thegeeklab/drone-docker-buildx - trigger: - event: - - tag - resources: - limits: - cpu: 100 - memory: 2048MiB - privileged: true - settings: - registry: git.badhouseplants.net - username: allanger - password: - from_secret: GITEA_TOKEN - repo: git.badhouseplants.net/badhouseplants/clever-install - tags: latest - platforms: - - linux/arm64 - - linux/amd64 diff --git a/.github/workflows/build-version.yaml b/.github/workflows/build-version.yaml index 9ab674c..09b01b7 100644 --- a/.github/workflows/build-version.yaml +++ b/.github/workflows/build-version.yaml @@ -50,7 +50,7 @@ jobs: uses: actions/download-artifact@v3 - name: Set version variable - run: echo "CLIN_VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV + run: echo "VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV - name: Rename release to avoid name conflict run: ./scripts/rename_releases.sh diff --git a/.github/workflows/container-stable.yaml b/.github/workflows/container-latest.yaml similarity index 94% rename from .github/workflows/container-stable.yaml rename to .github/workflows/container-latest.yaml index 9a7f0f3..7aa1d31 100644 --- a/.github/workflows/container-stable.yaml +++ b/.github/workflows/container-latest.yaml @@ -1,5 +1,5 @@ --- -name: "Stable container" +name: "Latest container" on: push: @@ -43,7 +43,6 @@ jobs: platforms: linux/amd64,linux/arm64 push: true tags: | - ghcr.io/${{ github.repository }}:stable ghcr.io/${{ github.repository }}:latest labels: | action_id=${{ github.action }} diff --git a/.github/workflows/container-version.yaml b/.github/workflows/container-version.yaml index 121c1ae..1b25e57 100644 --- a/.github/workflows/container-version.yaml +++ b/.github/workflows/container-version.yaml @@ -47,6 +47,7 @@ jobs: push: true tags: | ghcr.io/${{ github.repository }}:${{ env.TAG }} + ghcr.io/${{ github.repository }}:stable labels: | action_id=${{ github.action }} action_link=${{ env.LINK }} diff --git a/Cargo.lock b/Cargo.lock index ddcae8d..bb257c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,6 +155,8 @@ dependencies = [ "log", "reqwest", "serde", + "serde_yaml", + "tempfile", ] [[package]] @@ -772,15 +774,6 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - [[package]] name = "reqwest" version = "0.11.13" @@ -805,6 +798,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls", "serde", "serde_json", "serde_urlencoded", @@ -818,6 +812,21 @@ dependencies = [ "winreg", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rustix" version = "0.36.6" @@ -832,6 +841,18 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "ryu" version = "1.0.12" @@ -847,6 +868,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "2.7.0" @@ -913,6 +944,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82e6c8c047aa50a7328632d067bcae6ef38772a79e28daf32f735e0e4f3dd10" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha2" version = "0.10.6" @@ -943,6 +987,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "strsim" version = "0.10.0" @@ -962,16 +1012,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" dependencies = [ "cfg-if", "fastrand", - "libc", "redox_syscall", - "remove_dir_all", - "winapi", + "rustix", + "windows-sys", ] [[package]] @@ -1124,6 +1173,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad2024452afd3874bf539695e04af6732ba06517424dbf958fdb16a01f3bef6c" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.3.1" @@ -1239,6 +1300,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 36ad3e9..f39245f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,8 @@ log = "0.4.17" http = "0.2.8" serde = { version = "1.0.126", features = ["derive"] } reqwest = { version = "0.11", features = ["json", "blocking", "rustls"] } +serde_yaml = "0.9" + +[dev-dependencies] +tempfile = "3.4.0" + diff --git a/README.md b/README.md index 1c9a728..e0fc7f4 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ # What's it about? It's just a tool to make downloading binaries for different platforms easier. + ## For example If you want to build a docker image, but you want to make it available on different platforms. But your tool needs other tools as dependencies, e.g. `helm`. To install helm on Alpine you need to use curl, wget, or something. You need to choose a version, an operating system, and an architecture. For me, it was obvious that you must be able to use `uname -m`... @@ -32,15 +33,15 @@ Prebuilt binaries exist for **Linux x86_64** and **MacOS arm64** and **x86_64** Don't forget to add the binary to $PATH ``` -$ curl https://raw.githubusercontent.com/allanger/clever-install/main/scripts/download_dudo.sh | bash -$ dudo -h +$ curl https://raw.githubusercontent.com/allanger/dumb-downloader/main/scripts/download_dudo.sh | bash +$ dudo --help ``` ### Docker You can use the `latest` or a `tagged` docker image ``` -$ docker pull ghcr.io/allanger/clever-install:latest -$ docker run ghcr.io/allanger/clever-install:latest dudo -h +$ docker pull ghcr.io/allanger/dumb-downloader:latest +$ docker run ghcr.io/allanger/dumb-downloader:latest dudo -h ``` ### Build from source @@ -48,7 +49,67 @@ $ docker run ghcr.io/allanger/clever-install:latest dudo -h ``` $ cargo build --release ``` -2. Run `gum help` +2. Run `dudo --help` # How to use? -To be done +## Custom configurations + +In case the default config is not doing the trick for you, you can pass a custom configuration, for example, you need to download a package "package-linux-amd64_x86_64_intel_v1.0.3" and this kind of name for an architecture is not supported by the `dudo`, then you can create a config file like +```yaml +# config-example.yaml +--- +--- +os: + macos: + - macos + - darwin + - mac + - apple + linux: + - linux + windows: + - windows + freebsd: + - freebsd +arch: + x86_64: + - x86_64 + - amd64 + - amd + - intel + - amd64_x86_64_intel + aarch64: + - aarch64 + - arm64 + - m1 + +``` + +And execute `dudo -l "package-{{ os }}-{{ arch }}-{{ version}}" -p v1.0.3 -d /tmp/package` and dudo will download the package to the `/tmp/package` then, + +## Dockerfile + +The initial intetion for developing this was to use it for writing multi-architecture Dockerfiles for my another projects. I needed to download `helm` and `helmfile` for `arm64` and `amd64`. And I couldn't come up with good simple script for settings environment variables that would point to the the correct url, because `uname -m` wasn't giving me results that I would need. I was thinkg about writing a script to create some kind of map for different architectures, but then I thought that is was already not the first time I was having that problem and I decided to come up with a tool. And here is example, how one could use it in a `Dockerfile` + +```DOCKERFILE +ARG BASE_VERSION=latest +FROM ghcr.io/allanger/dumb-downloader as builder +RUN apt-get update -y && apt-get install tar -y +ARG HELM_VERSION=v3.10.3 +ARG HELMFILE_VERSION=0.151.0 +ENV RUST_LOG=info +RUN dudo -l "https://github.com/helmfile/helmfile/releases/download/v{{ version }}/helmfile_{{ version }}_{{ os }}_{{ arch }}.tar.gz" -i /tmp/helmfile.tar.gz -p $HELMFILE_VERSION +RUN dudo -l "https://get.helm.sh/helm-{{ version }}-{{ os }}-{{ arch }}.tar.gz" -i /tmp/helm.tar.gz -p $HELM_VERSION +RUN tar -xf /tmp/helm.tar.gz -C /tmp && rm -f /tmp/helm.tar.gz +RUN tar -xf /tmp/helmfile.tar.gz -C /tmp && rm -f /tmp/helmfile.tar.gz +RUN mkdir /out && for bin in `find /tmp | grep helm`; do cp $bin /out/; done +RUN chmod +x /out/helm +RUN chmod +x /out/helmfile + +FROM ghcr.io/allanger/check-da-helm-base:${BASE_VERSION} +COPY --from=builder /out/ /usr/bin +RUN apk update --no-cache && apk add --no-cache jq bash +ENTRYPOINT ["cdh"] +``` + +In the builder it is downloading dependencies that are needed in my final docker image. \ No newline at end of file diff --git a/example/config.yaml b/example/config.yaml new file mode 100644 index 0000000..ec00168 --- /dev/null +++ b/example/config.yaml @@ -0,0 +1,23 @@ +--- +os: + macos: + - macos + - darwin + - mac + - apple + linux: + - linux + windows: + - windows + freebsd: + - freebsd +arch: + x86_64: + - x86_64 + - amd64 + - amd + - intel + aarch64: + - aarch64 + - arm64 + - m1 diff --git a/scripts/rename_releases.sh b/scripts/rename_releases.sh index 1243071..b18ed18 100755 --- a/scripts/rename_releases.sh +++ b/scripts/rename_releases.sh @@ -1,10 +1,10 @@ #!/bin/bash echo 'renaming dudo to dudo-$VERSION-$SYSTEM format' mkdir -p release -echo "version - $CLIN_VERSION" +echo "version - $VERSION" for BUILD in build*; do SYSTEM=$(echo $BUILD | sed -e 's/build-//g') echo "system - $SYSTEM" - cp $BUILD/dudo release/dudo-$CLIN_VERSION-$SYSTEM + cp $BUILD/dudo release/dudo-$VERSION-$SYSTEM done ls release diff --git a/src/main.rs b/src/main.rs index f8a73a3..af01606 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,64 +5,251 @@ use log::{error, info}; use serde::{Deserialize, Serialize}; use std::{ + collections::HashMap, env::consts::{ARCH, OS}, - fs::File, - io, + fmt::Display, + fs::{File, OpenOptions}, + io::{self}, process::exit, }; +type Result = std::result::Result; +#[derive(Debug)] +enum DudoError { + IoError(io::Error), + SerdeYamlError(serde_yaml::Error), +} +impl From for DudoError { + fn from(error: io::Error) -> Self { + DudoError::IoError(error) + } +} + +impl From for DudoError { + fn from(error: serde_yaml::Error) -> Self { + DudoError::SerdeYamlError(error) + } +} + +impl Display for DudoError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DudoError::SerdeYamlError(err) => write!(f, "{}", err), + DudoError::IoError(err) => write!(f, "{}", err), + } + } +} + +static CONFIG: &str = " +--- +os: + macos: + - macos + - darwin + - mac + - apple + linux: + - linux + windows: + - windows + freebsd: + - freebsd +arch: + x86_64: + - x86_64 + - amd64 + - amd + - intel + aarch64: + - aarch64 + - arm64 + - m1 +"; + /// Maybe not that clever, but at least not dumb. Download binaries for defferent architectures easier #[derive(Parser)] #[clap(author = "allanger ", version, about, long_about = None, arg_required_else_help(true))] struct Args { /// A templated link for downloading - #[clap(short, long, env = "CLIN_LINK")] + #[clap(short, long, env = "DUDO_LINK_TEMPLATE")] link_template: String, /// Version that you want to download - #[clap(short, long, env = "CLIN_VERSION")] + #[clap(short, long, env = "DUDO_PACKAGE_VERSION")] package_version: String, /// Path to download - #[clap(short, long, env = "CLIN_PATH")] - install_path: String, + #[clap(short, long, env = "DUDO_DOWNLOADPATH")] + download_path: String, + /// Path to dudo config file + #[clap(short, long, default_value = "", env = "DUDO_CONFIG")] + config: String, } #[derive(Clone, Serialize, Deserialize)] -struct Values { +struct SystemValues { version: String, os: String, arch: String, } +#[derive(Clone, Serialize, Deserialize, Debug)] +struct Config { + os: HashMap>, + arch: HashMap>, +} + fn main() { + // Initial steps env_logger::init(); let args = Args::parse(); - let mut reg = Handlebars::new(); - reg.register_template_string("download_link", args.link_template) - .unwrap(); - let archs: Vec = match ARCH { - "x86_64" => vec!["x86_64".to_string(), "amd64".to_string()], - "aarch64" => vec!["aarch64".to_string(), "arm64".to_string()], - _ => { - error!("Unknown architecture"); + // Register download url template + let mut reg = Handlebars::new(); + match reg.register_template_string("download_link", args.link_template) { + Ok(_) => info!("Your template is successfully registered"), + Err(err) => error!("{}", err), + }; + + // Set system aliases + let config = match parse_config(args.config) { + Ok(config) => config, + Err(err) => { + error!("{}", err); exit(1); } }; - for arch in archs { - let version = args.package_version.clone(); - let os = OS.to_string(); - let values = Values { arch, os, version }; + info!("Running on {} {}", OS, ARCH); + let oss = config.os.get(&OS.clone().to_string()).unwrap(); + let archs = config.arch.get(&ARCH.clone().to_string()).unwrap(); - let link = reg.render("download_link", &values).unwrap(); - info!("Trying to download from {}", link.clone()); - let mut resp = reqwest::blocking::get(link).unwrap(); - if resp.status() == StatusCode::OK { - info!("Response is 200, I'll try to download"); - let mut out = File::create(args.install_path).expect("failed to create file"); - io::copy(&mut resp, &mut out).expect("failed to copy content"); - break; + for arch in archs { + for os in oss { + let version = args.package_version.clone(); + let values = SystemValues { + arch: arch.clone(), + os: os.clone(), + version, + }; + + let link = reg.render("download_link", &values).unwrap(); + info!("Trying to download from {}", link.clone()); + let mut resp = reqwest::blocking::get(link).unwrap(); + if resp.status() == StatusCode::OK { + info!("Response is 200, I'll try to download"); + let mut out = + File::create(args.download_path.clone()).expect("failed to create file"); + io::copy(&mut resp, &mut out).expect("failed to copy content"); + exit(0); + } + info!("Will try another name for arch, because response is not 200"); } - info!("Will try another name for arch, because response is not 200"); + } +} + +fn parse_config(config_path: String) -> Result { + let config_res: std::result::Result; + if config_path.is_empty() { + config_res = serde_yaml::from_str(CONFIG); + } else { + let f = OpenOptions::new().write(false).read(true).open(config_path); + let f = match f { + Ok(file) => file, + Err(err) => { + return Err(err.into()); + } + }; + config_res = serde_yaml::from_reader(f); + } + match config_res { + Ok(config) => Ok(config), + Err(err) => Err(err.into()), + } +} + +#[cfg(test)] +mod tests { + use crate::parse_config; + use std::io::Write; + use tempfile::NamedTempFile; + + #[test] + fn parse_config_default() { + let config = parse_config("".to_owned()).unwrap(); + assert_eq!( + config.os.get("linux").unwrap().clone(), + vec!["linux".to_string()] + ); + assert_eq!( + config.os.get("windows").unwrap().clone(), + vec!["windows".to_string()] + ); + assert_eq!( + config.os.get("macos").unwrap().clone(), + vec![ + "macos".to_string(), + "darwin".to_string(), + "mac".to_string(), + "apple".to_string(), + ] + ); + assert_eq!( + config.arch.get("x86_64").unwrap().clone(), + vec![ + "x86_64".to_string(), + "amd64".to_string(), + "amd".to_string(), + "intel".to_string(), + ] + ); + assert_eq!( + config.arch.get("aarch64").unwrap().clone(), + vec!["aarch64".to_string(), "arm64".to_string(), "m1".to_string(),] + ) + } + + #[test] + fn parse_config_custom() { + let config = " +--- +os: + macos: + - macos + linux: + - linux + windows: + - windows + freebsd: + - freebsd +arch: + x86_64: + - x86_64 + aarch64: + - aarch64 + "; + let mut file = NamedTempFile::new().unwrap(); + writeln!(file, "{}", config).unwrap(); + let path = file.into_temp_path(); + // It's looking damn not right + let config = parse_config(path.to_str().unwrap().clone().to_string()).unwrap(); + assert_eq!( + config.os.get("linux").unwrap().clone(), + vec!["linux".to_string()] + ); + assert_eq!( + config.os.get("windows").unwrap().clone(), + vec!["windows".to_string()] + ); + assert_eq!( + config.os.get("macos").unwrap().clone(), + vec!["macos".to_string()] + ); + assert_eq!( + config.arch.get("x86_64").unwrap().clone(), + vec!["x86_64".to_string()] + ); + assert_eq!( + config.arch.get("aarch64").unwrap().clone(), + vec!["aarch64".to_string()] + ) } }