diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 85dd54e..637aca1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -98,10 +98,22 @@ jobs: runner: ubuntu-latest arch_tag: amd64 cache_suffix: cache-amd64 + dockerfile: Dockerfile - platform: linux/arm64 runner: ubuntu-22.04-arm arch_tag: arm64 cache_suffix: cache-arm64 + dockerfile: Dockerfile + - platform: linux/amd64 + runner: ubuntu-latest + arch_tag: amd64-engine + cache_suffix: cache-amd64-engine + dockerfile: cloudengine.Dockerfile + - platform: linux/arm64 + runner: ubuntu-22.04-arm + arch_tag: arm64-engine + cache_suffix: cache-arm64-engine + dockerfile: cloudengine.Dockerfile steps: - name: "Checkout code" uses: actions/checkout@v4 @@ -138,6 +150,7 @@ jobs: uses: docker/build-push-action@v6 with: push: true + file: ${{ matrix.dockerfile }} platforms: ${{ matrix.platform }} tags: | ghcr.io/${{ github.repository }}:${{ matrix.arch_tag }} @@ -148,13 +161,32 @@ jobs: BUILDKIT_INLINE_CACHE=1 docker-manifest: - name: "Create and push multi-arch manifest" + name: "Create and push multi-arch manifest (${{ matrix.variant }})" if: ${{ github.ref_type == 'tag' }} needs: docker-build runs-on: ubuntu-latest permissions: contents: read packages: write + strategy: + matrix: + include: + - variant: standard + amd64_tag: amd64 + arm64_tag: arm64 + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=${{ github.sha }},enable=true + type=raw,value=latest,enable=true + - variant: engine + amd64_tag: amd64-engine + arm64_tag: arm64-engine + tags: | + type=semver,pattern={{version}},suffix=-engine + type=raw,value=engine-beta,enable=true steps: - name: "Checkout code" uses: actions/checkout@v4 @@ -167,13 +199,7 @@ jobs: with: images: | ghcr.io/${{ github.repository }} - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=raw,value=${{ github.sha }},enable=true - type=raw,value=latest,enable=${{ github.ref_type == 'tag' }} + tags: ${{ matrix.tags }} - name: "Log in to GitHub Container Registry" uses: docker/login-action@v3 @@ -187,5 +213,5 @@ jobs: with: tags: ${{ steps.meta.outputs.tags }} sources: | - ghcr.io/${{ github.repository }}:amd64 - ghcr.io/${{ github.repository }}:arm64 + ghcr.io/${{ github.repository }}:${{ matrix.amd64_tag }} + ghcr.io/${{ github.repository }}:${{ matrix.arm64_tag }} diff --git a/Cargo.lock b/Cargo.lock index 61f53ce..b23c54a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -791,7 +791,7 @@ dependencies = [ "serde_cbor", "serde_repr", "sha2", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1325,7 +1325,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pocket-ic" version = "12.0.0" -source = "git+https://github.com/dfinity/ic?rev=781ef50bd6bfbcfac6769b55361d0624247009c1#781ef50bd6bfbcfac6769b55361d0624247009c1" +source = "git+https://github.com/dfinity/ic?rev=d17cf0bcd330e94f868cc7d19d0ac860cbe64198#d17cf0bcd330e94f868cc7d19d0ac860cbe64198" dependencies = [ "backoff", "base64 0.13.1", @@ -1346,7 +1346,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "tracing-appender", @@ -1422,7 +1422,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -1443,7 +1443,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -2092,11 +2092,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -2112,9 +2112,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -2300,7 +2300,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" dependencies = [ "crossbeam-channel", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tracing-subscriber", ] diff --git a/Cargo.toml b/Cargo.toml index d33b77c..0ba5868 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,9 @@ edition = "2024" authors = ["DFINITY Stiftung "] license = "Apache-2.0" +[features] +cloud-engine = [] + [dependencies] anyhow = { version = "1.0.100", features = ["backtrace"] } camino = "1.2.2" @@ -12,7 +15,7 @@ clap = { version = "4.5.53", features = ["derive", "env"] } hex = "0.4.3" ic_principal = "0.1.1" notify = "8.2.0" -pocket-ic = { git = "https://github.com/dfinity/ic", rev = "781ef50bd6bfbcfac6769b55361d0624247009c1" } +pocket-ic = { git = "https://github.com/dfinity/ic", rev = "d17cf0bcd330e94f868cc7d19d0ac860cbe64198" } reqwest = { version = "0.12.24", default-features = false, features = ["rustls-tls", "json"] } semver = "1.0.27" serde = { version = "1.0.228", features = ["derive"] } diff --git a/Dockerfile b/Dockerfile index d71bf5e..2c65501 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.93.1-slim-trixie AS chef +FROM rust:1.94.0-slim-trixie AS chef RUN apt-get update && apt-get install -y jq curl WORKDIR /app RUN cargo install cargo-chef --version 0.1.73 --locked diff --git a/README.md b/README.md index fe28d30..8b41115 100644 --- a/README.md +++ b/README.md @@ -142,12 +142,18 @@ When `--status-dir` is provided, the launcher writes a JSON status file (`status The launcher handles `SIGINT` (Ctrl+C) and `SIGTERM` for graceful shutdown. It stops the PocketIC server and waits for it to exit before terminating. +### Experimental features + +If the launcher is built with `--feature cloud-engine`, you can create subnets of type `cloud-engine`. The subnet admin is set to the anonymous principal. The `:cloudengine-beta` Docker tag is an alternative to the standard image with this feature enabled. + ## Installing `icp-cli-network-launcher` is typically installed automatically by icp-cli. It can be updated to the latest version with `icp network update`. Binary downloads are also available on the [releases page](https://github.com/dfinity/icp-cli-network-launcher/releases). +A Docker image is published under the name `ghcr.io/dfinity/icp-cli-network-launcher`. The container port 4943 should be published for gateway access, and if a status dir is needed, mount it at `/app/status`. + ## Development ### Prerequisites diff --git a/cloudengine.Dockerfile b/cloudengine.Dockerfile new file mode 100644 index 0000000..4fdec4d --- /dev/null +++ b/cloudengine.Dockerfile @@ -0,0 +1,22 @@ +FROM rust:1.94.0-slim-trixie AS chef +RUN apt-get update && apt-get install -y jq curl +WORKDIR /app +RUN cargo install cargo-chef --version 0.1.73 --locked +FROM chef AS planner +COPY . . +RUN cargo chef prepare --recipe-path recipe.json +FROM chef AS builder +COPY --from=planner /app/recipe.json recipe.json +RUN cargo chef cook --release --recipe-path recipe.json +COPY . . +RUN ./package.sh out -- --features=cloud-engine +FROM debian:trixie-slim AS runtime +RUN apt-get update && apt-get install -y ca-certificates +WORKDIR /app +COPY --from=builder /app/out ./ +STOPSIGNAL SIGINT +EXPOSE 4942/tcp 4943/tcp +ENTRYPOINT ["/app/icp-cli-network-launcher", "--status-dir=/app/status", \ + "--config-port", "4942", "--gateway-port", "4943", \ + "--bind", "0.0.0.0", "--pocketic-config-bind", "0.0.0.0"] +CMD ["--nns", "--ii", "--subnet=application", "--subnet=cloud-engine"] diff --git a/package.sh b/package.sh index 48c6afd..3d11591 100755 --- a/package.sh +++ b/package.sh @@ -18,8 +18,19 @@ case $(uname -m) in aarch64*) arch="arm64";; *) echo "Unsupported architecture $(uname -m)"; exit 1;; esac + +dir= +cargo_args=() +while (( $# )); do + case "$1" in + --) shift; cargo_args=("$@"); break;; + *) [[ -z "$dir" ]] && dir="$1" || die "too many arguments";; + esac + shift +done + maketarball=0 -if [[ -z "$1" ]]; then +if [[ -z "$dir" ]]; then maketarball=1 fi tar=tar @@ -44,8 +55,8 @@ else [[ "$source" != "git+"* ]] || die "package.version is not patch but pocket-ic dependency is git" fi name="icp-cli-network-launcher-${arch}-${os}-v${v}" -outdir="${1-"dist/${name}"}" -cargo build --release +outdir="${dir:-"dist/${name}"}" +cargo build --release "${cargo_args[@]}" mkdir -p "${outdir}" cp "target/release/icp-cli-network-launcher" "${outdir}/" if [[ -z "$icdate" ]]; then diff --git a/src/main.rs b/src/main.rs index 9678539..45e3409 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(feature = "cloud-engine"), allow(unused))] + use std::{ collections::HashSet, fs, @@ -14,7 +16,10 @@ use ic_principal::Principal; use notify::{Event, RecursiveMode, Watcher, recommended_watcher}; use pocket_ic::{ PocketIcBuilder, - common::rest::{AutoProgressConfig, IcpFeatures, IcpFeaturesConfig, InstanceHttpGatewayConfig}, + common::rest::{ + AutoProgressConfig, ExtendedSubnetConfigSet, IcpFeatures, IcpFeaturesConfig, + InstanceHttpGatewayConfig, SubnetSpec, + }, }; use reqwest::Client; use semver::{Version, VersionReq}; @@ -101,6 +106,8 @@ enum SubnetKind { Fiduciary, Nns, Sns, + #[cfg(feature = "cloud-engine")] + CloudEngine, } #[tokio::main] @@ -218,7 +225,22 @@ async fn main() -> anyhow::Result<()> { drop(watcher); // pocket-ic CLI setup ends here // initial HTTP setup - let mut pic = PocketIcBuilder::new() + let mut base_subnets = ExtendedSubnetConfigSet::default(); + #[cfg(feature = "cloud-engine")] + for _ in 0..subnet + .iter() + .filter(|s| matches!(s, SubnetKind::CloudEngine)) + .count() + { + use pocket_ic::common::rest::CanisterCyclesCostSchedule; + + base_subnets.cloud_engine.push( + SubnetSpec::default() + .with_subnet_admins(vec![Principal::anonymous()]) + .with_cost_schedule(CanisterCyclesCostSchedule::Free), + ); + } + let mut pic = PocketIcBuilder::new_with_config(base_subnets) .with_server_url( format!("http://127.0.0.1:{config_port}/") .parse() @@ -256,6 +278,8 @@ async fn main() -> anyhow::Result<()> { SubnetKind::Fiduciary => pic = pic.with_fiduciary_subnet(), SubnetKind::Nns => pic = pic.with_nns_subnet(), SubnetKind::Sns => pic = pic.with_sns_subnet(), + #[cfg(feature = "cloud-engine")] + SubnetKind::CloudEngine => {} // handled above } } }