diff --git a/Cargo.lock b/Cargo.lock index e8bc0c15..eed4e093 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2604,6 +2604,7 @@ dependencies = [ "pocket-ic", "predicates", "rand 0.9.2", + "regex", "reqwest", "sec1 0.7.3", "serde", @@ -2612,6 +2613,8 @@ dependencies = [ "serial_test", "snafu", "sysinfo", + "thiserror 2.0.17", + "time", "tiny-bip39 2.0.0", "tokio", "tracing", diff --git a/crates/icp-cli/Cargo.toml b/crates/icp-cli/Cargo.toml index fdd9fc04..db222dee 100644 --- a/crates/icp-cli/Cargo.toml +++ b/crates/icp-cli/Cargo.toml @@ -16,8 +16,8 @@ bigdecimal.workspace = true bip32.workspace = true byte-unit.workspace = true camino-tempfile.workspace = true -candid_parser.workspace = true candid.workspace = true +candid_parser.workspace = true clap-markdown.workspace = true clap.workspace = true console.workspace = true @@ -43,11 +43,13 @@ pkcs8.workspace = true rand.workspace = true reqwest.workspace = true sec1.workspace = true +serde.workspace = true serde_json.workspace = true serde_yaml.workspace = true -serde.workspace = true snafu.workspace = true sysinfo.workspace = true +thiserror.workspace = true +time.workspace = true tiny-bip39.workspace = true tokio.workspace = true tracing-subscriber.workspace = true @@ -62,6 +64,7 @@ icp = { workspace = true } nix = { version = "0.30.1", features = ["process", "signal"] } pocket-ic.workspace = true predicates = "3" +regex = "1" rand.workspace = true serde_yaml.workspace = true serial_test = { version = "3.2.0", features = ["file_locks"] } diff --git a/crates/icp-cli/src/commands/canister/mod.rs b/crates/icp-cli/src/commands/canister/mod.rs index 0657d1ed..5bcdaa00 100644 --- a/crates/icp-cli/src/commands/canister/mod.rs +++ b/crates/icp-cli/src/commands/canister/mod.rs @@ -8,6 +8,7 @@ pub(crate) mod install; pub(crate) mod list; pub(crate) mod settings; pub(crate) mod show; +pub(crate) mod snapshot; pub(crate) mod start; pub(crate) mod status; pub(crate) mod stop; @@ -41,6 +42,10 @@ pub(crate) enum Command { /// Show a canister's details Show(show::ShowArgs), + /// Manipulate canister snapshots + #[command(subcommand)] + Snapshot(snapshot::Command), + /// Start a canister on a network Start(start::StartArgs), diff --git a/crates/icp-cli/src/commands/canister/snapshot/create.rs b/crates/icp-cli/src/commands/canister/snapshot/create.rs new file mode 100644 index 00000000..c997bd31 --- /dev/null +++ b/crates/icp-cli/src/commands/canister/snapshot/create.rs @@ -0,0 +1,63 @@ +use clap::Args; +use ic_management_canister_types::TakeCanisterSnapshotArgs; + +use crate::commands::{ + args, + canister::snapshot::{SnapshotId, ensure_canister_stopped}, +}; +use icp::context::Context; + +#[derive(Debug, Args)] +pub struct CreateArgs { + #[command(flatten)] + pub(crate) cmd_args: args::CanisterCommandArgs, + + /// If a snapshot ID is specified, this snapshot will replace it and reuse the ID. + #[arg(long)] + replace: Option, +} + +pub async fn exec(ctx: &Context, args: &CreateArgs) -> Result<(), anyhow::Error> { + let selections = args.cmd_args.selections(); + + let agent = ctx + .get_agent( + &selections.identity, + &selections.network, + &selections.environment, + ) + .await?; + let cid = ctx + .get_canister_id( + &selections.canister, + &selections.network, + &selections.environment, + ) + .await?; + + // Management Interface + let mgmt = ic_utils::interfaces::ManagementCanister::create(&agent); + + // Ensure canister is stopped + let (status,) = mgmt.canister_status(&cid).await?; + ensure_canister_stopped(status.status, &args.cmd_args.canister.to_string())?; + + // Create snapshot + let (snapshot,) = mgmt + .take_canister_snapshot( + &cid, + &TakeCanisterSnapshotArgs { + canister_id: cid, + replace_snapshot: args.replace.as_ref().map(|id| id.0.clone()), + }, + ) + .await?; + + eprintln!( + "Created a new snapshot of canister '{}'. Snapshot ID: '{}'", + args.cmd_args.canister, + SnapshotId(snapshot.id) + ); + + Ok(()) +} diff --git a/crates/icp-cli/src/commands/canister/snapshot/delete.rs b/crates/icp-cli/src/commands/canister/snapshot/delete.rs new file mode 100644 index 00000000..49545b2a --- /dev/null +++ b/crates/icp-cli/src/commands/canister/snapshot/delete.rs @@ -0,0 +1,53 @@ +use clap::Args; +use ic_management_canister_types::DeleteCanisterSnapshotArgs; + +use crate::commands::{args, canister::snapshot::SnapshotId}; +use icp::context::Context; + +#[derive(Debug, Args)] +pub struct DeleteArgs { + #[command(flatten)] + pub(crate) cmd_args: args::CanisterCommandArgs, + + /// The ID of the snapshot to delete. + snapshot: SnapshotId, +} + +pub async fn exec(ctx: &Context, args: &DeleteArgs) -> Result<(), anyhow::Error> { + let selections = args.cmd_args.selections(); + + let agent = ctx + .get_agent( + &selections.identity, + &selections.network, + &selections.environment, + ) + .await?; + let cid = ctx + .get_canister_id( + &selections.canister, + &selections.network, + &selections.environment, + ) + .await?; + + // Management Interface + let mgmt = ic_utils::interfaces::ManagementCanister::create(&agent); + + // Delete snapshot + mgmt.delete_canister_snapshot( + &cid, + &DeleteCanisterSnapshotArgs { + canister_id: cid, + snapshot_id: args.snapshot.0.clone(), + }, + ) + .await?; + + eprintln!( + "Deleted snapshot {} from canister '{}'", + args.snapshot, args.cmd_args.canister, + ); + + Ok(()) +} diff --git a/crates/icp-cli/src/commands/canister/snapshot/download.rs b/crates/icp-cli/src/commands/canister/snapshot/download.rs new file mode 100644 index 00000000..72628088 --- /dev/null +++ b/crates/icp-cli/src/commands/canister/snapshot/download.rs @@ -0,0 +1,42 @@ +use clap::Args; +use icp::{identity, prelude::PathBuf}; + +use crate::commands::{ + args, + canister::snapshot::{SnapshotId, directory_parser}, +}; +use icp::context::Context; + +#[derive(Debug, Args)] +pub struct DownloadArgs { + #[command(flatten)] + pub(crate) cmd_args: args::CanisterCommandArgs, + + /// The ID of the snapshot to download. + snapshot: SnapshotId, + + /// The directory to download the snapshot to. + #[arg(long, value_parser = directory_parser)] + dir: PathBuf, + + /// Whether to resume the download if the previous snapshot download failed. + #[arg(short, long, default_value = "false")] + resume: bool, + + /// The number of concurrent downloads to perform. + #[arg(long, default_value = "3")] + concurrency: usize, +} + +#[derive(Debug, thiserror::Error)] +pub enum CommandError { + #[error(transparent)] + Project(#[from] icp::LoadError), + + #[error(transparent)] + Identity(#[from] identity::LoadError), +} + +pub async fn exec(_ctx: &Context, _args: &DownloadArgs) -> Result<(), CommandError> { + unimplemented!("project mode is not implemented yet"); +} diff --git a/crates/icp-cli/src/commands/canister/snapshot/list.rs b/crates/icp-cli/src/commands/canister/snapshot/list.rs new file mode 100644 index 00000000..d1fafd55 --- /dev/null +++ b/crates/icp-cli/src/commands/canister/snapshot/list.rs @@ -0,0 +1,63 @@ +use clap::Args; +use ic_management_canister_types::Snapshot; +use indicatif::HumanBytes; +use time::{OffsetDateTime, macros::format_description}; + +use crate::commands::{args, canister::snapshot::SnapshotId}; +use icp::context::Context; + +#[derive(Debug, Args)] +pub struct ListArgs { + #[command(flatten)] + pub(crate) cmd_args: args::CanisterCommandArgs, +} + +pub async fn exec(ctx: &Context, args: &ListArgs) -> Result<(), anyhow::Error> { + let selections = args.cmd_args.selections(); + + let agent = ctx + .get_agent( + &selections.identity, + &selections.network, + &selections.environment, + ) + .await?; + let cid = ctx + .get_canister_id( + &selections.canister, + &selections.network, + &selections.environment, + ) + .await?; + + // Management Interface + let mgmt = ic_utils::interfaces::ManagementCanister::create(&agent); + + let (snapshots,) = mgmt.list_canister_snapshots(&cid).await?; + + if snapshots.is_empty() { + eprintln!( + "No snapshots found for canister '{}'", + args.cmd_args.canister + ); + } else { + for snapshot in snapshots { + print_snapshot(&snapshot); + } + } + Ok(()) +} + +fn print_snapshot(snapshot: &Snapshot) { + let time_fmt = format_description!("[year]-[month]-[day] [hour]:[minute]:[second] UTC"); + + eprintln!( + "{}: {}, taken at {}", + SnapshotId(snapshot.id.clone()), + HumanBytes(snapshot.total_size), + OffsetDateTime::from_unix_timestamp_nanos(snapshot.taken_at_timestamp as i128) + .unwrap() + .format(time_fmt) + .unwrap() + ); +} diff --git a/crates/icp-cli/src/commands/canister/snapshot/load.rs b/crates/icp-cli/src/commands/canister/snapshot/load.rs new file mode 100644 index 00000000..905805e7 --- /dev/null +++ b/crates/icp-cli/src/commands/canister/snapshot/load.rs @@ -0,0 +1,61 @@ +use clap::Args; +use ic_management_canister_types::LoadCanisterSnapshotArgs; + +use crate::commands::{ + args, + canister::snapshot::{SnapshotId, ensure_canister_stopped}, +}; +use icp::context::Context; + +#[derive(Debug, Args)] +pub struct LoadArgs { + #[command(flatten)] + pub(crate) cmd_args: args::CanisterCommandArgs, + + /// The ID of the snapshot to load. + snapshot: SnapshotId, +} + +pub async fn exec(ctx: &Context, args: &LoadArgs) -> Result<(), anyhow::Error> { + let selections = args.cmd_args.selections(); + + let agent = ctx + .get_agent( + &selections.identity, + &selections.network, + &selections.environment, + ) + .await?; + let cid = ctx + .get_canister_id( + &selections.canister, + &selections.network, + &selections.environment, + ) + .await?; + + // Management Interface + let mgmt = ic_utils::interfaces::ManagementCanister::create(&agent); + + // Ensure canister is stopped + let (status,) = mgmt.canister_status(&cid).await?; + ensure_canister_stopped(status.status, &args.cmd_args.canister.to_string())?; + + // Load snapshot + mgmt.load_canister_snapshot( + &cid, + &LoadCanisterSnapshotArgs { + canister_id: cid, + snapshot_id: args.snapshot.0.clone(), + sender_canister_version: None, + }, + ) + .await?; + + eprintln!( + "Loaded snapshot {} into canister '{}'", + args.snapshot, args.cmd_args.canister, + ); + + Ok(()) +} diff --git a/crates/icp-cli/src/commands/canister/snapshot/mod.rs b/crates/icp-cli/src/commands/canister/snapshot/mod.rs new file mode 100644 index 00000000..d661dc3c --- /dev/null +++ b/crates/icp-cli/src/commands/canister/snapshot/mod.rs @@ -0,0 +1,78 @@ +use clap::Subcommand; +use ic_management_canister_types::{CanisterStatusType, UploadCanisterSnapshotMetadataResult}; +use icp::prelude::PathBuf; +use serde::{Deserialize, Serialize}; +use std::{ + fmt::{self, Display, Formatter}, + str::FromStr, +}; + +pub(crate) mod create; +pub(crate) mod delete; +pub(crate) mod download; +pub(crate) mod list; +pub(crate) mod load; +pub(crate) mod upload; + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct SnapshotId(Vec); + +impl Display for SnapshotId { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(&hex::encode(&self.0)) + } +} + +impl FromStr for SnapshotId { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result { + Ok(Self(hex::decode(s)?)) + } +} + +impl From for SnapshotId { + fn from(canister_snapshot_id: UploadCanisterSnapshotMetadataResult) -> Self { + SnapshotId(canister_snapshot_id.snapshot_id) + } +} + +fn ensure_canister_stopped(status: CanisterStatusType, canister: &str) -> Result<(), CommandError> { + match status { + CanisterStatusType::Stopped => Ok(()), + CanisterStatusType::Running => Err(CommandError::CanisterNotStopped(format!( + "Canister {canister} is running. Run 'icp canister stop' to stop it first" + ))), + CanisterStatusType::Stopping => Err(CommandError::CanisterNotStopped(format!( + "Canister {canister} is stopping but is not yet stopped. Wait a few seconds and try again" + ))), + } +} + +fn directory_parser(path: &str) -> Result { + let path = PathBuf::from(path); + if path.is_dir() { + Ok(path) + } else { + Err(format!( + "Path '{}' does not exist or is not a directory.", + path + )) + } +} + +#[derive(Subcommand, Debug)] +#[allow(clippy::large_enum_variant)] +pub enum Command { + Create(create::CreateArgs), + Delete(delete::DeleteArgs), + Download(download::DownloadArgs), + List(list::ListArgs), + Load(load::LoadArgs), + Upload(upload::UploadArgs), +} + +#[derive(Debug, thiserror::Error)] +pub enum CommandError { + #[error("{0}")] + CanisterNotStopped(String), +} diff --git a/crates/icp-cli/src/commands/canister/snapshot/upload.rs b/crates/icp-cli/src/commands/canister/snapshot/upload.rs new file mode 100644 index 00000000..336f0444 --- /dev/null +++ b/crates/icp-cli/src/commands/canister/snapshot/upload.rs @@ -0,0 +1,32 @@ +use clap::Args; +use icp::prelude::PathBuf; + +use crate::commands::args; +use crate::commands::canister::snapshot::{SnapshotId, directory_parser}; +use icp::context::Context; + +#[derive(Debug, Args)] +pub struct UploadArgs { + #[command(flatten)] + pub(crate) cmd_args: args::CanisterCommandArgs, + + /// If a snapshot ID is specified, this snapshot will replace it and reuse the ID. + #[arg(long)] + replace: Option, + + /// The directory to upload the snapshot from. + #[arg(long, value_parser = directory_parser)] + dir: PathBuf, + + /// The snapshot ID to resume uploading to. + #[arg(short, long)] + resume: Option, + + /// The number of concurrent uploads to perform. + #[arg(long, default_value = "3")] + concurrency: usize, +} + +pub async fn exec(_ctx: &Context, _args: &UploadArgs) -> Result<(), anyhow::Error> { + unimplemented!("canister snapshot upload is not yet implemented"); +} diff --git a/crates/icp-cli/src/main.rs b/crates/icp-cli/src/main.rs index 813d2c2c..762db5e8 100644 --- a/crates/icp-cli/src/main.rs +++ b/crates/icp-cli/src/main.rs @@ -196,6 +196,44 @@ async fn main() -> Result<(), Error> { .await? } + commands::canister::Command::Snapshot(cmd) => match cmd { + commands::canister::snapshot::Command::Create(args) => { + commands::canister::snapshot::create::exec(&ctx, &args) + .instrument(trace_span) + .await? + } + + commands::canister::snapshot::Command::Delete(args) => { + commands::canister::snapshot::delete::exec(&ctx, &args) + .instrument(trace_span) + .await? + } + + commands::canister::snapshot::Command::Download(args) => { + commands::canister::snapshot::download::exec(&ctx, &args) + .instrument(trace_span) + .await? + } + + commands::canister::snapshot::Command::List(args) => { + commands::canister::snapshot::list::exec(&ctx, &args) + .instrument(trace_span) + .await? + } + + commands::canister::snapshot::Command::Load(args) => { + commands::canister::snapshot::load::exec(&ctx, &args) + .instrument(trace_span) + .await? + } + + commands::canister::snapshot::Command::Upload(args) => { + commands::canister::snapshot::upload::exec(&ctx, &args) + .instrument(trace_span) + .await? + } + }, + commands::canister::Command::Start(args) => { commands::canister::start::exec(&ctx, &args) .instrument(trace_span) diff --git a/crates/icp-cli/tests/canister_snapshot_tests.rs b/crates/icp-cli/tests/canister_snapshot_tests.rs new file mode 100644 index 00000000..3a189c11 --- /dev/null +++ b/crates/icp-cli/tests/canister_snapshot_tests.rs @@ -0,0 +1,220 @@ +use indoc::formatdoc; +use predicates::{ + prelude::PredicateBooleanExt, + str::{contains, starts_with}, +}; +use regex::Regex; + +use crate::common::{ENVIRONMENT_RANDOM_PORT, NETWORK_RANDOM_PORT, TestContext, clients}; +use icp::{fs::write_string, prelude::*}; + +mod common; + +#[test] +fn canister_snapshot() { + let ctx = TestContext::new(); + + // Setup project + let project_dir = ctx.create_project_dir("icp"); + + // Use vendored WASM + let wasm = ctx.make_asset("example_icp_mo.wasm"); + + // Project manifest + let pm = formatdoc! {r#" + canister: + name: my-canister + build: + steps: + - type: script + command: cp {wasm} "$ICP_WASM_OUTPUT_PATH" + + {NETWORK_RANDOM_PORT} + {ENVIRONMENT_RANDOM_PORT} + "#}; + + write_string( + &project_dir.join("icp.yaml"), // path + &pm, // contents + ) + .expect("failed to write project manifest"); + + // Start network + let _g = ctx.start_network_in(&project_dir, "my-network"); + ctx.ping_until_healthy(&project_dir, "my-network"); + + // Deploy project + clients::icp(&ctx, &project_dir, Some("my-environment".to_string())).mint_cycles(10 * TRILLION); + + ctx.icp() + .current_dir(&project_dir) + .args([ + "deploy", + "--subnet-id", + common::SUBNET_ID, + "--environment", + "my-environment", + ]) + .assert() + .success(); + + // Query status + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "status", + "my-canister", + "--environment", + "my-environment", + ]) + .assert() + .success() + .stderr(starts_with("Canister Status Report:").and(contains("Status: Running"))); + + // List canister snapshots + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "snapshot", + "list", + "my-canister", + "--environment", + "my-environment", + ]) + .assert() + .success() + .stderr(starts_with("No snapshots found")); + + // Failed to create canister snapshot as the canister is running. + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "snapshot", + "create", + "my-canister", + "--environment", + "my-environment", + ]) + .assert() + .failure() + .stderr(starts_with("Error: Canister my-canister is running.")); + + // Failed to load canister snapshot as the canister is running. + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "snapshot", + "load", + "my-canister", + "0000000000000003ffffffffffc000000101", // A faked snapshot id. + "--environment", + "my-environment", + ]) + .assert() + .failure() + .stderr(starts_with("Error: Canister my-canister is running.")); + + // Stop canister. + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "stop", + "my-canister", + "--environment", + "my-environment", + ]) + .assert() + .success(); + + // Create canister snapshot and parse the snapshot ID. + let result = ctx + .icp() + .current_dir(&project_dir) + .args([ + "canister", + "snapshot", + "create", + "my-canister", + "--environment", + "my-environment", + ]) + .assert() + .success(); + + let result_str = std::str::from_utf8(&result.get_output().stderr).unwrap(); + assert!(result_str.starts_with("Created a new snapshot of canister")); + + let re = Regex::new(r"Snapshot ID: '([0-9a-fA-F]+)'").unwrap(); + let caps = re + .captures(result_str) + .expect("snapshot id not found in stderr"); + let snapshot_id = &caps[1]; + assert!(!snapshot_id.is_empty(), "snapshot id is empty"); + + // List canister snapshots + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "snapshot", + "list", + "my-canister", + "--environment", + "my-environment", + ]) + .assert() + .success() + .stderr(contains(snapshot_id)); + + // Load canister snapshot + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "snapshot", + "load", + "my-canister", + snapshot_id, + "--environment", + "my-environment", + ]) + .assert() + .success() + .stderr(starts_with(format!("Loaded snapshot {}", snapshot_id))); + + // Delete canister snapshot + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "snapshot", + "delete", + "my-canister", + snapshot_id, + "--environment", + "my-environment", + ]) + .assert() + .success() + .stderr(starts_with(format!("Deleted snapshot {}", snapshot_id))); + + // List canister snapshots + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "snapshot", + "list", + "my-canister", + "--environment", + "my-environment", + ]) + .assert() + .success() + .stderr(starts_with("No snapshots found")); +} diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 8416bc9a..f010b5fb 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -18,6 +18,13 @@ This document contains the help content for the `icp-cli` command-line program. * [`icp-cli canister settings update`↴](#icp-cli-canister-settings-update) * [`icp-cli canister settings sync`↴](#icp-cli-canister-settings-sync) * [`icp-cli canister show`↴](#icp-cli-canister-show) +* [`icp-cli canister snapshot`↴](#icp-cli-canister-snapshot) +* [`icp-cli canister snapshot create`↴](#icp-cli-canister-snapshot-create) +* [`icp-cli canister snapshot delete`↴](#icp-cli-canister-snapshot-delete) +* [`icp-cli canister snapshot download`↴](#icp-cli-canister-snapshot-download) +* [`icp-cli canister snapshot list`↴](#icp-cli-canister-snapshot-list) +* [`icp-cli canister snapshot load`↴](#icp-cli-canister-snapshot-load) +* [`icp-cli canister snapshot upload`↴](#icp-cli-canister-snapshot-upload) * [`icp-cli canister start`↴](#icp-cli-canister-start) * [`icp-cli canister status`↴](#icp-cli-canister-status) * [`icp-cli canister stop`↴](#icp-cli-canister-stop) @@ -102,6 +109,7 @@ Perform canister operations against a network * `list` — List the canisters in an environment * `settings` — Commands to manage canister settings * `show` — Show a canister's details +* `snapshot` — Manipulate canister snapshots * `start` — Start a canister on a network * `status` — Show the status of a canister * `stop` — Stop a canister on a network @@ -347,6 +355,148 @@ Show a canister's details +## `icp-cli canister snapshot` + +Manipulate canister snapshots + +**Usage:** `icp-cli canister snapshot ` + +###### **Subcommands:** + +* `create` — +* `delete` — +* `download` — +* `list` — +* `load` — +* `upload` — + + + +## `icp-cli canister snapshot create` + +**Usage:** `icp-cli canister snapshot create [OPTIONS] ` + +###### **Arguments:** + +* `` — Name or principal of canister to target When using a name an environment must be specified + +###### **Options:** + +* `--network ` — Name of the network to target, conflicts with environment argument +* `--mainnet` — Shorthand for --network=mainnet +* `--environment ` — Override the environment to connect to. By default, the local environment is used +* `--ic` — Shorthand for --environment=ic +* `--identity ` — The user identity to run this command as +* `--replace ` — If a snapshot ID is specified, this snapshot will replace it and reuse the ID + + + +## `icp-cli canister snapshot delete` + +**Usage:** `icp-cli canister snapshot delete [OPTIONS] ` + +###### **Arguments:** + +* `` — Name or principal of canister to target When using a name an environment must be specified +* `` — The ID of the snapshot to delete + +###### **Options:** + +* `--network ` — Name of the network to target, conflicts with environment argument +* `--mainnet` — Shorthand for --network=mainnet +* `--environment ` — Override the environment to connect to. By default, the local environment is used +* `--ic` — Shorthand for --environment=ic +* `--identity ` — The user identity to run this command as + + + +## `icp-cli canister snapshot download` + +**Usage:** `icp-cli canister snapshot download [OPTIONS] --dir ` + +###### **Arguments:** + +* `` — Name or principal of canister to target When using a name an environment must be specified +* `` — The ID of the snapshot to download + +###### **Options:** + +* `--network ` — Name of the network to target, conflicts with environment argument +* `--mainnet` — Shorthand for --network=mainnet +* `--environment ` — Override the environment to connect to. By default, the local environment is used +* `--ic` — Shorthand for --environment=ic +* `--identity ` — The user identity to run this command as +* `--dir ` — The directory to download the snapshot to +* `-r`, `--resume` — Whether to resume the download if the previous snapshot download failed + + Default value: `false` +* `--concurrency ` — The number of concurrent downloads to perform + + Default value: `3` + + + +## `icp-cli canister snapshot list` + +**Usage:** `icp-cli canister snapshot list [OPTIONS] ` + +###### **Arguments:** + +* `` — Name or principal of canister to target When using a name an environment must be specified + +###### **Options:** + +* `--network ` — Name of the network to target, conflicts with environment argument +* `--mainnet` — Shorthand for --network=mainnet +* `--environment ` — Override the environment to connect to. By default, the local environment is used +* `--ic` — Shorthand for --environment=ic +* `--identity ` — The user identity to run this command as + + + +## `icp-cli canister snapshot load` + +**Usage:** `icp-cli canister snapshot load [OPTIONS] ` + +###### **Arguments:** + +* `` — Name or principal of canister to target When using a name an environment must be specified +* `` — The ID of the snapshot to load + +###### **Options:** + +* `--network ` — Name of the network to target, conflicts with environment argument +* `--mainnet` — Shorthand for --network=mainnet +* `--environment ` — Override the environment to connect to. By default, the local environment is used +* `--ic` — Shorthand for --environment=ic +* `--identity ` — The user identity to run this command as + + + +## `icp-cli canister snapshot upload` + +**Usage:** `icp-cli canister snapshot upload [OPTIONS] --dir ` + +###### **Arguments:** + +* `` — Name or principal of canister to target When using a name an environment must be specified + +###### **Options:** + +* `--network ` — Name of the network to target, conflicts with environment argument +* `--mainnet` — Shorthand for --network=mainnet +* `--environment ` — Override the environment to connect to. By default, the local environment is used +* `--ic` — Shorthand for --environment=ic +* `--identity ` — The user identity to run this command as +* `--replace ` — If a snapshot ID is specified, this snapshot will replace it and reuse the ID +* `--dir ` — The directory to upload the snapshot from +* `-r`, `--resume ` — The snapshot ID to resume uploading to +* `--concurrency ` — The number of concurrent uploads to perform + + Default value: `3` + + + ## `icp-cli canister start` Start a canister on a network