diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d5caa64b1..f9ec8bd4f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ The command line interface for nns commands has been updated to: - Not offer --network as a flag given that it is unused - List nns subcommands +### feat: -y flag for canister installation + +`dfx canister install` and `dfx deploy` now have a `-y` flag that will automatically confirm any y/n checks made during canister installation. + ### fix: Compute Motoko dependencies in linear (not exponential) time by detecting visited imports. ### fix(generate): add missing typescript types and fix issues with bindings array in dfx.json diff --git a/e2e/tests-dfx/install.bash b/e2e/tests-dfx/install.bash index c831434ea6..f8ba0691db 100644 --- a/e2e/tests-dfx/install.bash +++ b/e2e/tests-dfx/install.bash @@ -163,3 +163,11 @@ teardown() { assert_command dfx canister install e2e_project_backend --mode auto --upgrade-unchanged assert_command dfx canister call e2e_project_backend greet dfx } + +@test "-y skips compat check" { + dfx_start + assert_command dfx canister create e2e_project_backend + assert_command dfx build e2e_project_backend + assert_command dfx canister install e2e_project_backend + assert_command timeout -s9 20s dfx canister install e2e_project_backend --mode reinstall -y # if -y does not work, hangs without stdin +} diff --git a/src/dfx/src/commands/canister/install.rs b/src/dfx/src/commands/canister/install.rs index 80238d53c4..af6fc06c9c 100644 --- a/src/dfx/src/commands/canister/install.rs +++ b/src/dfx/src/commands/canister/install.rs @@ -51,6 +51,11 @@ pub struct CanisterInstallOpts { /// Specifies a particular WASM file to install, bypassing the dfx.json project settings. #[clap(long, conflicts_with("all"))] wasm: Option, + + /// Skips yes/no checks by answering 'yes'. Such checks usually result in data loss, + /// so this is not recommended outside of CI. + #[clap(long, short)] + yes: bool, } pub async fn exec( @@ -113,6 +118,7 @@ pub async fn exec( call_sender, fs::read(&wasm_path) .with_context(|| format!("Unable to read {}", wasm_path.display()))?, + opts.yes, ) .await } else { @@ -132,6 +138,7 @@ pub async fn exec( call_sender, opts.upgrade_unchanged, None, + opts.yes, ) .await } @@ -170,6 +177,7 @@ pub async fn exec( call_sender, opts.upgrade_unchanged, None, + opts.yes, ) .await?; } diff --git a/src/dfx/src/commands/deploy.rs b/src/dfx/src/commands/deploy.rs index a9d253980d..0d15913c00 100644 --- a/src/dfx/src/commands/deploy.rs +++ b/src/dfx/src/commands/deploy.rs @@ -71,6 +71,11 @@ pub struct DeployOpts { /// Bypasses the Wallet canister. #[clap(long, conflicts_with("wallet"))] no_wallet: bool, + + /// Skips yes/no checks by answering 'yes'. Such checks usually result in data loss, + /// so this is not recommended outside of CI. + #[clap(long, short)] + yes: bool, } pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult { @@ -129,6 +134,7 @@ pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult { with_cycles, &call_sender, create_call_sender, + opts.yes, ))?; display_urls(&env) diff --git a/src/dfx/src/lib/nns/install_nns.rs b/src/dfx/src/lib/nns/install_nns.rs index 2689b54b08..29a6e2501e 100644 --- a/src/dfx/src/lib/nns/install_nns.rs +++ b/src/dfx/src/lib/nns/install_nns.rs @@ -680,6 +680,7 @@ pub async fn install_canister( timeout, &call_sender, fs::read(&wasm_path).with_context(|| format!("Unable to read {:?}", wasm_path))?, + true, ) .await?; diff --git a/src/dfx/src/lib/operations/canister/deploy_canisters.rs b/src/dfx/src/lib/operations/canister/deploy_canisters.rs index 3454e227b1..aa175a14be 100644 --- a/src/dfx/src/lib/operations/canister/deploy_canisters.rs +++ b/src/dfx/src/lib/operations/canister/deploy_canisters.rs @@ -32,6 +32,7 @@ pub async fn deploy_canisters( with_cycles: Option<&str>, call_sender: &CallSender, create_call_sender: &CallSender, + skip_consent: bool, ) -> DfxResult { let log = env.get_logger(); @@ -102,6 +103,7 @@ pub async fn deploy_canisters( timeout, call_sender, pool, + skip_consent, ) .await?; @@ -217,6 +219,7 @@ async fn install_canisters( timeout: Duration, call_sender: &CallSender, pool: CanisterPool, + skip_consent: bool, ) -> DfxResult { info!(env.get_logger(), "Installing canisters..."); @@ -254,6 +257,7 @@ async fn install_canisters( call_sender, upgrade_unchanged, Some(&pool), + skip_consent, ) .await?; } diff --git a/src/dfx/src/lib/operations/canister/install_canister.rs b/src/dfx/src/lib/operations/canister/install_canister.rs index 95e99aab2c..82e91cebe2 100644 --- a/src/dfx/src/lib/operations/canister/install_canister.rs +++ b/src/dfx/src/lib/operations/canister/install_canister.rs @@ -42,6 +42,7 @@ pub async fn install_canister( call_sender: &CallSender, upgrade_unchanged: bool, pool: Option<&CanisterPool>, + skip_consent: bool, ) -> DfxResult { let log = env.get_logger(); let network = env.get_network_descriptor(); @@ -67,7 +68,7 @@ pub async fn install_canister( InstallMode::Install } }); - if matches!(mode, InstallMode::Reinstall | InstallMode::Upgrade) { + if !skip_consent && matches!(mode, InstallMode::Reinstall | InstallMode::Upgrade) { let candid = read_module_metadata(agent, canister_id, "candid:service").await; if let Some(candid) = &candid { match check_candid_compatibility(canister_info, candid) { @@ -83,7 +84,7 @@ pub async fn install_canister( } } } - if canister_info.is_motoko() && matches!(mode, InstallMode::Upgrade) { + if !skip_consent && canister_info.is_motoko() && matches!(mode, InstallMode::Upgrade) { let stable_types = read_module_metadata(agent, canister_id, "motoko:stable-types").await; if let Some(stable_types) = &stable_types { match check_stable_compatibility(canister_info, env, stable_types) { @@ -124,6 +125,7 @@ pub async fn install_canister( timeout, call_sender, wasm_module, + skip_consent, ) .await?; } @@ -357,10 +359,11 @@ pub async fn install_canister_wasm( timeout: Duration, call_sender: &CallSender, wasm_module: Vec, + skip_consent: bool, ) -> DfxResult { let log = env.get_logger(); let mgr = ManagementCanister::create(agent); - if mode == InstallMode::Reinstall { + if !skip_consent && mode == InstallMode::Reinstall { let msg = if let Some(name) = canister_name { format!("You are about to reinstall the {name} canister") } else {