diff --git a/crates/icp-cli/Cargo.toml b/crates/icp-cli/Cargo.toml index 9ed75ef7a..6540f1f41 100644 --- a/crates/icp-cli/Cargo.toml +++ b/crates/icp-cli/Cargo.toml @@ -53,6 +53,7 @@ tiny-bip39.workspace = true tokio.workspace = true tracing-subscriber.workspace = true tracing.workspace = true +url.workspace = true [dev-dependencies] assert_cmd = "2" @@ -65,7 +66,6 @@ predicates = "3" rand.workspace = true serde_yaml.workspace = true serial_test = { version = "3.2.0", features = ["file_locks"] } -url.workspace = true uuid.workspace = true [lints] diff --git a/crates/icp-cli/src/commands/args.rs b/crates/icp-cli/src/commands/args.rs index c3cc0f4b7..8719489a8 100644 --- a/crates/icp-cli/src/commands/args.rs +++ b/crates/icp-cli/src/commands/args.rs @@ -5,7 +5,7 @@ use clap::Args; use icp::context::{CanisterSelection, EnvironmentSelection, NetworkSelection}; use icp::identity::IdentitySelection; -use crate::options::IdentityOpt; +use crate::options::{EnvironmentOpt, IdentityOpt, NetworkOpt}; #[derive(Args, Debug)] pub(crate) struct CanisterEnvironmentArgs { @@ -13,17 +13,15 @@ pub(crate) struct CanisterEnvironmentArgs { /// When using a name an environment must be specified pub(crate) canister: Canister, - /// Name of the target environment - #[arg(long)] - pub(crate) environment: Option, + #[command(flatten)] + pub(crate) environment: EnvironmentOpt, } impl CanisterEnvironmentArgs { /// Convert arguments into selection enums for canister and environment pub(crate) fn selections(&self) -> (CanisterSelection, EnvironmentSelection) { let canister_selection: CanisterSelection = self.canister.clone().into(); - let environment_selection: EnvironmentSelection = - self.environment.clone().unwrap_or_default().into(); + let environment_selection: EnvironmentSelection = self.environment.clone().into(); (canister_selection, environment_selection) } } @@ -35,15 +33,12 @@ pub(crate) struct CanisterCommandArgs { /// When using a name an environment must be specified pub(crate) canister: Canister, - /// Name of the network to target, conflicts with environment argument - #[arg(long, conflicts_with = "environment")] - pub(crate) network: Option, + #[command(flatten)] + pub(crate) network: NetworkOpt, - /// Name of the target environment - #[arg(long)] - pub(crate) environment: Option, + #[command(flatten)] + pub(crate) environment: EnvironmentOpt, - /// The identity to use for this request #[command(flatten)] pub(crate) identity: IdentityOpt, } @@ -60,12 +55,8 @@ impl CanisterCommandArgs { /// Convert command arguments into selection enums pub(crate) fn selections(&self) -> CommandSelections { let canister_selection: CanisterSelection = self.canister.clone().into(); - let environment_selection: EnvironmentSelection = - self.environment.clone().unwrap_or_default().into(); - let network_selection: NetworkSelection = match self.network.clone() { - Some(network) => network.into_selection(), - None => NetworkSelection::FromEnvironment, - }; + let environment_selection: EnvironmentSelection = self.environment.clone().into(); + let network_selection: NetworkSelection = self.network.clone().into(); let identity_selection: IdentitySelection = self.identity.clone().into(); CommandSelections { @@ -111,73 +102,6 @@ impl Display for Canister { } } -#[derive(Clone, Debug, PartialEq)] -pub(crate) enum Network { - Name(String), - Url(String), -} - -impl From<&str> for Network { - fn from(v: &str) -> Self { - if v.starts_with("http://") || v.starts_with("https://") { - return Self::Url(v.to_string()); - } - - Self::Name(v.to_string()) - } -} - -impl Network { - pub(crate) fn into_selection(self) -> NetworkSelection { - match self { - Network::Name(name) => NetworkSelection::Named(name), - Network::Url(url) => NetworkSelection::Url(url), - } - } -} - -#[derive(Clone, Debug, PartialEq)] -pub(crate) enum Environment { - Name(String), - Default(String), -} - -impl Environment { - pub(crate) fn name(&self) -> &str { - match self { - Environment::Name(name) => name, - Environment::Default(name) => name, - } - } -} - -impl Default for Environment { - fn default() -> Self { - Self::Default("local".to_string()) - } -} - -impl From<&str> for Environment { - fn from(v: &str) -> Self { - Self::Name(v.to_string()) - } -} - -impl From for EnvironmentSelection { - fn from(v: Environment) -> Self { - match v { - Environment::Name(name) => EnvironmentSelection::Named(name), - Environment::Default(_) => EnvironmentSelection::Default, - } - } -} - -impl Display for Environment { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.name()) - } -} - #[cfg(test)] mod tests { use candid::Principal; @@ -201,22 +125,4 @@ mod tests { Canister::Principal(Principal::from_text(cid).expect("failed to parse principal")), ); } - - #[test] - fn network_by_name() { - assert_eq!( - Network::from("my-network"), - Network::Name("my-network".to_string()), - ); - } - - #[test] - fn network_by_url_http() { - let url = "http://www.example.com"; - - assert_eq!( - Network::from(url), - Network::Url("http://www.example.com".to_string()), - ); - } } diff --git a/crates/icp-cli/src/commands/canister/binding_env_vars.rs b/crates/icp-cli/src/commands/canister/binding_env_vars.rs index c39224952..b50a97ba0 100644 --- a/crates/icp-cli/src/commands/canister/binding_env_vars.rs +++ b/crates/icp-cli/src/commands/canister/binding_env_vars.rs @@ -10,7 +10,9 @@ use ic_agent::AgentError; use ic_utils::interfaces::management_canister::builders::EnvironmentVariable; use icp::{ agent, - context::{GetAgentForEnvError, GetCanisterIdForEnvError, GetEnvironmentError}, + context::{ + EnvironmentSelection, GetAgentForEnvError, GetCanisterIdForEnvError, GetEnvironmentError, + }, identity, network, }; use tracing::debug; @@ -48,7 +50,7 @@ pub(crate) enum CommandError { Access(#[from] network::AccessError), #[error(transparent)] - Agent(#[from] agent::CreateError), + Agent(#[from] agent::CreateAgentError), #[error("Could not find canister id(s) for '{}' in environment '{environment}' make sure they are created first", canister_names.join(", "))] CanisterNotCreated { @@ -76,12 +78,14 @@ pub(crate) enum CommandError { } pub(crate) async fn exec(ctx: &Context, args: &BindingArgs) -> Result<(), CommandError> { + let environment_selection: EnvironmentSelection = args.environment.clone().into(); + // Load target environment - let env = ctx.get_environment(args.environment.name()).await?; + let env = ctx.get_environment(&environment_selection).await?; // Agent let agent = ctx - .get_agent_for_env(&args.identity.clone().into(), args.environment.name()) + .get_agent_for_env(&args.identity.clone().into(), &environment_selection) .await?; let target_canisters = match args.names.is_empty() { @@ -91,9 +95,11 @@ pub(crate) async fn exec(ctx: &Context, args: &BindingArgs) -> Result<(), Comman let env_canisters = &env.canisters; let canisters = try_join_all(target_canisters.into_iter().map(|name| { - let env_name = args.environment.name(); + let environment_selection = environment_selection.clone(); async move { - let cid = ctx.get_canister_id_for_env(&name, env_name).await?; + let cid = ctx + .get_canister_id_for_env(&name, &environment_selection) + .await?; let (_, info) = env_canisters .get(&name) .expect("Canister id exists but no canister info"); diff --git a/crates/icp-cli/src/commands/canister/create.rs b/crates/icp-cli/src/commands/canister/create.rs index 2ab0264f4..6655b8a8a 100644 --- a/crates/icp-cli/src/commands/canister/create.rs +++ b/crates/icp-cli/src/commands/canister/create.rs @@ -7,7 +7,7 @@ use futures::{StreamExt, stream::FuturesOrdered}; use ic_agent::{Agent, AgentError, export::Principal}; use icp::{ agent, - context::{GetAgentForEnvError, GetEnvironmentError}, + context::{EnvironmentSelection, GetAgentForEnvError, GetEnvironmentError}, identity, network, prelude::*, }; @@ -94,7 +94,7 @@ pub(crate) enum CommandError { Access(#[from] network::AccessError), #[error(transparent)] - Agent(#[from] agent::CreateError), + Agent(#[from] agent::CreateAgentError), #[error("project does not contain a canister named '{name}'")] CanisterNotFound { name: String }, @@ -137,11 +137,13 @@ pub(crate) enum CommandError { // The cycles ledger will take cycles out of the user's account, and attaches them to a call to CMC::create_canister. // The CMC will then pick a subnet according to the user's preferences and permissions, and create a canister on that subnet. pub(crate) async fn exec(ctx: &Context, args: &CreateArgs) -> Result<(), CommandError> { + let environment_selection: EnvironmentSelection = args.environment.clone().into(); + // Load project let p = ctx.project.load().await?; // Load target environment - let env = ctx.get_environment(args.environment.name()).await?; + let env = ctx.get_environment(&environment_selection).await?; let target_canisters = match args.names.is_empty() { true => env.get_canister_names(), @@ -185,7 +187,7 @@ pub(crate) async fn exec(ctx: &Context, args: &CreateArgs) -> Result<(), Command // Agent let agent = ctx - .get_agent_for_env(&args.identity.clone().into(), args.environment.name()) + .get_agent_for_env(&args.identity.clone().into(), &environment_selection) .await?; // Select which subnet to deploy the canisters to diff --git a/crates/icp-cli/src/commands/canister/info.rs b/crates/icp-cli/src/commands/canister/info.rs index 1b55d5c6f..478575073 100644 --- a/crates/icp-cli/src/commands/canister/info.rs +++ b/crates/icp-cli/src/commands/canister/info.rs @@ -27,7 +27,7 @@ pub(crate) enum CommandError { Access(#[from] network::AccessError), #[error(transparent)] - Agent(#[from] agent::CreateError), + Agent(#[from] agent::CreateAgentError), #[error(transparent)] Lookup(#[from] LookupIdError), diff --git a/crates/icp-cli/src/commands/canister/install.rs b/crates/icp-cli/src/commands/canister/install.rs index 6ec85a9c9..0d31db573 100644 --- a/crates/icp-cli/src/commands/canister/install.rs +++ b/crates/icp-cli/src/commands/canister/install.rs @@ -4,7 +4,9 @@ use ic_agent::AgentError; use ic_utils::interfaces::management_canister::builders::CanisterInstallMode; use icp::{ agent, - context::{GetAgentForEnvError, GetCanisterIdForEnvError, GetEnvironmentError}, + context::{ + EnvironmentSelection, GetAgentForEnvError, GetCanisterIdForEnvError, GetEnvironmentError, + }, identity, network, }; use tracing::debug; @@ -46,7 +48,7 @@ pub(crate) enum CommandError { Access(#[from] network::AccessError), #[error(transparent)] - Agent(#[from] agent::CreateError), + Agent(#[from] agent::CreateAgentError), #[error(transparent)] LookupCanisterId(#[from] LookupIdError), @@ -68,12 +70,14 @@ pub(crate) enum CommandError { } pub(crate) async fn exec(ctx: &Context, args: &InstallArgs) -> Result<(), CommandError> { + let environment_selection: EnvironmentSelection = args.environment.clone().into(); + // Load target environment - let env = ctx.get_environment(args.environment.name()).await?; + let env = ctx.get_environment(&environment_selection).await?; // Agent let agent = ctx - .get_agent_for_env(&args.identity.clone().into(), args.environment.name()) + .get_agent_for_env(&args.identity.clone().into(), &environment_selection) .await?; let target_canisters = match args.names.is_empty() { @@ -82,9 +86,11 @@ pub(crate) async fn exec(ctx: &Context, args: &InstallArgs) -> Result<(), Comman }; let canisters = try_join_all(target_canisters.into_iter().map(|name| { - let env_name = args.environment.name(); + let environment_selection = environment_selection.clone(); async move { - let cid = ctx.get_canister_id_for_env(&name, env_name).await?; + let cid = ctx + .get_canister_id_for_env(&name, &environment_selection) + .await?; Ok::<_, CommandError>((name, cid)) } })) diff --git a/crates/icp-cli/src/commands/canister/settings/show.rs b/crates/icp-cli/src/commands/canister/settings/show.rs index 5e204b263..bbeb8c8ee 100644 --- a/crates/icp-cli/src/commands/canister/settings/show.rs +++ b/crates/icp-cli/src/commands/canister/settings/show.rs @@ -26,7 +26,7 @@ pub(crate) enum CommandError { Access(#[from] network::AccessError), #[error(transparent)] - Agent(#[from] agent::CreateError), + Agent(#[from] agent::CreateAgentError), #[error(transparent)] Lookup(#[from] LookupIdError), diff --git a/crates/icp-cli/src/commands/canister/settings/update.rs b/crates/icp-cli/src/commands/canister/settings/update.rs index 416948778..74e8dea1c 100644 --- a/crates/icp-cli/src/commands/canister/settings/update.rs +++ b/crates/icp-cli/src/commands/canister/settings/update.rs @@ -116,7 +116,7 @@ pub(crate) enum CommandError { Access(#[from] network::AccessError), #[error(transparent)] - Agent(#[from] agent::CreateError), + Agent(#[from] agent::CreateAgentError), #[error("invalid environment variable '{variable}'")] InvalidEnvironmentVariable { variable: String }, diff --git a/crates/icp-cli/src/commands/canister/start.rs b/crates/icp-cli/src/commands/canister/start.rs index 350332bf5..e4c48f12f 100644 --- a/crates/icp-cli/src/commands/canister/start.rs +++ b/crates/icp-cli/src/commands/canister/start.rs @@ -25,7 +25,7 @@ pub(crate) enum CommandError { Access(#[from] network::AccessError), #[error(transparent)] - Agent(#[from] agent::CreateError), + Agent(#[from] agent::CreateAgentError), #[error(transparent)] LookupCanisterId(#[from] LookupIdError), diff --git a/crates/icp-cli/src/commands/canister/stop.rs b/crates/icp-cli/src/commands/canister/stop.rs index 1dbed5ef6..7ea40b570 100644 --- a/crates/icp-cli/src/commands/canister/stop.rs +++ b/crates/icp-cli/src/commands/canister/stop.rs @@ -25,7 +25,7 @@ pub(crate) enum CommandError { Access(#[from] network::AccessError), #[error(transparent)] - Agent(#[from] agent::CreateError), + Agent(#[from] agent::CreateAgentError), #[error(transparent)] LookupCanisterId(#[from] LookupIdError), diff --git a/crates/icp-cli/src/commands/canister/top_up.rs b/crates/icp-cli/src/commands/canister/top_up.rs index efbde2224..8e4c042f9 100644 --- a/crates/icp-cli/src/commands/canister/top_up.rs +++ b/crates/icp-cli/src/commands/canister/top_up.rs @@ -34,7 +34,7 @@ pub(crate) enum CommandError { Access(#[from] network::AccessError), #[error(transparent)] - Agent(#[from] agent::CreateError), + Agent(#[from] agent::CreateAgentError), #[error(transparent)] Lookup(#[from] LookupIdError), diff --git a/crates/icp-cli/src/commands/cycles/mint.rs b/crates/icp-cli/src/commands/cycles/mint.rs index 44ad44f86..bbbcb2255 100644 --- a/crates/icp-cli/src/commands/cycles/mint.rs +++ b/crates/icp-cli/src/commands/cycles/mint.rs @@ -5,7 +5,11 @@ use ic_agent::AgentError; use ic_ledger_types::{ AccountIdentifier, Memo, Subaccount, Tokens, TransferArgs, TransferError, TransferResult, }; -use icp::{agent, context::GetAgentForEnvError, identity, network}; +use icp::{ + agent, + context::{EnvironmentSelection, GetAgentForEnvError}, + identity, network, +}; use icp_canister_interfaces::{ cycles_ledger::CYCLES_LEDGER_BLOCK_FEE, cycles_minting_canister::{ @@ -48,7 +52,7 @@ pub(crate) enum CommandError { Access(#[from] network::AccessError), #[error(transparent)] - Agent(#[from] agent::CreateError), + Agent(#[from] agent::CreateAgentError), #[error("Failed to get identity principal: {message}")] Principal { message: String }, @@ -82,9 +86,11 @@ pub(crate) enum CommandError { } pub(crate) async fn exec(ctx: &Context, args: &MintArgs) -> Result<(), CommandError> { + let environment_selection: EnvironmentSelection = args.environment.clone().into(); + // Agent let agent = ctx - .get_agent_for_env(&args.identity.clone().into(), args.environment.name()) + .get_agent_for_env(&args.identity.clone().into(), &environment_selection) .await?; // Prepare deposit diff --git a/crates/icp-cli/src/commands/network/ping.rs b/crates/icp-cli/src/commands/network/ping.rs index 087858d6d..a0fc4f922 100644 --- a/crates/icp-cli/src/commands/network/ping.rs +++ b/crates/icp-cli/src/commands/network/ping.rs @@ -7,6 +7,7 @@ use icp::{ context::GetAgentForUrlError, identity::{self, IdentitySelection}, network::{self}, + project::DEFAULT_LOCAL_NETWORK_NAME, }; use tokio::time::sleep; @@ -16,7 +17,7 @@ use icp::context::Context; #[derive(Args, Debug)] pub(crate) struct PingArgs { /// The compute network to connect to. By default, ping the local network. - #[arg(value_name = "NETWORK", default_value = "local")] + #[arg(value_name = "NETWORK", default_value = DEFAULT_LOCAL_NETWORK_NAME)] network: String, /// Repeatedly ping until the replica is healthy or 1 minute has passed. @@ -39,7 +40,7 @@ pub(crate) enum CommandError { NetworkAccess(#[from] network::AccessError), #[error(transparent)] - Agent(#[from] agent::CreateError), + Agent(#[from] agent::CreateAgentError), #[error(transparent)] Status(#[from] AgentError), diff --git a/crates/icp-cli/src/commands/network/run.rs b/crates/icp-cli/src/commands/network/run.rs index a827feb46..aa6f2bf92 100644 --- a/crates/icp-cli/src/commands/network/run.rs +++ b/crates/icp-cli/src/commands/network/run.rs @@ -15,6 +15,7 @@ use icp::{ identity::manifest::{LoadIdentityManifestError, load_identity_list}, manifest, network::{Configuration, NetworkDirectory, RunNetworkError, run_network}, + project::DEFAULT_LOCAL_NETWORK_NAME, }; use sysinfo::Pid; use tracing::debug; @@ -25,7 +26,7 @@ use icp::context::Context; #[derive(Args, Debug)] pub(crate) struct RunArgs { /// Name of the network to run - #[arg(default_value = "local")] + #[arg(default_value = DEFAULT_LOCAL_NETWORK_NAME)] name: String, /// Starts the network in a background process. This command will exit once the network is running. diff --git a/crates/icp-cli/src/commands/network/stop.rs b/crates/icp-cli/src/commands/network/stop.rs index 53138a510..b730dc80b 100644 --- a/crates/icp-cli/src/commands/network/stop.rs +++ b/crates/icp-cli/src/commands/network/stop.rs @@ -1,7 +1,9 @@ use std::time::Duration; use clap::Parser; -use icp::{fs::remove_file, manifest, network::NetworkDirectory}; +use icp::{ + fs::remove_file, manifest, network::NetworkDirectory, project::DEFAULT_LOCAL_NETWORK_NAME, +}; use sysinfo::{Pid, ProcessesToUpdate, Signal, System}; use icp::context::Context; @@ -12,7 +14,7 @@ const TIMEOUT_SECS: u64 = 30; #[derive(Parser, Debug)] pub struct Cmd { /// Name of the network to stop - #[arg(default_value = "local")] + #[arg(default_value = DEFAULT_LOCAL_NETWORK_NAME)] name: String, } diff --git a/crates/icp-cli/src/commands/sync/mod.rs b/crates/icp-cli/src/commands/sync/mod.rs index 2568b2c90..55085647e 100644 --- a/crates/icp-cli/src/commands/sync/mod.rs +++ b/crates/icp-cli/src/commands/sync/mod.rs @@ -6,7 +6,7 @@ use futures::{StreamExt, stream::FuturesOrdered}; use icp::{ agent, canister::sync::{Params, SynchronizeError}, - context::GetAgentForEnvError, + context::{EnvironmentSelection, GetAgentForEnvError, GetEnvironmentError}, identity, network, }; @@ -38,14 +38,11 @@ pub(crate) enum CommandError { #[error(transparent)] Identity(#[from] identity::LoadError), - #[error("project does not contain an environment named '{name}'")] - EnvironmentNotFound { name: String }, - #[error(transparent)] Access(#[from] network::AccessError), #[error(transparent)] - Agent(#[from] agent::CreateError), + Agent(#[from] agent::CreateAgentError), #[error("project does not contain a canister named '{name}'")] CanisterNotFound { name: String }, @@ -67,23 +64,23 @@ pub(crate) enum CommandError { #[error(transparent)] GetAgentForEnv(#[from] GetAgentForEnvError), + + #[error(transparent)] + GetEnvironment(#[from] GetEnvironmentError), } pub(crate) async fn exec(ctx: &Context, args: &SyncArgs) -> Result<(), CommandError> { + let environment_selection: EnvironmentSelection = args.environment.clone().into(); + // Load the project let p = ctx.project.load().await?; // Load target environment - let env = - p.environments - .get(args.environment.name()) - .ok_or(CommandError::EnvironmentNotFound { - name: args.environment.name().to_owned(), - })?; + let env = ctx.get_environment(&environment_selection).await?; // Agent let agent = ctx - .get_agent_for_env(&args.identity.clone().into(), args.environment.name()) + .get_agent_for_env(&args.identity.clone().into(), &environment_selection) .await?; let cnames = match args.names.is_empty() { diff --git a/crates/icp-cli/src/commands/token/balance.rs b/crates/icp-cli/src/commands/token/balance.rs index 505a36978..2f2a03740 100644 --- a/crates/icp-cli/src/commands/token/balance.rs +++ b/crates/icp-cli/src/commands/token/balance.rs @@ -2,7 +2,11 @@ use bigdecimal::BigDecimal; use candid::{Decode, Encode, Nat, Principal}; use clap::Args; use ic_agent::AgentError; -use icp::{agent, context::GetAgentForEnvError, identity, network}; +use icp::{ + agent, + context::{EnvironmentSelection, GetAgentForEnvError}, + identity, network, +}; use icrc_ledger_types::icrc1::account::Account; use icp::context::Context; @@ -33,7 +37,7 @@ pub(crate) enum CommandError { Access(#[from] network::AccessError), #[error(transparent)] - Agent(#[from] agent::CreateError), + Agent(#[from] agent::CreateAgentError), #[error("failed to get identity principal: {err}")] Principal { err: String }, @@ -58,9 +62,11 @@ pub(crate) async fn exec( token: &str, args: &BalanceArgs, ) -> Result<(), CommandError> { + let environment_selection: EnvironmentSelection = args.environment.clone().into(); + // Agent let agent = ctx - .get_agent_for_env(&args.identity.clone().into(), args.environment.name()) + .get_agent_for_env(&args.identity.clone().into(), &environment_selection) .await?; // Obtain ledger address diff --git a/crates/icp-cli/src/commands/token/transfer.rs b/crates/icp-cli/src/commands/token/transfer.rs index d949809bc..1d11e3db4 100644 --- a/crates/icp-cli/src/commands/token/transfer.rs +++ b/crates/icp-cli/src/commands/token/transfer.rs @@ -2,7 +2,11 @@ use bigdecimal::{BigDecimal, num_bigint::ToBigInt}; use candid::{Decode, Encode, Nat, Principal}; use clap::Args; use ic_agent::AgentError; -use icp::{agent, context::GetAgentForEnvError, identity, network}; +use icp::{ + agent, + context::{EnvironmentSelection, GetAgentForEnvError}, + identity, network, +}; use icrc_ledger_types::icrc1::{ account::Account, transfer::{TransferArg, TransferError}, @@ -42,7 +46,7 @@ pub(crate) enum CommandError { Access(#[from] network::AccessError), #[error(transparent)] - Agent(#[from] agent::CreateError), + Agent(#[from] agent::CreateAgentError), #[error("failed to get identity principal: {err}")] Principal { err: String }, @@ -75,9 +79,11 @@ pub(crate) async fn exec( token: &str, args: &TransferArgs, ) -> Result<(), CommandError> { + let environment_selection: EnvironmentSelection = args.environment.clone().into(); + // Agent let agent = ctx - .get_agent_for_env(&args.identity.clone().into(), args.environment.name()) + .get_agent_for_env(&args.identity.clone().into(), &environment_selection) .await?; // Obtain ledger address diff --git a/crates/icp-cli/src/options.rs b/crates/icp-cli/src/options.rs index 7140b0d46..6a9f1a56a 100644 --- a/crates/icp-cli/src/options.rs +++ b/crates/icp-cli/src/options.rs @@ -1,5 +1,8 @@ use clap::{ArgGroup, Args}; +use icp::context::{EnvironmentSelection, NetworkSelection}; use icp::identity::IdentitySelection; +use icp::project::{DEFAULT_LOCAL_ENVIRONMENT_NAME, DEFAULT_MAINNET_ENVIRONMENT_NAME}; +use url::Url; #[derive(Args, Clone, Debug, Default)] pub(crate) struct IdentityOpt { @@ -31,12 +34,18 @@ pub(crate) struct EnvironmentOpt { long, env = "ICP_ENVIRONMENT", global(true), - group = "environment-select" + group = "environment-select", + group = "network-select" )] environment: Option, /// Shorthand for --environment=ic. - #[arg(long, global(true), group = "environment-select")] + #[arg( + long, + global(true), + group = "environment-select", + group = "network-select" + )] ic: bool, } @@ -44,10 +53,44 @@ impl EnvironmentOpt { pub(crate) fn name(&self) -> &str { // Support --ic if self.ic { - return "ic"; + return DEFAULT_MAINNET_ENVIRONMENT_NAME; } // Otherwise, default to `local` - self.environment.as_deref().unwrap_or("local") + self.environment + .as_deref() + .unwrap_or(DEFAULT_LOCAL_ENVIRONMENT_NAME) + } +} + +impl From for EnvironmentSelection { + fn from(v: EnvironmentOpt) -> Self { + if v.ic { + return EnvironmentSelection::Named(DEFAULT_MAINNET_ENVIRONMENT_NAME.to_string()); + } + match v.environment { + Some(name) => EnvironmentSelection::Named(name), + None => EnvironmentSelection::Default, + } + } +} + +#[derive(Args, Clone, Debug, Default)] +#[clap(group(ArgGroup::new("network-select").multiple(false)))] +pub(crate) struct NetworkOpt { + /// Name of the network to target, conflicts with environment argument + #[arg(long, group = "network-select")] + network: Option, +} + +impl From for NetworkSelection { + fn from(v: NetworkOpt) -> Self { + match v.network { + Some(network) => match Url::parse(&network) { + Ok(url) => NetworkSelection::Url(url), + Err(_) => NetworkSelection::Named(network), + }, + None => NetworkSelection::Default, + } } } diff --git a/crates/icp-cli/tests/common/clients/icp_cli.rs b/crates/icp-cli/tests/common/clients/icp_cli.rs index 5a2986551..c46c8ddc6 100644 --- a/crates/icp-cli/tests/common/clients/icp_cli.rs +++ b/crates/icp-cli/tests/common/clients/icp_cli.rs @@ -1,5 +1,5 @@ use candid::Principal; -use icp::prelude::*; +use icp::{prelude::*, project::DEFAULT_LOCAL_ENVIRONMENT_NAME}; use crate::common::TestContext; @@ -18,7 +18,7 @@ impl<'a> Client<'a> { Self { ctx, current_dir, - environment: environment.unwrap_or("local".to_string()), + environment: environment.unwrap_or(DEFAULT_LOCAL_ENVIRONMENT_NAME.to_string()), } } diff --git a/crates/icp/src/agent.rs b/crates/icp/src/agent.rs index c7d755fc9..cfb361e89 100644 --- a/crates/icp/src/agent.rs +++ b/crates/icp/src/agent.rs @@ -6,7 +6,7 @@ use ic_agent::{Agent, AgentError, Identity}; use crate::prelude::*; #[derive(Debug, thiserror::Error)] -pub enum CreateError { +pub enum CreateAgentError { #[error(transparent)] Agent(#[from] AgentError), @@ -16,14 +16,14 @@ pub enum CreateError { #[async_trait] pub trait Create: Sync + Send { - async fn create(&self, id: Arc, url: &str) -> Result; + async fn create(&self, id: Arc, url: &str) -> Result; } pub struct Creator; #[async_trait] impl Create for Creator { - async fn create(&self, id: Arc, url: &str) -> Result { + async fn create(&self, id: Arc, url: &str) -> Result { let b = Agent::builder(); // Url diff --git a/crates/icp/src/context/mod.rs b/crates/icp/src/context/mod.rs index 577b57f14..130975826 100644 --- a/crates/icp/src/context/mod.rs +++ b/crates/icp/src/context/mod.rs @@ -1,11 +1,14 @@ use console::Term; use std::sync::Arc; +use url::Url; use crate::{ + agent::CreateAgentError, canister::{build::Build, sync::Synchronize}, directories, identity::IdentitySelection, network::access::NetworkAccess, + project::DEFAULT_LOCAL_ENVIRONMENT_NAME, }; use candid::Principal; use ic_agent::{Agent, Identity}; @@ -21,11 +24,11 @@ pub use init::initialize; #[derive(Clone, Debug, PartialEq)] pub enum NetworkSelection { /// Use the network from the environment - FromEnvironment, + Default, /// Use a named network Named(String), /// Use a network by URL - Url(String), + Url(Url), } /// Selection type for environments - similar to IdentitySelection @@ -40,7 +43,7 @@ pub enum EnvironmentSelection { impl EnvironmentSelection { pub fn name(&self) -> &str { match self { - EnvironmentSelection::Default => "local", + EnvironmentSelection::Default => DEFAULT_LOCAL_ENVIRONMENT_NAME, EnvironmentSelection::Named(name) => name, } } @@ -112,7 +115,7 @@ impl Context { /// Returns an error if the project cannot be loaded or if the environment is not found. pub async fn get_environment( &self, - environment_name: &str, + environment: &EnvironmentSelection, ) -> Result { // Load project let p = self.project.load().await?; @@ -120,9 +123,9 @@ impl Context { // Load target environment let env = p .environments - .get(environment_name) + .get(environment.name()) .context(EnvironmentNotFoundSnafu { - name: environment_name.to_owned(), + name: environment.name().to_owned(), })?; Ok(env.clone()) @@ -133,16 +136,21 @@ impl Context { /// # Errors /// /// Returns an error if the project cannot be loaded or if the network is not found. - pub async fn get_network(&self, network_name: &str) -> Result { - // Load project - let p = self.project.load().await?; - - // Load target network - let net = p.networks.get(network_name).context(NetworkNotFoundSnafu { - name: network_name.to_owned(), - })?; - - Ok(net.clone()) + pub async fn get_network( + &self, + network_selection: &NetworkSelection, + ) -> Result { + match network_selection { + NetworkSelection::Named(network_name) => { + let p = self.project.load().await?; + let net = p.networks.get(network_name).context(NetworkNotFoundSnafu { + name: network_name.to_owned(), + })?; + Ok(net.clone()) + } + NetworkSelection::Default => Err(GetNetworkError::DefaultNetwork), + NetworkSelection::Url(_) => Err(GetNetworkError::UrlSpecifiedNetwork), + } } /// Gets the canister ID for a given canister name in a specified environment. @@ -153,14 +161,14 @@ impl Context { pub async fn get_canister_id_for_env( &self, canister_name: &str, - environment_name: &str, + environment: &EnvironmentSelection, ) -> Result { - let env = self.get_environment(environment_name).await?; + let env = self.get_environment(environment).await?; if !env.canisters.contains_key(canister_name) { return Err(GetCanisterIdForEnvError::CanisterNotFoundInEnv { canister_name: canister_name.to_owned(), - environment_name: environment_name.to_owned(), + environment_name: environment.name().to_owned(), }); } @@ -173,7 +181,7 @@ impl Context { }) .context(CanisterIdLookupSnafu { canister_name: canister_name.to_owned(), - environment_name: environment_name.to_owned(), + environment_name: environment.name().to_owned(), })?; Ok(cid) @@ -183,10 +191,10 @@ impl Context { pub async fn get_agent_for_env( &self, identity: &IdentitySelection, - environment_name: &str, + environment: &EnvironmentSelection, ) -> Result { let id = self.get_identity(identity).await?; - let env = self.get_environment(environment_name).await?; + let env = self.get_environment(environment).await?; let access = self.network.access(&env.network).await?; Ok(self.create_agent(id, access).await?) } @@ -195,10 +203,10 @@ impl Context { pub async fn get_agent_for_network( &self, identity: &IdentitySelection, - network_name: &str, + network_selection: &NetworkSelection, ) -> Result { let id = self.get_identity(identity).await?; - let network = self.get_network(network_name).await?; + let network = self.get_network(network_selection).await?; let access = self.network.access(&network).await?; Ok(self.create_agent(id, access).await?) } @@ -210,8 +218,8 @@ impl Context { &self, id: Arc, network_access: NetworkAccess, - ) -> Result { - let agent = self.agent.create(id, &network_access.url).await?; + ) -> Result { + let agent = self.agent.create(id, network_access.url.as_str()).await?; if let Some(k) = network_access.root_key { agent.set_root_key(k); } @@ -222,10 +230,10 @@ impl Context { pub async fn get_agent_for_url( &self, identity: &IdentitySelection, - url: &str, // TODO: change to Url + url: &Url, ) -> Result { let id = self.get_identity(identity).await?; - let agent = self.agent.create(id, url).await?; + let agent = self.agent.create(id, url.as_str()).await?; Ok(agent) } @@ -238,16 +246,14 @@ impl Context { canister: &CanisterSelection, environment: &EnvironmentSelection, ) -> Result { - let environment_name = environment.name(); - let principal = match canister { CanisterSelection::Named(canister_name) => { - self.get_canister_id_for_env(canister_name, environment_name) + self.get_canister_id_for_env(canister_name, environment) .await? } CanisterSelection::Principal(principal) => { // Make sure a valid environment was requested - let _ = self.get_environment(environment_name).await?; + let _ = self.get_environment(environment).await?; *principal } }; @@ -266,8 +272,6 @@ impl Context { network: &NetworkSelection, identity: &IdentitySelection, ) -> Result<(Principal, Agent), GetCanisterIdAndAgentError> { - let env_name = environment.name(); - let (cid, agent) = match (canister, environment, network) { // Error: Both environment and network specified (_, EnvironmentSelection::Named(_), NetworkSelection::Named(_)) @@ -290,15 +294,15 @@ impl Context { } // Canister by name, use environment - (CanisterSelection::Named(cname), _, NetworkSelection::FromEnvironment) => { - let agent = self.get_agent_for_env(identity, env_name).await?; - let cid = self.get_canister_id_for_env(cname, env_name).await?; + (CanisterSelection::Named(cname), _, NetworkSelection::Default) => { + let agent = self.get_agent_for_env(identity, environment).await?; + let cid = self.get_canister_id_for_env(cname, environment).await?; (cid, agent) } // Canister by principal, use environment - (CanisterSelection::Principal(principal), _, NetworkSelection::FromEnvironment) => { - let agent = self.get_agent_for_env(identity, env_name).await?; + (CanisterSelection::Principal(principal), _, NetworkSelection::Default) => { + let agent = self.get_agent_for_env(identity, environment).await?; (*principal, agent) } @@ -306,9 +310,9 @@ impl Context { ( CanisterSelection::Principal(principal), EnvironmentSelection::Default, - NetworkSelection::Named(net_name), + NetworkSelection::Named(_), ) => { - let agent = self.get_agent_for_network(identity, net_name).await?; + let agent = self.get_agent_for_network(identity, network).await?; (*principal, agent) } @@ -370,6 +374,12 @@ pub enum GetNetworkError { #[snafu(display("network '{}' not found in project", name))] NetworkNotFound { name: String }, + + #[snafu(display("cannot load URL-specified network"))] + UrlSpecifiedNetwork, + + #[snafu(display("cannot load default network"))] + DefaultNetwork, } #[derive(Debug, Snafu)] @@ -411,7 +421,9 @@ pub enum GetAgentForEnvError { NetworkAccess { source: crate::network::AccessError }, #[snafu(transparent)] - AgentCreate { source: crate::agent::CreateError }, + AgentCreate { + source: crate::agent::CreateAgentError, + }, } #[derive(Debug, Snafu)] @@ -426,7 +438,9 @@ pub enum GetAgentForNetworkError { NetworkAccess { source: crate::network::AccessError }, #[snafu(transparent)] - AgentCreate { source: crate::agent::CreateError }, + AgentCreate { + source: crate::agent::CreateAgentError, + }, } #[derive(Debug, Snafu)] @@ -435,7 +449,9 @@ pub enum GetAgentForUrlError { GetIdentity { source: GetIdentityError }, #[snafu(transparent)] - AgentCreate { source: crate::agent::CreateError }, + AgentCreate { + source: crate::agent::CreateAgentError, + }, } #[derive(Debug, Snafu)] diff --git a/crates/icp/src/context/tests.rs b/crates/icp/src/context/tests.rs index 1799402ee..86b79d77c 100644 --- a/crates/icp/src/context/tests.rs +++ b/crates/icp/src/context/tests.rs @@ -62,7 +62,10 @@ async fn test_get_environment_success() { ..Context::mocked() }; - let env = ctx.get_environment("dev").await.unwrap(); + let env = ctx + .get_environment(&EnvironmentSelection::Named("dev".to_string())) + .await + .unwrap(); assert_eq!(env.name, "dev"); } @@ -71,7 +74,9 @@ async fn test_get_environment_success() { async fn test_get_environment_not_found() { let ctx = Context::mocked(); - let result = ctx.get_environment("nonexistent").await; + let result = ctx + .get_environment(&EnvironmentSelection::Named("nonexistent".to_string())) + .await; assert!(matches!( result, @@ -86,7 +91,10 @@ async fn test_get_network_success() { ..Context::mocked() }; - let network = ctx.get_network("local").await.unwrap(); + let network = ctx + .get_network(&NetworkSelection::Named("local".to_string())) + .await + .unwrap(); assert_eq!(network.name, "local"); } @@ -95,7 +103,9 @@ async fn test_get_network_success() { async fn test_get_network_not_found() { let ctx = Context::mocked(); - let result = ctx.get_network("nonexistent").await; + let result = ctx + .get_network(&NetworkSelection::Named("nonexistent".to_string())) + .await; assert!(matches!( result, @@ -128,7 +138,10 @@ async fn test_get_canister_id_for_env_success() { ..Context::mocked() }; - let cid = ctx.get_canister_id_for_env("backend", "dev").await.unwrap(); + let cid = ctx + .get_canister_id_for_env("backend", &EnvironmentSelection::Named("dev".to_string())) + .await + .unwrap(); assert_eq!(cid, canister_id); } @@ -141,7 +154,9 @@ async fn test_get_canister_id_for_env_canister_not_in_env() { }; // "database" is only in "dev" environment, not in "test" - let result = ctx.get_canister_id_for_env("database", "test").await; + let result = ctx + .get_canister_id_for_env("database", &EnvironmentSelection::Named("test".to_string())) + .await; assert!(matches!( result, @@ -160,7 +175,9 @@ async fn test_get_canister_id_for_env_id_not_registered() { }; // Environment exists and canister is in it, but ID not registered - let result = ctx.get_canister_id_for_env("backend", "dev").await; + let result = ctx + .get_canister_id_for_env("backend", &EnvironmentSelection::Named("dev".to_string())) + .await; assert!(matches!( result, @@ -188,7 +205,7 @@ async fn test_get_agent_for_env_uses_environment_network() { NetworkAccess { default_effective_canister_id: None, root_key: None, - url: "http://localhost:8000".to_string(), + url: Url::parse("http://localhost:8000").unwrap(), }, ) .with_network( @@ -196,7 +213,7 @@ async fn test_get_agent_for_env_uses_environment_network() { NetworkAccess { default_effective_canister_id: None, root_key: Some(staging_root_key.clone()), - url: "http://staging:9000".to_string(), + url: Url::parse("http://staging:9000").unwrap(), }, ), ), @@ -204,7 +221,10 @@ async fn test_get_agent_for_env_uses_environment_network() { }; let agent = ctx - .get_agent_for_env(&IdentitySelection::Anonymous, "test") + .get_agent_for_env( + &IdentitySelection::Anonymous, + &EnvironmentSelection::Named("test".to_string()), + ) .await .unwrap(); @@ -216,7 +236,10 @@ async fn test_get_agent_for_env_environment_not_found() { let ctx = Context::mocked(); let result = ctx - .get_agent_for_env(&IdentitySelection::Anonymous, "nonexistent") + .get_agent_for_env( + &IdentitySelection::Anonymous, + &EnvironmentSelection::Named("nonexistent".to_string()), + ) .await; assert!(matches!( @@ -238,7 +261,10 @@ async fn test_get_agent_for_env_network_not_configured() { }; let result = ctx - .get_agent_for_env(&IdentitySelection::Anonymous, "dev") + .get_agent_for_env( + &IdentitySelection::Anonymous, + &EnvironmentSelection::Named("dev".to_string()), + ) .await; assert!(matches!( @@ -262,14 +288,17 @@ async fn test_get_agent_for_network_success() { NetworkAccess { default_effective_canister_id: None, root_key: Some(root_key.clone()), - url: "http://localhost:8000".to_string(), + url: Url::parse("http://localhost:8000").unwrap(), }, )), ..Context::mocked() }; let agent = ctx - .get_agent_for_network(&IdentitySelection::Anonymous, "local") + .get_agent_for_network( + &IdentitySelection::Anonymous, + &NetworkSelection::Named("local".to_string()), + ) .await .unwrap(); @@ -281,7 +310,10 @@ async fn test_get_agent_for_network_network_not_found() { let ctx = Context::mocked(); let result = ctx - .get_agent_for_network(&IdentitySelection::Anonymous, "nonexistent") + .get_agent_for_network( + &IdentitySelection::Anonymous, + &NetworkSelection::Named("nonexistent".to_string()), + ) .await; assert!(matches!( @@ -302,7 +334,10 @@ async fn test_get_agent_for_network_not_configured() { }; let result = ctx - .get_agent_for_network(&IdentitySelection::Anonymous, "local") + .get_agent_for_network( + &IdentitySelection::Anonymous, + &NetworkSelection::Named("local".to_string()), + ) .await; assert!(matches!( @@ -318,7 +353,10 @@ async fn test_get_agent_for_url_success() { let ctx = Context::mocked(); let result = ctx - .get_agent_for_url(&IdentitySelection::Anonymous, "http://localhost:8000") + .get_agent_for_url( + &IdentitySelection::Anonymous, + &Url::parse("http://localhost:8000").unwrap(), + ) .await; assert!(result.is_ok()); diff --git a/crates/icp/src/manifest/environment.rs b/crates/icp/src/manifest/environment.rs index bb4d2b805..ed3eeeb82 100644 --- a/crates/icp/src/manifest/environment.rs +++ b/crates/icp/src/manifest/environment.rs @@ -3,7 +3,10 @@ use std::collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Deserializer}; -use crate::canister::Settings; +use crate::{ + canister::Settings, + project::{DEFAULT_LOCAL_ENVIRONMENT_NAME, DEFAULT_LOCAL_NETWORK_NAME}, +}; #[derive(Clone, Debug, PartialEq, Deserialize, JsonSchema)] pub struct EnvironmentInner { @@ -68,12 +71,12 @@ impl TryFrom for EnvironmentManifest { } = v; // Name - if name == "local" { + if name == DEFAULT_LOCAL_ENVIRONMENT_NAME { return Err(ParseError::OverrideLocal); } // Network - let network = network.unwrap_or("local".to_string()); + let network = network.unwrap_or(DEFAULT_LOCAL_NETWORK_NAME.to_string()); // Canisters let canisters = match canisters { diff --git a/crates/icp/src/network/access.rs b/crates/icp/src/network/access.rs index d33a82de8..0f121ec01 100644 --- a/crates/icp/src/network/access.rs +++ b/crates/icp/src/network/access.rs @@ -1,5 +1,6 @@ use ic_agent::export::Principal; use snafu::{OptionExt, ResultExt, Snafu}; +use url::Url; use crate::{ Network, @@ -17,22 +18,22 @@ pub struct NetworkAccess { pub root_key: Option>, /// Routing configuration - pub url: String, + pub url: Url, } impl NetworkAccess { - pub fn new(url: &str) -> Self { + pub fn new(url: &Url) -> Self { Self { default_effective_canister_id: None, root_key: None, - url: url.into(), + url: url.clone(), } } } impl NetworkAccess { pub fn mainnet() -> Self { - Self::new(IC_MAINNET_NETWORK_URL) + Self::new(&Url::parse(IC_MAINNET_NETWORK_URL).unwrap()) } } @@ -61,6 +62,12 @@ pub enum GetNetworkAccessError { #[snafu(display("no descriptor found for port {port}"))] NoPortDescriptor { port: u16 }, + + #[snafu(display("failed to parse URL {url}"))] + ParseUrl { + url: String, + source: url::ParseError, + }, } pub fn get_network_access( @@ -106,7 +113,7 @@ pub fn get_network_access( NetworkAccess { default_effective_canister_id, root_key: Some(root_key), - url: format!("http://localhost:{port}"), + url: Url::parse(&format!("http://localhost:{port}")).unwrap(), } } @@ -123,7 +130,9 @@ pub fn get_network_access( NetworkAccess { default_effective_canister_id: None, root_key, - url: cfg.url.to_owned(), + url: Url::parse(&cfg.url).context(ParseUrlSnafu { + url: cfg.url.clone(), + })?, } } }; diff --git a/crates/icp/src/project.rs b/crates/icp/src/project.rs index e689d6bed..0e277761a 100644 --- a/crates/icp/src/project.rs +++ b/crates/icp/src/project.rs @@ -21,6 +21,11 @@ use crate::{ prelude::*, }; +pub const DEFAULT_LOCAL_ENVIRONMENT_NAME: &str = "local"; +pub const DEFAULT_MAINNET_ENVIRONMENT_NAME: &str = "ic"; +pub const DEFAULT_LOCAL_NETWORK_NAME: &str = "local"; +pub const DEFAULT_MAINNET_NETWORK_NAME: &str = "mainnet"; + #[derive(Debug, thiserror::Error)] pub enum LoadPathError { #[error("failed to read manifest at {0}")] @@ -112,7 +117,7 @@ fn default_networks() -> Vec { vec![ Network { // The local network at localhost:8000 - name: "local".to_string(), + name: DEFAULT_LOCAL_NETWORK_NAME.to_string(), configuration: Configuration::Managed { managed: Managed { gateway: Gateway { @@ -124,10 +129,10 @@ fn default_networks() -> Vec { }, Network { // Mainnet at https://icp-api.io - name: "mainnet".to_string(), + name: DEFAULT_MAINNET_NETWORK_NAME.to_string(), configuration: Configuration::Connected { connected: Connected { - url: "https://icp-api.io".to_string(), + url: IC_MAINNET_NETWORK_URL.to_string(), // Will use the IC Root key hard coded in agent-rs. // https://github.com/dfinity/agent-rs/blob/b77f1fc5fe05d8de1065ee4cec837bc3f2ce9976/ic-agent/src/agent/mod.rs#L82 root_key: None, @@ -410,14 +415,16 @@ impl LoadManifest for ManifestLoade // We're done adding all the user environments // Now we add the default `local` environment if the user hasn't overriden it - if let Entry::Vacant(vacant_entry) = environments.entry("local".to_string()) { + if let Entry::Vacant(vacant_entry) = + environments.entry(DEFAULT_LOCAL_ENVIRONMENT_NAME.to_string()) + { vacant_entry.insert(Environment { - name: "local".to_string(), + name: DEFAULT_LOCAL_ENVIRONMENT_NAME.to_string(), network: networks - .get("local") + .get(DEFAULT_LOCAL_NETWORK_NAME) .ok_or(EnvironmentError::Network { - environment: "local".to_owned(), - network: "local".to_owned(), + environment: DEFAULT_LOCAL_ENVIRONMENT_NAME.to_owned(), + network: DEFAULT_LOCAL_NETWORK_NAME.to_owned(), })? .to_owned(), canisters: canisters.clone(), diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 84f3334e5..81e823183 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -124,7 +124,8 @@ Make a canister call ###### **Options:** * `--network ` — Name of the network to target, conflicts with environment argument -* `--environment ` — Name of the target environment +* `--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 @@ -170,7 +171,8 @@ Delete a canister from a network ###### **Options:** * `--network ` — Name of the network to target, conflicts with environment argument -* `--environment ` — Name of the target environment +* `--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 @@ -188,7 +190,8 @@ Display a canister's information ###### **Options:** * `--network ` — Name of the network to target, conflicts with environment argument -* `--environment ` — Name of the target environment +* `--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 @@ -252,7 +255,8 @@ List the canisters in an environment ###### **Options:** * `--network ` — Name of the network to target, conflicts with environment argument -* `--environment ` — Name of the target environment +* `--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 @@ -268,7 +272,8 @@ List the canisters in an environment ###### **Options:** * `--network ` — Name of the network to target, conflicts with environment argument -* `--environment ` — Name of the target environment +* `--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 * `--add-controller ` * `--remove-controller ` @@ -300,7 +305,8 @@ Show a canister's details ###### **Options:** -* `--environment ` — Name of the target environment +* `--environment ` — Override the environment to connect to. By default, the local environment is used +* `--ic` — Shorthand for --environment=ic @@ -317,7 +323,8 @@ Start a canister on a network ###### **Options:** * `--network ` — Name of the network to target, conflicts with environment argument -* `--environment ` — Name of the target environment +* `--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 @@ -335,7 +342,8 @@ Show the status of a canister ###### **Options:** * `--network ` — Name of the network to target, conflicts with environment argument -* `--environment ` — Name of the target environment +* `--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 @@ -353,7 +361,8 @@ Stop a canister on a network ###### **Options:** * `--network ` — Name of the network to target, conflicts with environment argument -* `--environment ` — Name of the target environment +* `--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 @@ -372,7 +381,8 @@ Top up a canister with cycles * `--amount ` — Amount of cycles to top up * `--network ` — Name of the network to target, conflicts with environment argument -* `--environment ` — Name of the target environment +* `--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