From fc14680e3ca872ddf8c82979960e17d3906b14cd Mon Sep 17 00:00:00 2001 From: Eric Swanson <64809312+ericswanson-dfinity@users.noreply.github.com> Date: Mon, 18 Nov 2024 07:52:38 -0800 Subject: [PATCH 01/15] test: extension commands with extension defined canister type (#4000) Also use newer nns extension version Part of https://dfinity.atlassian.net/browse/SDK-1832 --- e2e/tests-dfx/cycles-ledger.bash | 2 +- e2e/tests-dfx/extension.bash | 54 ++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/e2e/tests-dfx/cycles-ledger.bash b/e2e/tests-dfx/cycles-ledger.bash index 481923ec32..7c20cbf057 100644 --- a/e2e/tests-dfx/cycles-ledger.bash +++ b/e2e/tests-dfx/cycles-ledger.bash @@ -25,7 +25,7 @@ teardown() { start_and_install_nns() { dfx_start_for_nns_install - dfx extension install nns --version 0.4.3 + dfx extension install nns --version 0.4.7 dfx nns install --ledger-accounts "$(dfx ledger account-id --identity cycle-giver)" } diff --git a/e2e/tests-dfx/extension.bash b/e2e/tests-dfx/extension.bash index 0b56c07240..7aa52b2122 100644 --- a/e2e/tests-dfx/extension.bash +++ b/e2e/tests-dfx/extension.bash @@ -14,6 +14,54 @@ teardown() { standard_teardown } +@test "run an extension command with a canister type defined by another extension" { + install_shared_asset subnet_type/shared_network_settings/system + dfx_start_for_nns_install + + install_asset wasm/identity + CACHE_DIR=$(dfx cache show) + mkdir -p "$CACHE_DIR"/extensions/embera + cat > "$CACHE_DIR"/extensions/embera/extension.json < dfx.json < Date: Mon, 18 Nov 2024 11:02:21 -0800 Subject: [PATCH 02/15] feat: Update dfx project creation output (#4001) * remove dfinity logo when dfx new * CREATE log at trace level * simplify welcome message * wording: Creating git repo -> Initializing --- src/dfx/assets/dfinity-color-xterm256.aart | 16 ---------------- src/dfx/assets/dfinity-color.aart | 16 ---------------- src/dfx/assets/dfinity-nocolor.aart | 20 -------------------- src/dfx/assets/welcome.txt | 22 ---------------------- src/dfx/src/commands/new.rs | 22 ++++++++++++---------- src/dfx/src/util/assets.rs | 14 -------------- 6 files changed, 12 insertions(+), 98 deletions(-) delete mode 100644 src/dfx/assets/dfinity-color-xterm256.aart delete mode 100644 src/dfx/assets/dfinity-color.aart delete mode 100644 src/dfx/assets/dfinity-nocolor.aart delete mode 100644 src/dfx/assets/welcome.txt diff --git a/src/dfx/assets/dfinity-color-xterm256.aart b/src/dfx/assets/dfinity-color-xterm256.aart deleted file mode 100644 index 0637491adb..0000000000 --- a/src/dfx/assets/dfinity-color-xterm256.aart +++ /dev/null @@ -1,16 +0,0 @@ -                                                              -                                                              -                                                              -                                                              -                                                              -                                                              -                                                              -                                                              -                                                              -                                                              -                                                              -                                                              -                                                              -                                                              -                                                              -  diff --git a/src/dfx/assets/dfinity-color.aart b/src/dfx/assets/dfinity-color.aart deleted file mode 100644 index daae6d7392..0000000000 --- a/src/dfx/assets/dfinity-color.aart +++ /dev/null @@ -1,16 +0,0 @@ -        ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄                ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄        -      ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄          ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄     -    ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄      ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄   -   ▄▄▄▄▄▄▄▄▄▄▀▀▀▀▀▄▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄▄▀▀▀▀▀▀▄▄▄▄▄▄▄▄▄▄  -  ▄▄▄▄▄▄▄▄▀         ▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀         ▀▄▄▄▄▄▄▄▄▄ - ▄▄▄▄▄▄▄▄▀            ▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀             ▄▄▄▄▄▄▄▄ - ▄▄▄▄▄▄▄▄               ▀▄▄▄▄▄▄▄▄▄▄▄▄▀                ▄▄▄▄▄▄▄ - ▄▄▄▄▄▄▄▄                ▄▄▄▄▄▄▄▄▄▄▄▄                 ▄▄▄▄▄▄▄ - ▄▄▄▄▄▄▄▄               ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄              ▄▄▄▄▄▄▄▄ -  ▄▄▄▄▄▄▄▄           ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄          ▄▄▄▄▄▄▄▄▀ -  ▀▄▄▄▄▄▄▄▄▄▄     ▄▄▄▄▄▄▄▄▄▄▄▄▀ ▀▄▄▄▄▄▄▄▄▄▄▄▄    ▄▄▄▄▄▄▄▄▄▄▄  -   ▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀     ▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀   -     ▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀         ▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄     -       ▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀▀             ▀▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀       -          ▀▀▀▀▀▀▀▀▀▀▀                    ▀▀▀▀▀▀▀▀▀▀▀          -  diff --git a/src/dfx/assets/dfinity-nocolor.aart b/src/dfx/assets/dfinity-nocolor.aart deleted file mode 100644 index e57da0e4dc..0000000000 --- a/src/dfx/assets/dfinity-nocolor.aart +++ /dev/null @@ -1,20 +0,0 @@ - - 8HG5yoXWQ Q#DXZG$b%Q - Rf^_.--..----.'=fR 0ol!~~~~~~~~~:~>fb - Qz:---------..------._)W R{;~~~~~~~~~~~:,____'_]% - Q)'._~!++=+;!!~_--------..~y X^~~~~~~~~~~!+;~___'''''.-r& - H,~>)TTv)f6& Q2*_--.-----_}Q Qy;~~~~~~~~*40 go^'..-````y - k=)TTTv)5Q bi'.------'{ y!~~~~~~~^2Q Q/-``` c - Q)]TT))/b Q{_-------_!~~~~~~~>q $- K - ATvv)/iS C_------.'_~~~z Z!,,,_'-------.=b ] ^ - ]*<>>rry Qy!,,,,,,,^<.-------!$ v `% - Qi>rr^^=.-------_vb y' .H - Q]^^=+++;=JZRQ %X]!,,,,,,,,=X R>.-------._*S% Q&Df=` ``~R - q<+;!!!!~~~~~~:,,,,,,,,,:'--.-------------.;G - Wyi+~:,,,,,,~>zqQ bz^_.------._=c$Q - QQ8#8Q Q#WRBQ - diff --git a/src/dfx/assets/welcome.txt b/src/dfx/assets/welcome.txt deleted file mode 100644 index e14d0176d8..0000000000 --- a/src/dfx/assets/welcome.txt +++ /dev/null @@ -1,22 +0,0 @@ - -=============================================================================== - Welcome to the internet computer developer community! - You're using dfx {0} - -{1} - -To learn more before you start coding, see the documentation available online: - -- Quick Start: https://internetcomputer.org/docs/current/tutorials/deploy_sample_app -- SDK Developer Tools: https://internetcomputer.org/docs/current/developer-docs/setup/install/ -- Motoko Language Guide: https://internetcomputer.org/docs/current/motoko/main/about-this-guide -- Motoko Quick Reference: https://internetcomputer.org/docs/current/motoko/main/language-manual -- Rust CDK Guide: https://internetcomputer.org/docs/current/developer-docs/backend/rust/ - -If you want to work on programs right away, try the following commands to get started: - - cd {2} - dfx help - dfx new --help - -=============================================================================== diff --git a/src/dfx/src/commands/new.rs b/src/dfx/src/commands/new.rs index 412d8269f3..08e67b7256 100644 --- a/src/dfx/src/commands/new.rs +++ b/src/dfx/src/commands/new.rs @@ -21,7 +21,7 @@ use dialoguer::{FuzzySelect, MultiSelect}; use fn_error_context::context; use indicatif::HumanBytes; use semver::Version; -use slog::{info, warn, Logger}; +use slog::{info, trace, warn, Logger}; use std::collections::{BTreeMap, HashMap}; use std::io::{self, IsTerminal, Read}; use std::path::{Path, PathBuf}; @@ -130,7 +130,7 @@ pub fn create_file(log: &Logger, path: &Path, content: &[u8], dry_run: bool) -> .with_context(|| format!("Failed to write to {}.", path.to_string_lossy()))?; } - info!(log, "{}", Status::Create(path, content.len())); + trace!(log, "{}", Status::Create(path, content.len())); Ok(()) } @@ -184,7 +184,7 @@ pub fn create_dir>(log: &Logger, path: P, dry_run: bool) -> DfxRe .with_context(|| format!("Failed to create directory {}.", path.to_string_lossy()))?; } - info!(log, "{}", Status::CreateDir(path)); + trace!(log, "{}", Status::CreateDir(path)); Ok(()) } @@ -198,7 +198,7 @@ pub fn init_git(log: &Logger, project_name: &Path) -> DfxResult { .status(); if init_status.is_ok() && init_status.unwrap().success() { - info!(log, "Creating git repository..."); + info!(log, "Initializing git repository..."); std::process::Command::new("git") .arg("add") .current_dir(project_name) @@ -558,12 +558,14 @@ pub fn exec(env: &dyn Environment, mut opts: NewOpts) -> DfxResult { // Print welcome message. info!( log, - // This needs to be included here because we cannot use the result of a function for - // the format!() rule (and so it cannot be moved in the util::assets module). - include_str!("../../assets/welcome.txt"), - version_str, - assets::dfinity_logo(), - project_name_str + "=============================================================================== + Welcome to the internet computer developer community! + +To learn more before you start coding, check out the developer docs and samples: + +- Documentation: https://internetcomputer.org/docs/current/developer-docs +- Samples: https://internetcomputer.org/samples +===============================================================================" ); Ok(()) diff --git a/src/dfx/src/util/assets.rs b/src/dfx/src/util/assets.rs index 87ab80a8ff..aa6d032236 100644 --- a/src/dfx/src/util/assets.rs +++ b/src/dfx/src/util/assets.rs @@ -6,20 +6,6 @@ use std::io::Read; include!(concat!(env!("OUT_DIR"), "/load_assets.rs")); -pub fn dfinity_logo() -> String { - let colors = supports_color::on(supports_color::Stream::Stdout); - if let Some(colors) = colors { - //Some terminals, notably MacOS's Terminal.app, do not support Truecolor (RGB-colored characters) properly. - //Therefore we use xterm256 coloring when the program is running in such a terminal. - if colors.has_16m { - return include_str!("../../assets/dfinity-color.aart").to_string(); - } else if colors.has_256 { - return include_str!("../../assets/dfinity-color-xterm256.aart").to_string(); - } - } - include_str!("../../assets/dfinity-nocolor.aart").to_string() -} - #[context("Failed to load wallet wasm.")] pub fn wallet_wasm(logger: &slog::Logger) -> DfxResult> { if let Ok(dfx_wallet_wasm) = std::env::var("DFX_WALLET_WASM") { From ea6fd5d7c63c035b5a39034203a8b22063341aaa Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Mon, 18 Nov 2024 13:33:27 -0800 Subject: [PATCH 03/15] chore: update log related to dfx generate (#4004) * trace log when generating type instead of eprintln * debug log when frontend build succeed instead of warning * fmt --- src/dfx/src/commands/generate.rs | 2 +- src/dfx/src/lib/builders/assets.rs | 6 +++++- src/dfx/src/lib/builders/mod.rs | 15 +++++++++------ src/dfx/src/lib/models/canister.rs | 10 ++++++++-- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/dfx/src/commands/generate.rs b/src/dfx/src/commands/generate.rs index 840958162d..2844db9443 100644 --- a/src/dfx/src/commands/generate.rs +++ b/src/dfx/src/commands/generate.rs @@ -77,7 +77,7 @@ pub fn exec(env: &dyn Environment, opts: GenerateOpts) -> DfxResult { } for canister in canister_pool_load.canisters_to_build(&generate_config) { - canister.generate(&canister_pool_load, &generate_config)?; + canister.generate(log, &canister_pool_load, &generate_config)?; } Ok(()) diff --git a/src/dfx/src/lib/builders/assets.rs b/src/dfx/src/lib/builders/assets.rs index 5860cbb8a0..46a6a636ae 100644 --- a/src/dfx/src/lib/builders/assets.rs +++ b/src/dfx/src/lib/builders/assets.rs @@ -239,7 +239,11 @@ fn build_frontend( ))); } else if !output.stderr.is_empty() { // Cannot use eprintln, because it would interfere with the progress bar. - slog::warn!(logger, "{}", String::from_utf8_lossy(&output.stderr)); + slog::debug!( + logger, + "Frontend build succeed:\n{}", + String::from_utf8_lossy(&output.stderr) + ); } } Ok(()) diff --git a/src/dfx/src/lib/builders/mod.rs b/src/dfx/src/lib/builders/mod.rs index 0344c27b0e..78b1c0974f 100644 --- a/src/dfx/src/lib/builders/mod.rs +++ b/src/dfx/src/lib/builders/mod.rs @@ -12,6 +12,7 @@ use dfx_core::network::provider::get_network_context; use dfx_core::util; use fn_error_context::context; use handlebars::Handlebars; +use slog::{trace, Logger}; use std::borrow::Cow; use std::collections::BTreeMap; use std::ffi::OsStr; @@ -94,6 +95,7 @@ pub trait CanisterBuilder { /// Generate type declarations for the canister fn generate( &self, + logger: &Logger, pool: &CanisterPool, info: &CanisterInfo, config: &BuildConfig, @@ -137,8 +139,9 @@ pub trait CanisterBuilder { return Ok(()); } - eprintln!( - "Generating type declarations for canister {}:", + trace!( + logger, + "Generating type declarations for canister {}", &info.get_name() ); @@ -172,7 +175,7 @@ pub trait CanisterBuilder { output_did_ts_path.to_string_lossy() ) })?; - eprintln!(" {}", &output_did_ts_path.display()); + trace!(logger, " {}", &output_did_ts_path.display()); compile_handlebars_files("ts", info, generate_output_dir)?; } @@ -191,7 +194,7 @@ pub trait CanisterBuilder { output_did_js_path.to_string_lossy() ) })?; - eprintln!(" {}", &output_did_js_path.display()); + trace!(logger, " {}", &output_did_js_path.display()); compile_handlebars_files("js", info, generate_output_dir)?; } @@ -206,7 +209,7 @@ pub trait CanisterBuilder { std::fs::write(&output_mo_path, content).with_context(|| { format!("Failed to write to {}.", output_mo_path.to_string_lossy()) })?; - eprintln!(" {}", &output_mo_path.display()); + trace!(logger, " {}", &output_mo_path.display()); } // Candid @@ -216,7 +219,7 @@ pub trait CanisterBuilder { .with_extension("did"); dfx_core::fs::copy(&did_from_build, &output_did_path)?; dfx_core::fs::set_permissions_readwrite(&output_did_path)?; - eprintln!(" {}", &output_did_path.display()); + trace!(logger, " {}", &output_did_path.display()); } Ok(()) diff --git a/src/dfx/src/lib/models/canister.rs b/src/dfx/src/lib/models/canister.rs index c49e54c2d1..77667e95e3 100644 --- a/src/dfx/src/lib/models/canister.rs +++ b/src/dfx/src/lib/models/canister.rs @@ -104,8 +104,14 @@ impl Canister { } #[context("Failed while trying to generate type declarations for '{}'.", self.info.get_name())] - pub fn generate(&self, pool: &CanisterPool, build_config: &BuildConfig) -> DfxResult { - self.builder.generate(pool, &self.info, build_config) + pub fn generate( + &self, + logger: &Logger, + pool: &CanisterPool, + build_config: &BuildConfig, + ) -> DfxResult { + self.builder + .generate(logger, pool, &self.info, build_config) } #[context("Failed to post-process wasm of canister '{}'.", self.info.get_name())] From a5e8144c4e8fd0a30fd8f4e05a0c7c50281a3584 Mon Sep 17 00:00:00 2001 From: mraszyk <31483726+mraszyk@users.noreply.github.com> Date: Wed, 20 Nov 2024 11:48:44 +0100 Subject: [PATCH 04/15] fix: address already in use error in dfx start (#4006) * fix: address already in use error in dfx start * unused dependency * lint --- Cargo.lock | 1 - src/dfx/Cargo.toml | 1 - src/dfx/src/util/mod.rs | 17 +---------------- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf50fb84af..8acb851d04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1590,7 +1590,6 @@ dependencies = [ "slog", "slog-async", "slog-term", - "socket2 0.5.7", "supports-color", "sysinfo", "tar", diff --git a/src/dfx/Cargo.toml b/src/dfx/Cargo.toml index 91ef990def..34a0f5480b 100644 --- a/src/dfx/Cargo.toml +++ b/src/dfx/Cargo.toml @@ -104,7 +104,6 @@ shell-words = "1.1.0" slog = { workspace = true, features = ["max_level_trace"] } slog-async.workspace = true slog-term.workspace = true -socket2 = "0.5.5" supports-color = "2.1.0" sysinfo = "0.28.4" tar.workspace = true diff --git a/src/dfx/src/util/mod.rs b/src/dfx/src/util/mod.rs index 073c4c5e14..c338b72d08 100644 --- a/src/dfx/src/util/mod.rs +++ b/src/dfx/src/util/mod.rs @@ -15,7 +15,6 @@ use idl2json::{idl2json, Idl2JsonOptions}; use num_traits::FromPrimitive; use reqwest::{Client, StatusCode, Url}; use rust_decimal::Decimal; -use socket2::{Domain, Socket}; use std::collections::BTreeMap; use std::io::{stderr, stdin, stdout, IsTerminal, Read}; use std::net::{IpAddr, SocketAddr, TcpListener}; @@ -35,22 +34,8 @@ const DECIMAL_POINT: char = '.'; // thus, we need to recreate SocketAddr with the kernel-provided dynamically allocated port here. #[context("Failed to find available socket address")] pub fn get_reusable_socket_addr(ip: IpAddr, port: u16) -> DfxResult { - let socket = if ip.is_ipv4() { - Socket::new(Domain::IPV4, socket2::Type::STREAM, None) - .context("Failed to create IPv4 socket.")? - } else { - Socket::new(Domain::IPV6, socket2::Type::STREAM, None) - .context("Failed to create IPv6 socket.")? - }; - socket - .set_linger(Some(Duration::from_secs(10))) - .context("Failed to set linger duration of tcp listener.")?; - socket - .bind(&SocketAddr::new(ip, port).into()) + let listener = TcpListener::bind(SocketAddr::new(ip, port)) .with_context(|| format!("Failed to bind socket to {}:{}.", ip, port))?; - socket.listen(128).context("Failed to listen on socket.")?; - - let listener: TcpListener = socket.into(); listener .local_addr() .context("Failed to fetch local address.") From 06afa6f6da401412c03dc2fe29d2f19d76760e9f Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Wed, 20 Nov 2024 10:55:53 -0500 Subject: [PATCH 05/15] feat: error when using insecure identity on mainnet (#4005) * error when using insecure identity on mainnet no warning on non-mainnet (playground, local replica) * changelog * surpress the error in irrelevant tests * fix typo --- CHANGELOG.md | 8 ++++++++ e2e/tests-dfx/canister_url.bash | 6 ++++-- e2e/tests-dfx/fabricate_cycles.bash | 2 ++ e2e/tests-dfx/identity.bash | 12 ++++++------ e2e/tests-dfx/network.bash | 8 ++++---- e2e/tests-dfx/sign_send.bash | 3 ++- src/dfx/src/lib/environment.rs | 22 +++++++++++++++++----- 7 files changed, 43 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8740bce112..a3ed50fef1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ # UNRELEASED +### feat: error when using insecure identity on mainnet + +This used to be a warning. A hard error can abort the command so that no insecure state will be on the mainnet. + +Users can surpress this error by setting `export DFX_WARNING=-mainnet_plaintext_identity`. + +The warning won't display when executing commands like `dfx deploy --playground`. + # 0.24.3 ### feat: Bitcoin support in PocketIC diff --git a/e2e/tests-dfx/canister_url.bash b/e2e/tests-dfx/canister_url.bash index 974c64627e..cd5f6f03d3 100644 --- a/e2e/tests-dfx/canister_url.bash +++ b/e2e/tests-dfx/canister_url.bash @@ -4,7 +4,9 @@ load ../utils/_ setup() { standard_setup - + # some of the tests run on mainnet with default plaintext identity + # so we need to set this to avoid the error + export DFX_WARNING=-mainnet_plaintext_identity dfx_new_assets hello } @@ -58,7 +60,7 @@ teardown() { echo "{}" > canister_ids.json jq '.hello_frontend.ic = "qsgof-4qaaa-aaaan-qekqq-cai"' canister_ids.json | sponge canister_ids.json frontend_id=$(dfx canister id hello_frontend --ic) - + assert_command dfx canister url hello_frontend --ic assert_match "https://${frontend_id}.icp0.io" diff --git a/e2e/tests-dfx/fabricate_cycles.bash b/e2e/tests-dfx/fabricate_cycles.bash index 82f5c61da5..6d811adb03 100644 --- a/e2e/tests-dfx/fabricate_cycles.bash +++ b/e2e/tests-dfx/fabricate_cycles.bash @@ -39,6 +39,8 @@ teardown() { @test "ledger fabricate-cycles fails on real IC" { install_asset greet + # without DFX_WARNING, the command would fail with different error (Failed to create AgentEnvironment...) + export DFX_WARNING=-mainnet_plaintext_identity assert_command_fail dfx ledger fabricate-cycles --all --network ic assert_match "Cannot run this on the real IC." assert_command_fail dfx ledger fabricate-cycles --all --ic diff --git a/e2e/tests-dfx/identity.bash b/e2e/tests-dfx/identity.bash index 291b720906..a6f8568e3a 100644 --- a/e2e/tests-dfx/identity.bash +++ b/e2e/tests-dfx/identity.bash @@ -186,15 +186,15 @@ teardown() { assert_eq '(blob "hello")' "$stdout" } -@test "using an unencrypted identity on mainnet provokes a warning" { - assert_command dfx ledger balance --network ic - assert_match "WARN: The default identity is not stored securely." "$stderr" +@test "using an unencrypted identity on mainnet provokes a hard error which can be suppressed" { + assert_command_fail dfx ledger balance --network ic + assert_match "The default identity is not stored securely." "$stderr" assert_command "${BATS_TEST_DIRNAME}/../assets/expect_scripts/init_alice_with_pw.exp" assert_command "${BATS_TEST_DIRNAME}/../assets/expect_scripts/get_ledger_balance.exp" dfx identity new bob --storage-mode plaintext - assert_command dfx ledger balance --network ic --identity bob - assert_match "WARN: The bob identity is not stored securely." "$stderr" - + assert_command_fail dfx ledger balance --network ic --identity bob + assert_match "The bob identity is not stored securely." "$stderr" + # can suppress the error export DFX_WARNING=-mainnet_plaintext_identity assert_command dfx ledger balance --network ic --identity bob assert_not_contains "not stored securely" "$stderr" diff --git a/e2e/tests-dfx/network.bash b/e2e/tests-dfx/network.bash index dcb12ac8da..857c3dfedc 100644 --- a/e2e/tests-dfx/network.bash +++ b/e2e/tests-dfx/network.bash @@ -99,13 +99,13 @@ teardown() { assert_command_fail dfx diagnose --network ic assert_contains "The test_id identity is not stored securely." - assert_contains "use it in mainnet-facing commands" - assert_contains "No wallet found; nothing to do" + assert_contains "in mainnet-facing commands" + assert_contains "you can suppress this warning" assert_command_fail dfx diagnose --ic assert_contains "The test_id identity is not stored securely." - assert_contains "use it in mainnet-facing commands" - assert_contains "No wallet found; nothing to do" + assert_contains "in mainnet-facing commands" + assert_contains "you can suppress this warning" assert_command dfx diagnose assert_not_contains "identity is not stored securely" diff --git a/e2e/tests-dfx/sign_send.bash b/e2e/tests-dfx/sign_send.bash index b266510a1e..5a828eb052 100644 --- a/e2e/tests-dfx/sign_send.bash +++ b/e2e/tests-dfx/sign_send.bash @@ -44,7 +44,8 @@ teardown() { cd "$E2E_TEMP_DIR" mkdir not-a-project-dir cd not-a-project-dir - + # suppress the error + export DFX_WARNING=-mainnet_plaintext_identity assert_command dfx canister sign --query rwlgt-iiaaa-aaaaa-aaaaa-cai read --network ic assert_match "Query message generated at \[message.json\]" } diff --git a/src/dfx/src/lib/environment.rs b/src/dfx/src/lib/environment.rs index 5b9ecc4881..6962f5d975 100644 --- a/src/dfx/src/lib/environment.rs +++ b/src/dfx/src/lib/environment.rs @@ -3,12 +3,12 @@ use crate::config::dfx_version; use crate::lib::error::DfxResult; use crate::lib::progress_bar::ProgressBar; use crate::lib::warning::{is_warning_disabled, DfxWarning::MainnetPlainTextIdentity}; -use anyhow::anyhow; +use anyhow::{anyhow, bail}; use candid::Principal; use dfx_core::config::cache::Cache; use dfx_core::config::model::canister_id_store::CanisterIdStore; use dfx_core::config::model::dfinity::{Config, NetworksConfig}; -use dfx_core::config::model::network_descriptor::NetworkDescriptor; +use dfx_core::config::model::network_descriptor::{NetworkDescriptor, NetworkTypeDescriptor}; use dfx_core::error::canister_id_store::CanisterIdStoreError; use dfx_core::error::identity::NewIdentityManagerError; use dfx_core::error::load_dfx_config::LoadDfxConfigError; @@ -17,7 +17,7 @@ use dfx_core::identity::identity_manager::{IdentityManager, InitializeIdentity}; use fn_error_context::context; use ic_agent::{Agent, Identity}; use semver::Version; -use slog::{warn, Logger, Record}; +use slog::{Logger, Record}; use std::borrow::Cow; use std::cell::RefCell; use std::path::PathBuf; @@ -288,11 +288,23 @@ impl<'a> AgentEnvironment<'a> { identity_manager.instantiate_selected_identity(&logger)? }; if network_descriptor.is_ic + && !matches!( + network_descriptor.r#type, + NetworkTypeDescriptor::Playground { .. } + ) && identity.insecure && !is_warning_disabled(MainnetPlainTextIdentity) { - warn!(logger, "The {} identity is not stored securely. Do not use it to control a lot of cycles/ICP. Create a new identity with `dfx identity new` \ - and use it in mainnet-facing commands with the `--identity` flag", identity.name()); + bail!( + "The {} identity is not stored securely. Do not use it to control a lot of cycles/ICP. +- For enhanced security, create a new identity using the command: + dfx identity new + Then, specify the new identity in mainnet-facing commands with the `--identity` flag. +- If you understand the risks and still wish to use the insecure plaintext identity, you can suppress this warning by running: + export DFX_WARNING=-mainnet_plaintext_identity + After setting this environment variable, re-run the command.", + identity.name() + ); } let url = network_descriptor.first_provider()?; let effective_canister_id = if let Some(d) = &network_descriptor.local_server_descriptor { From dd85c4e12640b12fd7519d39c307102d94fdf3cd Mon Sep 17 00:00:00 2001 From: mraszyk <31483726+mraszyk@users.noreply.github.com> Date: Wed, 20 Nov 2024 22:44:03 +0100 Subject: [PATCH 06/15] chore: stop HTTP gateway gracefully (#4009) * chore: stop HTTP gateway gracefully * windows --- src/dfx/src/actors/pocketic_proxy.rs | 60 ++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/src/dfx/src/actors/pocketic_proxy.rs b/src/dfx/src/actors/pocketic_proxy.rs index b41a04454f..8d5cc16526 100644 --- a/src/dfx/src/actors/pocketic_proxy.rs +++ b/src/dfx/src/actors/pocketic_proxy.rs @@ -284,23 +284,26 @@ fn pocketic_proxy_start_thread( } } }; - if let Err(e) = initialize_gateway( + let instance = match initialize_gateway( format!("http://localhost:{port}").parse().unwrap(), replica_url.clone(), domains.clone(), address, logger.clone(), ) { - error!(logger, "Failed to initialize HTTP gateway: {e:#}"); - let _ = child.kill(); - let _ = child.wait(); - if receiver.try_recv().is_ok() { - debug!(logger, "Got signal to stop."); - break; - } else { - continue; + Err(e) => { + error!(logger, "Failed to initialize HTTP gateway: {e:#}"); + let _ = child.kill(); + let _ = child.wait(); + if receiver.try_recv().is_ok() { + debug!(logger, "Got signal to stop."); + break; + } else { + continue; + } } - } + Ok(i) => i, + }; info!(logger, "Replica API running on {address}"); // Send PocketIcProxyReadySignal to PocketIcProxy. @@ -314,6 +317,9 @@ fn pocketic_proxy_start_thread( logger, "Got signal to stop. Killing pocket-ic gateway process..." ); + if let Err(e) = shutdown_pocketic_proxy(port, instance, logger.clone()) { + error!(logger, "Error shutting down PocketIC gracefully: {e}"); + } let _ = child.kill(); let _ = child.wait(); break; @@ -349,7 +355,7 @@ async fn initialize_gateway( domains: Option>, addr: SocketAddr, logger: Logger, -) -> DfxResult { +) -> DfxResult { use pocket_ic::common::rest::{ CreateHttpGatewayResponse, HttpGatewayBackend, HttpGatewayConfig, }; @@ -369,11 +375,12 @@ async fn initialize_gateway( .await? .error_for_status()?; let resp = resp.json::().await?; - if let CreateHttpGatewayResponse::Error { message } = resp { - bail!("Gateway init error: {message}") - } + let instance = match resp { + CreateHttpGatewayResponse::Created(info) => info.instance_id, + CreateHttpGatewayResponse::Error { message } => bail!("Gateway init error: {message}"), + }; info!(logger, "Initialized HTTP gateway."); - Ok(()) + Ok(instance) } #[cfg(not(unix))] @@ -383,6 +390,27 @@ fn initialize_gateway( _: Option>, _: SocketAddr, _: Logger, -) -> DfxResult { +) -> DfxResult { bail!("PocketIC gateway not supported on this platform") } + +#[cfg(unix)] +#[tokio::main(flavor = "current_thread")] +async fn shutdown_pocketic_proxy(port: u16, instance: usize, logger: Logger) -> DfxResult { + use reqwest::Client; + let shutdown_client = Client::new(); + debug!(logger, "Sending shutdown request to HTTP gateway"); + shutdown_client + .post(format!( + "http://localhost:{port}/http_gateway/{instance}/stop" + )) + .send() + .await? + .error_for_status()?; + Ok(()) +} + +#[cfg(not(unix))] +fn shutdown_pocketic_proxy(_: u16, _: usize, _: Logger) -> DfxResult { + bail!("PocketIC not supported on this platform") +} From 1c22db89ac1f9f96384902c91aa27bac1c1eff36 Mon Sep 17 00:00:00 2001 From: Severin Siffert Date: Thu, 21 Nov 2024 10:53:50 +0100 Subject: [PATCH 07/15] chore: fix dfx cycles convert docs (#4010) --- docs/cli-reference/dfx-cycles.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/cli-reference/dfx-cycles.mdx b/docs/cli-reference/dfx-cycles.mdx index 788764808e..8982569289 100644 --- a/docs/cli-reference/dfx-cycles.mdx +++ b/docs/cli-reference/dfx-cycles.mdx @@ -18,8 +18,10 @@ The following subcommands are available: | Command | Description | |---------------------------------------|--------------------------------------------------------------------------------------| +| [`approve`](#dfx-cycles-approve) | Approves a principal to spend cycles on your behalf. | | [`balance`](#dfx-cycles-balance) | Prints the account balance of the user. | | [`convert`](#dfx-cycles-convert) | Convert some of the user's ICP balance into cycles. | +| [`top-up`](#dfx-cycles-top-up) | Deposit cycles into a canister. | | [`transfer`](#dfx-cycles-transfer) | Send cycles to another account. | | `help` | Displays usage information message for a specified subcommand. | @@ -135,7 +137,7 @@ You can specify the following arguments for the `dfx cycles convert` command. | `--e8s ` | Specify ICP token fractional units—called e8s—as a whole number, where one e8 is the smallest fraction of an ICP token. For example, 1.05000000 is 1 ICP and 5000000 e8s. You can use this option on its own or in conjunction with the `--icp` option. | | `--fee ` | Specify a transaction fee. The default is 10000 e8s. | | `--icp ` | Specify ICP tokens as a whole number. You can use this option on its own or in conjunction with `--e8s`. | -| `--memo ` | Memo used when depositing the minted cycles. | +| `--deposit-memo ` | Memo used when depositing the minted cycles. | | `--to-subaccount ` | Subaccount where the cycles are deposited. | ### Examples From 8cb62ce7afa65a7de23640843a5c69d349f255b6 Mon Sep 17 00:00:00 2001 From: Severin Siffert Date: Fri, 22 Nov 2024 16:30:02 +0100 Subject: [PATCH 08/15] feat: improve missing ledger error message (#3995) --- CHANGELOG.md | 4 ++-- e2e/tests-dfx/ledger.bash | 30 +++++++++++++++++++++++++----- src/dfx/src/lib/diagnosis.rs | 16 ++++++++++++++++ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3ed50fef1..d76bb09abb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,9 +41,9 @@ Allow setting permissions lists in init arguments just like in upgrade arguments - Module hash: f45db224b40fac516c877e3108dc809d4b22fa42d05ee8dfa5002536a3a3daed - Bump agent-js to fix error code -### chore!: improve the messages for the subcommands of `dfx cycles`. +### chore!: improve the messages for the subcommands of `dfx cycles` and `dfx ledger`. -If users run subcommands of `dfx cycles` without the `--ic` flag, show below messages to indicate what to do next. +If users run subcommands of `dfx cycles` or `dfx ledger` without the `--ic` flag, show below messages to indicate what to do next. ``` Error explanation: Cycles ledger with canister ID 'um5iw-rqaaa-aaaaq-qaaba-cai' is not installed. diff --git a/e2e/tests-dfx/ledger.bash b/e2e/tests-dfx/ledger.bash index f41100b847..ea5fa40567 100644 --- a/e2e/tests-dfx/ledger.bash +++ b/e2e/tests-dfx/ledger.bash @@ -2,6 +2,13 @@ load ../utils/_ +install_nns() { + dfx_start_for_nns_install + + dfx extension install nns --version 0.4.3 + dfx nns install --ledger-accounts 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752 22ca7edac648b814e81d7946e8bacea99280e07c5f51a04ba7a38009d8ad8e89 5a94fe181e9d411c58726cb87cbf2d016241b6c350bc3330e4869ca76e54ecbc +} + setup() { standard_setup install_asset ledger @@ -9,11 +16,6 @@ setup() { dfx identity import --storage-mode plaintext alice alice.pem dfx identity import --storage-mode plaintext bob bob.pem - - dfx_start_for_nns_install - - dfx extension install nns --version 0.4.3 - dfx nns install --ledger-accounts 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752 22ca7edac648b814e81d7946e8bacea99280e07c5f51a04ba7a38009d8ad8e89 5a94fe181e9d411c58726cb87cbf2d016241b6c350bc3330e4869ca76e54ecbc } teardown() { @@ -27,6 +29,8 @@ current_time_nanoseconds() { } @test "ledger account-id" { + install_nns + dfx identity use alice assert_command dfx ledger account-id assert_match 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752 @@ -46,6 +50,8 @@ current_time_nanoseconds() { } @test "ledger balance & transfer" { + install_nns + dfx identity use alice assert_command dfx ledger account-id assert_eq 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752 @@ -104,6 +110,8 @@ current_time_nanoseconds() { } @test "ledger subaccounts" { + install_nns + subacct=000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f assert_command dfx ledger account-id --identity bob --subaccount "$subacct" assert_match 5a94fe181e9d411c58726cb87cbf2d016241b6c350bc3330e4869ca76e54ecbc @@ -140,6 +148,8 @@ tc_to_num() { } @test "ledger top-up" { + install_nns + dfx identity use alice assert_command dfx ledger balance assert_match "1000000000.00000000 ICP" @@ -198,6 +208,8 @@ tc_to_num() { } @test "ledger create-canister" { + install_nns + dfx identity use alice assert_command dfx ledger create-canister --amount=100 --subnet-type "type1" "$(dfx identity get-principal)" assert_match "Transfer sent at block height" @@ -269,6 +281,7 @@ tc_to_num() { } @test "ledger show-subnet-types" { + install_nns install_asset cmc dfx deploy cmc @@ -278,3 +291,10 @@ tc_to_num() { assert_command dfx ledger show-subnet-types --cycles-minting-canister-id "$CANISTER_ID" assert_eq '["type1", "type2"]' } + +@test "balance without ledger fails as expected" { + dfx_start + + assert_command_fail dfx ledger balance + assert_contains "ICP Ledger with canister ID 'ryjl3-tyaaa-aaaaa-aaaba-cai' is not installed." +} diff --git a/src/dfx/src/lib/diagnosis.rs b/src/dfx/src/lib/diagnosis.rs index e59a810da7..150f1c9b56 100644 --- a/src/dfx/src/lib/diagnosis.rs +++ b/src/dfx/src/lib/diagnosis.rs @@ -57,6 +57,9 @@ pub fn diagnose(err: &AnyhowError) -> Diagnosis { if cycles_ledger_not_found(err) { return diagnose_cycles_ledger_not_found(); } + if ledger_not_found(err) { + return diagnose_ledger_not_found(); + } } if local_replica_not_running(err) { @@ -246,3 +249,16 @@ fn diagnose_cycles_ledger_not_found() -> Diagnosis { (Some(explanation.to_string()), Some(suggestion.to_string())) } + +fn ledger_not_found(err: &AnyhowError) -> bool { + err.to_string() + .contains("Canister ryjl3-tyaaa-aaaaa-aaaba-cai not found") +} + +fn diagnose_ledger_not_found() -> Diagnosis { + let explanation = "ICP Ledger with canister ID 'ryjl3-tyaaa-aaaaa-aaaba-cai' is not installed."; + let suggestion = + "Run the command with '--ic' flag if you want to manage the ICP on the mainnet."; + + (Some(explanation.to_string()), Some(suggestion.to_string())) +} From 74b110a10d194bee1db2c801ea44802815fd9862 Mon Sep 17 00:00:00 2001 From: mraszyk <31483726+mraszyk@users.noreply.github.com> Date: Fri, 22 Nov 2024 18:43:07 +0100 Subject: [PATCH 09/15] feat: PocketIC supports force and shared network (#4002) --- CHANGELOG.md | 4 ++++ .../src/config/model/settings_digest.rs | 17 +++++++---------- src/dfx/src/commands/start.rs | 4 ++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d76bb09abb..8ecc8f39df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ # UNRELEASED +### feat: `dfx start --pocketic` supports `--force` and shared networks. + +`dfx start --pocketic` is now compatible with `--force` and shared networks. + ### feat: error when using insecure identity on mainnet This used to be a warning. A hard error can abort the command so that no insecure state will be on the mainnet. diff --git a/src/dfx-core/src/config/model/settings_digest.rs b/src/dfx-core/src/config/model/settings_digest.rs index bdbfda9280..7daceed3ae 100644 --- a/src/dfx-core/src/config/model/settings_digest.rs +++ b/src/dfx-core/src/config/model/settings_digest.rs @@ -41,10 +41,9 @@ struct ReplicaSettings { } #[derive(Serialize, Deserialize, PartialEq, Eq)] -#[serde(tag = "type", rename_all = "snake_case")] -enum BackendSettings<'a> { - Replica { settings: Cow<'a, ReplicaSettings> }, - PocketIc, +struct BackendSettings<'a> { + settings: Cow<'a, ReplicaSettings>, + pocketic: bool, } #[derive(Serialize, Deserialize, PartialEq, Eq)] @@ -60,11 +59,7 @@ pub fn get_settings_digest( artificial_delay: u32, pocketic: bool, ) -> String { - let backend = if pocketic { - BackendSettings::PocketIc - } else { - get_replica_backend_settings(local_server_descriptor, artificial_delay) - }; + let backend = get_replica_backend_settings(local_server_descriptor, artificial_delay, pocketic); let settings = Settings { ic_repo_commit: ic_repo_commit.into(), backend, @@ -77,6 +72,7 @@ pub fn get_settings_digest( fn get_replica_backend_settings( local_server_descriptor: &LocalServerDescriptor, artificial_delay: u32, + pocketic: bool, ) -> BackendSettings { let http_handler = HttpHandlerSettings { port: if let Some(port) = local_server_descriptor.replica.port { @@ -105,7 +101,8 @@ fn get_replica_backend_settings( .unwrap_or_default(), artificial_delay, }; - BackendSettings::Replica { + BackendSettings { settings: Cow::Owned(replica_settings), + pocketic, } } diff --git a/src/dfx/src/commands/start.rs b/src/dfx/src/commands/start.rs index 9ba08000d9..ea9270e9b3 100644 --- a/src/dfx/src/commands/start.rs +++ b/src/dfx/src/commands/start.rs @@ -75,7 +75,7 @@ pub struct StartOpts { artificial_delay: u32, /// Start even if the network config was modified. - #[arg(long, conflicts_with = "pocketic")] + #[arg(long)] force: bool, /// A list of domains that can be served. These are used for canister resolution [default: localhost] @@ -338,7 +338,7 @@ pub fn exec( &local_server_descriptor.scope, LocalNetworkScopeDescriptor::Shared { .. } ); - if is_shared_network && !pocketic { + if is_shared_network { save_json_file( &local_server_descriptor.effective_config_path_by_settings_digest(), &effective_config, From 09180b794c66905f1c2d7c68261ff1b5f6ba931b Mon Sep 17 00:00:00 2001 From: Severin Siffert Date: Mon, 25 Nov 2024 11:44:56 +0100 Subject: [PATCH 10/15] feat: support --replica for dfx start (#4014) --- CHANGELOG.md | 6 ++++++ docs/cli-reference/dfx-start.mdx | 1 + src/dfx/src/commands/start.rs | 7 +++++++ 3 files changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ecc8f39df..5396853a28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ Users can surpress this error by setting `export DFX_WARNING=-mainnet_plaintext_ The warning won't display when executing commands like `dfx deploy --playground`. +### feat: support `--replica` in `dfx start` + +Added a flag `--replica` to `dfx start`. This flag currently has no effect. +Once PocketIC becomes the default for `dfx start` this flag will start the replica instead. +You can use the `--replica` flag already to write scripts that anticipate that change. + # 0.24.3 ### feat: Bitcoin support in PocketIC diff --git a/docs/cli-reference/dfx-start.mdx b/docs/cli-reference/dfx-start.mdx index 515400c796..87f6afae8e 100644 --- a/docs/cli-reference/dfx-start.mdx +++ b/docs/cli-reference/dfx-start.mdx @@ -25,6 +25,7 @@ You can use the following optional flags with the `dfx start` command. | `--enable-bitcoin` | Enables bitcoin integration. | | `--enable-canister-http` | Enables canister HTTP requests. (deprecated: now enabled by default) | | `--pocketic` | Runs [PocketIC](https://github.com/dfinity/pocketic) instead of the replica. | +| `--replica` | Runs the replica instead of [PocketIC](https://github.com/dfinity/pocketic). | ## Options diff --git a/src/dfx/src/commands/start.rs b/src/dfx/src/commands/start.rs index ea9270e9b3..768f6d286b 100644 --- a/src/dfx/src/commands/start.rs +++ b/src/dfx/src/commands/start.rs @@ -85,6 +85,12 @@ pub struct StartOpts { /// Runs PocketIC instead of the replica #[clap(long, alias = "emulator")] pocketic: bool, + + /// Runs the replica instead of pocketic. + /// Currently this has no effect. + #[clap(long, conflicts_with = "pocketic")] + #[allow(unused)] + replica: bool, } // The frontend webserver is brought up by the bg process; thus, the fg process @@ -152,6 +158,7 @@ pub fn exec( artificial_delay, domain, pocketic, + replica: _, }: StartOpts, ) -> DfxResult { if !background { From 1acb8fbfdf3026e6bde6b0ba42fb3f79df851a12 Mon Sep 17 00:00:00 2001 From: Eric Swanson <64809312+ericswanson-dfinity@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:18:04 -0800 Subject: [PATCH 11/15] refactor: Category -> ProjectTemplateCategory (#4015) --- src/dfx-core/src/config/model/mod.rs | 1 + .../src/config/model/project_template.rs | 8 ++++ src/dfx-core/src/config/project_templates.rs | 16 ++------ src/dfx/src/commands/new.rs | 3 +- src/dfx/src/lib/project/templates.rs | 37 +++++++++---------- 5 files changed, 33 insertions(+), 32 deletions(-) create mode 100644 src/dfx-core/src/config/model/project_template.rs diff --git a/src/dfx-core/src/config/model/mod.rs b/src/dfx-core/src/config/model/mod.rs index 2014e4ae34..b29410f073 100644 --- a/src/dfx-core/src/config/model/mod.rs +++ b/src/dfx-core/src/config/model/mod.rs @@ -5,5 +5,6 @@ pub mod dfinity; pub mod extension_canister_type; pub mod local_server_descriptor; pub mod network_descriptor; +pub mod project_template; pub mod replica_config; pub mod settings_digest; diff --git a/src/dfx-core/src/config/model/project_template.rs b/src/dfx-core/src/config/model/project_template.rs new file mode 100644 index 0000000000..f35ca7e399 --- /dev/null +++ b/src/dfx-core/src/config/model/project_template.rs @@ -0,0 +1,8 @@ +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum ProjectTemplateCategory { + Backend, + Frontend, + FrontendTest, + Extra, + Support, +} diff --git a/src/dfx-core/src/config/project_templates.rs b/src/dfx-core/src/config/project_templates.rs index 28c5565131..9febc449f2 100644 --- a/src/dfx-core/src/config/project_templates.rs +++ b/src/dfx-core/src/config/project_templates.rs @@ -1,3 +1,4 @@ +use crate::config::model::project_template::ProjectTemplateCategory; use itertools::Itertools; use std::collections::BTreeMap; use std::fmt::Display; @@ -12,15 +13,6 @@ pub enum ResourceLocation { Bundled { get_archive_fn: GetArchiveFn }, } -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum Category { - Backend, - Frontend, - FrontendTest, - Extra, - Support, -} - #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct ProjectTemplateName(pub String); @@ -44,7 +36,7 @@ pub struct ProjectTemplate { /// Used to determine which CLI group (`--type`, `--backend`, `--frontend`) /// as well as for interactive selection - pub category: Category, + pub category: ProjectTemplateCategory, /// Other project templates to patch in alongside this one pub requirements: Vec, @@ -99,7 +91,7 @@ pub fn find_project_template(name: &ProjectTemplateName) -> Option Vec { +pub fn get_sorted_templates(category: ProjectTemplateCategory) -> Vec { PROJECT_TEMPLATES .get() .unwrap() @@ -114,7 +106,7 @@ pub fn get_sorted_templates(category: Category) -> Vec { .collect() } -pub fn project_template_cli_names(category: Category) -> Vec { +pub fn project_template_cli_names(category: ProjectTemplateCategory) -> Vec { PROJECT_TEMPLATES .get() .unwrap() diff --git a/src/dfx/src/commands/new.rs b/src/dfx/src/commands/new.rs index 08e67b7256..093fe23805 100644 --- a/src/dfx/src/commands/new.rs +++ b/src/dfx/src/commands/new.rs @@ -11,9 +11,10 @@ use anyhow::{anyhow, bail, ensure, Context, Error}; use clap::builder::PossibleValuesParser; use clap::Parser; use console::{style, Style}; +use dfx_core::config::model::project_template::ProjectTemplateCategory as Category; use dfx_core::config::project_templates::{ find_project_template, get_project_template, get_sorted_templates, project_template_cli_names, - Category, ProjectTemplate, ProjectTemplateName, ResourceLocation, + ProjectTemplate, ProjectTemplateName, ResourceLocation, }; use dfx_core::json::{load_json_file, save_json_file}; use dialoguer::theme::ColorfulTheme; diff --git a/src/dfx/src/lib/project/templates.rs b/src/dfx/src/lib/project/templates.rs index 364556a504..8f52cc8ec9 100644 --- a/src/dfx/src/lib/project/templates.rs +++ b/src/dfx/src/lib/project/templates.rs @@ -1,7 +1,6 @@ use crate::util::assets; -use dfx_core::config::project_templates::{ - Category, ProjectTemplate, ProjectTemplateName, ResourceLocation, -}; +use dfx_core::config::model::project_template::ProjectTemplateCategory; +use dfx_core::config::project_templates::{ProjectTemplate, ProjectTemplateName, ResourceLocation}; const NPM_INSTALL: &str = "npm install --quiet --no-progress --workspaces --if-present"; const NPM_INSTALL_SPINNER_MESSAGE: &str = "Installing node dependencies..."; @@ -16,7 +15,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_motoko_files, }, - category: Category::Backend, + category: ProjectTemplateCategory::Backend, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -30,7 +29,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_rust_files, }, - category: Category::Backend, + category: ProjectTemplateCategory::Backend, post_create: vec!["cargo update".to_string()], post_create_failure_warning: Some(CARGO_UPDATE_FAILURE_MESSAGE.to_string()), post_create_spinner_message: None, @@ -44,7 +43,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_azle_files, }, - category: Category::Backend, + category: ProjectTemplateCategory::Backend, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -58,7 +57,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_kybra_files, }, - category: Category::Backend, + category: ProjectTemplateCategory::Backend, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -72,7 +71,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_svelte_files, }, - category: Category::Frontend, + category: ProjectTemplateCategory::Frontend, post_create: vec![NPM_INSTALL.to_string()], post_create_failure_warning: Some(NPM_INSTALL_FAILURE_WARNING.to_string()), post_create_spinner_message: Some(NPM_INSTALL_SPINNER_MESSAGE.to_string()), @@ -86,7 +85,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_react_files, }, - category: Category::Frontend, + category: ProjectTemplateCategory::Frontend, post_create: vec![NPM_INSTALL.to_string()], post_create_failure_warning: Some(NPM_INSTALL_FAILURE_WARNING.to_string()), post_create_spinner_message: Some(NPM_INSTALL_SPINNER_MESSAGE.to_string()), @@ -100,7 +99,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_vue_files, }, - category: Category::Frontend, + category: ProjectTemplateCategory::Frontend, post_create: vec![NPM_INSTALL.to_string()], post_create_failure_warning: Some(NPM_INSTALL_FAILURE_WARNING.to_string()), post_create_spinner_message: Some(NPM_INSTALL_SPINNER_MESSAGE.to_string()), @@ -114,7 +113,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_vanillajs_files, }, - category: Category::Frontend, + category: ProjectTemplateCategory::Frontend, post_create: vec![NPM_INSTALL.to_string()], post_create_failure_warning: Some(NPM_INSTALL_FAILURE_WARNING.to_string()), post_create_spinner_message: Some(NPM_INSTALL_SPINNER_MESSAGE.to_string()), @@ -128,7 +127,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_assets_files, }, - category: Category::Frontend, + category: ProjectTemplateCategory::Frontend, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -142,7 +141,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_svelte_test_files, }, - category: Category::FrontendTest, + category: ProjectTemplateCategory::FrontendTest, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -156,7 +155,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_react_test_files, }, - category: Category::FrontendTest, + category: ProjectTemplateCategory::FrontendTest, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -170,7 +169,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_vue_test_files, }, - category: Category::FrontendTest, + category: ProjectTemplateCategory::FrontendTest, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -184,7 +183,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_vanillajs_test_files, }, - category: Category::FrontendTest, + category: ProjectTemplateCategory::FrontendTest, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -198,7 +197,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_internet_identity_files, }, - category: Category::Extra, + category: ProjectTemplateCategory::Extra, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -212,7 +211,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_bitcoin_files, }, - category: Category::Extra, + category: ProjectTemplateCategory::Extra, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, @@ -226,7 +225,7 @@ pub fn builtin_templates() -> Vec { resource_location: ResourceLocation::Bundled { get_archive_fn: assets::new_project_js_files, }, - category: Category::Support, + category: ProjectTemplateCategory::Support, post_create: vec![], post_create_failure_warning: None, post_create_spinner_message: None, From e05c888d5652832c8c4bb9fbd7b736d21144443d Mon Sep 17 00:00:00 2001 From: Eric Swanson <64809312+ericswanson-dfinity@users.noreply.github.com> Date: Tue, 26 Nov 2024 04:05:25 -0800 Subject: [PATCH 12/15] feat: extension defined project templates (#4012) --- CHANGELOG.md | 5 + .../extension-defined-project-templates.md | 66 +++++++++ docs/concepts/index.md | 2 + docs/extension-manifest-schema.json | 84 ++++++++++++ e2e/tests-dfx/extension.bash | 127 ++++++++++++++++++ e2e/utils/_.bash | 8 +- .../src/config/model/project_template.rs | 7 +- src/dfx-core/src/config/project_templates.rs | 13 +- src/dfx-core/src/extension/installed.rs | 13 ++ .../src/extension/manifest/extension.rs | 82 ++++++++++- src/dfx/src/commands/new.rs | 60 ++++++++- src/dfx/src/main.rs | 6 +- 12 files changed, 459 insertions(+), 14 deletions(-) create mode 100644 docs/concepts/extension-defined-project-templates.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5396853a28..c2e6e01994 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,11 @@ Added a flag `--replica` to `dfx start`. This flag currently has no effect. Once PocketIC becomes the default for `dfx start` this flag will start the replica instead. You can use the `--replica` flag already to write scripts that anticipate that change. +### feat: extensions can define project templates + +An extension can define one or more project templates for `dfx new` to use. +These can be new templates or replace the built-in project templates. + # 0.24.3 ### feat: Bitcoin support in PocketIC diff --git a/docs/concepts/extension-defined-project-templates.md b/docs/concepts/extension-defined-project-templates.md new file mode 100644 index 0000000000..b66789221a --- /dev/null +++ b/docs/concepts/extension-defined-project-templates.md @@ -0,0 +1,66 @@ +# Extension-Defined Project Templates + +## Overview + +An extension can define one or more project templates for `dfx new` to use. + +A project template is a set of files that `dfx new` copies or patches into a new project. + +For examples of project template files, see the [project_templates] directory in the SDK repository. + +# Specification + +The `project_templates` field in an extension's `extension.json` defines the project templates +included in the extension. It is an object field mapping `project template name -> project template properties`. +These are the properties of a project template: + +| Field | Type | Description | +|------------------------------|---------------------------|------------------------------------------------------------------------------------------------------| +| `display` | String | Display name of the project template | +| `category` | String | Category for inclusion in `--backend` and `--frontend` CLI options, as well as interactive selection | +| `requirements` | Array of String | Required project templates | +| `post_create` | String or Array of String | Command(s) to run after adding the canister to the project | +| `post_create_spinner_message` | String | Message to display while running the post_create command | +| `post_create_failure_warning` | String | Warning to display if the post_create command fails | + +Within the files distributed with the extension, the project template files are +located in the `project_templates/{project template name}` directory. + +## The `display` field + +The `display` field is a string that describes the project template. +`dfx new` will use this value for interactive selection of project templates. + +## The `category` field + +The `category` field is an array of strings that categorize the project template. +`dfx new` uses this field to determine whether to include this project template +as an option for the `--backend` and `-frontend` flags, as well as in interactive setup. + +Valid values for the field: +- `frontend` +- `backend` +- `extra` +- `frontend-test` +- `support` + +## The `requirements` field + +The `requirements` field lists any project templates that `dfx new` must apply before this project template. +For example, many of the frontend templates depend on the `dfx_js_base` template, which adds +package.json and tsconfig.json to the project. + +## The `post_create` field + +The `post_create` field specifies a command or commands to run after adding the project template files to the project. +For example, the rust project template runs `cargo update` after adding the files. + +## The `post_create_spinner_message` field + +If this field is set, `dfx new` will display a spinner with this message while running the `post_create` command. + +## The `post_create_failure_warning` field + +If this field is present and the `post_create` command fails, `dfx new` will display this warning but won't stop creating the project. + +[project_templates]: https://github.com/dfinity/sdk/tree/master/src/dfx/assets/project_templates diff --git a/docs/concepts/index.md b/docs/concepts/index.md index 4f45495c5b..cf91724f81 100644 --- a/docs/concepts/index.md +++ b/docs/concepts/index.md @@ -2,3 +2,5 @@ - [Asset Canister Interface](../design/asset-canister-interface.md) - [Canister metadata](./canister-metadata.md) +- [Extension-Defined Canister Types](./extension-defined-canister-types.md) +- [Extension-Defined Project Templates](./extension-defined-project-templates.md) diff --git a/docs/extension-manifest-schema.json b/docs/extension-manifest-schema.json index beaff0a894..544823b5cb 100644 --- a/docs/extension-manifest-schema.json +++ b/docs/extension-manifest-schema.json @@ -70,6 +70,15 @@ "name": { "type": "string" }, + "project_templates": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/ExtensionProjectTemplate" + } + }, "subcommands": { "anyOf": [ { @@ -155,6 +164,58 @@ } ] }, + "ExtensionProjectTemplate": { + "type": "object", + "required": [ + "category", + "display", + "post_create", + "requirements" + ], + "properties": { + "category": { + "description": "Used to determine which CLI group (`--type`, `--backend`, `--frontend`) as well as for interactive selection", + "allOf": [ + { + "$ref": "#/definitions/ProjectTemplateCategory" + } + ] + }, + "display": { + "description": "The name used for display and sorting", + "type": "string" + }, + "post_create": { + "description": "Run a command after adding the canister to dfx.json", + "allOf": [ + { + "$ref": "#/definitions/SerdeVec_for_String" + } + ] + }, + "post_create_failure_warning": { + "description": "If the post-create command fails, display this warning but don't fail", + "type": [ + "string", + "null" + ] + }, + "post_create_spinner_message": { + "description": "If set, display a spinner while this command runs", + "type": [ + "string", + "null" + ] + }, + "requirements": { + "description": "Other project templates to patch in alongside this one", + "type": "array", + "items": { + "type": "string" + } + } + } + }, "ExtensionSubcommandArgOpts": { "type": "object", "properties": { @@ -231,6 +292,16 @@ "$ref": "#/definitions/ExtensionSubcommandOpts" } }, + "ProjectTemplateCategory": { + "type": "string", + "enum": [ + "backend", + "frontend", + "frontend-test", + "extra", + "support" + ] + }, "Range_of_uint": { "type": "object", "required": [ @@ -249,6 +320,19 @@ "minimum": 0.0 } } + }, + "SerdeVec_for_String": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] } } } \ No newline at end of file diff --git a/e2e/tests-dfx/extension.bash b/e2e/tests-dfx/extension.bash index 7aa52b2122..b4059ffab3 100644 --- a/e2e/tests-dfx/extension.bash +++ b/e2e/tests-dfx/extension.bash @@ -14,6 +14,133 @@ teardown() { standard_teardown } +@test "extension-defined project template" { + start_webserver --directory www + EXTENSION_URL="http://localhost:$E2E_WEB_SERVER_PORT/arbitrary/extension.json" + mkdir -p www/arbitrary/downloads www/arbitrary/project_templates/a-template + + cat > www/arbitrary/extension.json < www/arbitrary/dependencies.json <=0.8.0" + } + } +} +EOF + + cp -R "${BATS_TEST_DIRNAME}/../../src/dfx/assets/project_templates/rust" www/arbitrary/project_templates/rust-by-extension + + ARCHIVE_BASENAME="an-extension-v0.1.0" + + mkdir "$ARCHIVE_BASENAME" + cp www/arbitrary/extension.json "$ARCHIVE_BASENAME" + cp -R www/arbitrary/project_templates "$ARCHIVE_BASENAME" + tar -czf "$ARCHIVE_BASENAME".tar.gz "$ARCHIVE_BASENAME" + rm -rf "$ARCHIVE_BASENAME" + + mv "$ARCHIVE_BASENAME".tar.gz www/arbitrary/downloads/ + + assert_command dfx extension install "$EXTENSION_URL" + + setup_rust + + dfx new rbe --type rust-by-extension --no-frontend + cd rbe || exit + + dfx_start + assert_command dfx deploy + assert_command dfx canister call rbe_backend greet '("Rust By Extension")' + assert_contains "Hello, Rust By Extension!" +} + +@test "extension-defined project template replaces built-in type" { + start_webserver --directory www + EXTENSION_URL="http://localhost:$E2E_WEB_SERVER_PORT/arbitrary/extension.json" + mkdir -p www/arbitrary/downloads www/arbitrary/project_templates/a-template + + cat > www/arbitrary/extension.json < www/arbitrary/dependencies.json <=0.8.0" + } + } +} +EOF + + cp -R "${BATS_TEST_DIRNAME}/../../src/dfx/assets/project_templates/rust" www/arbitrary/project_templates/rust + echo "just-proves-it-used-the-project-template" > www/arbitrary/project_templates/rust/proof.txt + + ARCHIVE_BASENAME="an-extension-v0.1.0" + + mkdir "$ARCHIVE_BASENAME" + cp www/arbitrary/extension.json "$ARCHIVE_BASENAME" + cp -R www/arbitrary/project_templates "$ARCHIVE_BASENAME" + tar -czf "$ARCHIVE_BASENAME".tar.gz "$ARCHIVE_BASENAME" + rm -rf "$ARCHIVE_BASENAME" + + mv "$ARCHIVE_BASENAME".tar.gz www/arbitrary/downloads/ + + assert_command dfx extension install "$EXTENSION_URL" + + setup_rust + + dfx new rbe --type rust --no-frontend + assert_command cat rbe/proof.txt + assert_eq "just-proves-it-used-the-project-template" + + cd rbe || exit + + dfx_start + assert_command dfx deploy + assert_command dfx canister call rbe_backend greet '("Rust By Extension")' + assert_contains "Hello, Rust By Extension!" +} + @test "run an extension command with a canister type defined by another extension" { install_shared_asset subnet_type/shared_network_settings/system dfx_start_for_nns_install diff --git a/e2e/utils/_.bash b/e2e/utils/_.bash index 5ec7b8466e..889253e68a 100644 --- a/e2e/utils/_.bash +++ b/e2e/utils/_.bash @@ -82,10 +82,14 @@ dfx_new() { echo PWD: "$(pwd)" >&2 } -dfx_new_rust() { - local project_name=${1:-e2e_project} +setup_rust() { rustup default stable rustup target add wasm32-unknown-unknown +} + +dfx_new_rust() { + local project_name=${1:-e2e_project} + setup_rust dfx new "${project_name}" --type=rust --no-frontend test -d "${project_name}" test -f "${project_name}/dfx.json" diff --git a/src/dfx-core/src/config/model/project_template.rs b/src/dfx-core/src/config/model/project_template.rs index f35ca7e399..3dfd00a7b1 100644 --- a/src/dfx-core/src/config/model/project_template.rs +++ b/src/dfx-core/src/config/model/project_template.rs @@ -1,7 +1,12 @@ -#[derive(Debug, Clone, Eq, PartialEq)] +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] pub enum ProjectTemplateCategory { Backend, Frontend, + #[serde(rename = "frontend-test")] FrontendTest, Extra, Support, diff --git a/src/dfx-core/src/config/project_templates.rs b/src/dfx-core/src/config/project_templates.rs index 9febc449f2..d6194a7a5d 100644 --- a/src/dfx-core/src/config/project_templates.rs +++ b/src/dfx-core/src/config/project_templates.rs @@ -3,6 +3,7 @@ use itertools::Itertools; use std::collections::BTreeMap; use std::fmt::Display; use std::io; +use std::path::PathBuf; use std::sync::OnceLock; type GetArchiveFn = fn() -> Result>, io::Error>; @@ -11,6 +12,9 @@ type GetArchiveFn = fn() -> Result; static PROJECT_TEMPLATES: OnceLock = OnceLock::new(); -pub fn populate(builtin_templates: Vec) { - let templates = builtin_templates - .iter() - .map(|t| (t.name.clone(), t.clone())) +pub fn populate(builtin_templates: Vec, loaded_templates: Vec) { + let templates: ProjectTemplates = builtin_templates + .into_iter() + .map(|t| (t.name.clone(), t)) + .chain(loaded_templates.into_iter().map(|t| (t.name.clone(), t))) .collect(); PROJECT_TEMPLATES.set(templates).unwrap(); diff --git a/src/dfx-core/src/extension/installed.rs b/src/dfx-core/src/extension/installed.rs index 4a85ba598b..c701b3949c 100644 --- a/src/dfx-core/src/extension/installed.rs +++ b/src/dfx-core/src/extension/installed.rs @@ -1,4 +1,6 @@ +use crate::config::project_templates::ProjectTemplate; use crate::error::extension::ConvertExtensionIntoClapCommandError; +use crate::extension::manager::ExtensionManager; use crate::extension::manifest::ExtensionManifest; use crate::extension::ExtensionName; use clap::Command; @@ -28,4 +30,15 @@ impl InstalledExtensionManifests { pub fn contains(&self, extension: &str) -> bool { self.0.contains_key(extension) } + + pub fn loaded_templates( + &self, + em: &ExtensionManager, + builtin_templates: &[ProjectTemplate], + ) -> Vec { + self.0 + .values() + .flat_map(|manifest| manifest.project_templates(em, builtin_templates)) + .collect() + } } diff --git a/src/dfx-core/src/extension/manifest/extension.rs b/src/dfx-core/src/extension/manifest/extension.rs index 7ed0edcf5b..b72b6619de 100644 --- a/src/dfx-core/src/extension/manifest/extension.rs +++ b/src/dfx-core/src/extension/manifest/extension.rs @@ -1,8 +1,11 @@ +use crate::config::model::project_template::ProjectTemplateCategory; +use crate::config::project_templates::{ProjectTemplate, ProjectTemplateName, ResourceLocation}; use crate::error::extension::{ ConvertExtensionSubcommandIntoClapArgError, ConvertExtensionSubcommandIntoClapCommandError, LoadExtensionManifestError, }; -use crate::json::structure::{VersionReqWithJsonSchema, VersionWithJsonSchema}; +use crate::extension::manager::ExtensionManager; +use crate::json::structure::{SerdeVec, VersionReqWithJsonSchema, VersionWithJsonSchema}; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; @@ -34,6 +37,8 @@ pub struct ExtensionManifest { pub dependencies: Option>, pub canister_type: Option, + pub project_templates: Option>, + /// Components of the download url template are: /// - `{{tag}}`: the tag of the extension release, which will follow the form "-v" /// - `{{basename}}`: The basename of the release filename, which will follow the form "--", for example "nns-x86_64-unknown-linux-gnu" @@ -56,6 +61,28 @@ pub enum ExtensionDependency { Version(VersionReqWithJsonSchema), } +#[derive(Debug, Serialize, Deserialize, JsonSchema)] +pub struct ExtensionProjectTemplate { + /// The name used for display and sorting + pub display: String, + + /// Used to determine which CLI group (`--type`, `--backend`, `--frontend`) + /// as well as for interactive selection + pub category: ProjectTemplateCategory, + + /// Other project templates to patch in alongside this one + pub requirements: Vec, + + /// Run a command after adding the canister to dfx.json + pub post_create: SerdeVec, + + /// If set, display a spinner while this command runs + pub post_create_spinner_message: Option, + + /// If the post-create command fails, display this warning but don't fail + pub post_create_failure_warning: Option, +} + impl ExtensionManifest { pub fn load( name: &str, @@ -92,6 +119,59 @@ impl ExtensionManifest { Ok(vec![]) } } + + pub fn project_templates( + &self, + em: &ExtensionManager, + builtin_templates: &[ProjectTemplate], + ) -> Vec { + let Some(project_templates) = self.project_templates.as_ref() else { + return vec![]; + }; + + let extension_dir = em.get_extension_directory(&self.name); + + // the default sort order is after everything built-in + let default_sort_order = builtin_templates + .iter() + .map(|t| t.sort_order) + .max() + .unwrap_or(0) + + 1; + + project_templates + .iter() + .map(|(name, template)| { + let resource_dir = extension_dir.join("project_templates").join(name); + let resource_location = ResourceLocation::Directory { path: resource_dir }; + + // keep the sort order as a built-in template of the same name, + // otherwise put it after everything else + let sort_order = builtin_templates + .iter() + .find(|t| t.name == ProjectTemplateName(name.clone())) + .map(|t| t.sort_order) + .unwrap_or(default_sort_order); + + let requirements = template + .requirements + .iter() + .map(|r| ProjectTemplateName(r.clone())) + .collect(); + ProjectTemplate { + name: ProjectTemplateName(name.clone()), + display: template.display.clone(), + resource_location, + category: template.category.clone(), + requirements, + post_create: template.post_create.clone().into_vec(), + post_create_spinner_message: template.post_create_spinner_message.clone(), + post_create_failure_warning: template.post_create_failure_warning.clone(), + sort_order, + } + }) + .collect() + } } #[derive(Debug, Serialize, Deserialize, JsonSchema)] diff --git a/src/dfx/src/commands/new.rs b/src/dfx/src/commands/new.rs index 093fe23805..81b3c28e88 100644 --- a/src/dfx/src/commands/new.rs +++ b/src/dfx/src/commands/new.rs @@ -29,6 +29,7 @@ use std::path::{Path, PathBuf}; use std::process::{Command, ExitStatus, Stdio}; use std::time::Duration; use tar::Archive; +use walkdir::WalkDir; // const DRY_RUN: &str = "dry_run"; // const PROJECT_NAME: &str = "project_name"; @@ -270,6 +271,52 @@ fn write_files_from_entries( Ok(()) } +fn write_files_from_directory( + log: &Logger, + dir: &Path, + root: &Path, + dry_run: bool, + variables: &BTreeMap, +) -> DfxResult { + for entry in WalkDir::new(dir).into_iter().filter_map(Result::ok) { + let path = entry.path(); + + if path.is_dir() { + continue; + } + + // Read file contents into a Vec + let file_content = dfx_core::fs::read(path)?; + + // Process the file content (replace variables) + let processed_content = match String::from_utf8(file_content) { + Err(err) => err.into_bytes(), + Ok(s) => replace_variables(s, variables).into_bytes(), + }; + + // Perform path replacements + let relative_path = path + .strip_prefix(dir)? + .to_str() + .ok_or_else(|| anyhow!("Non-unicode path encountered: {}", path.display()))?; + let relative_path = replace_variables(relative_path.to_string(), variables); + + // Build the final target path + let final_path = root.join(&relative_path); + + // Process files based on their extension + if final_path.extension() == Some("json-patch".as_ref()) { + json_patch_file(log, &final_path, &processed_content, dry_run)?; + } else if final_path.extension() == Some("patch".as_ref()) { + patch_file(log, &final_path, &processed_content, dry_run)?; + } else { + create_file(log, &final_path, &processed_content, dry_run)?; + } + } + + Ok(()) +} + #[context("Failed to scaffold frontend code.")] fn scaffold_frontend_code( env: &dyn Environment, @@ -706,10 +753,15 @@ fn write_project_template_resources( dry_run: bool, variables: &BTreeMap, ) -> DfxResult { - let mut resources = match template.resource_location { - ResourceLocation::Bundled { get_archive_fn } => get_archive_fn()?, - }; - write_files_from_entries(logger, &mut resources, project_name, dry_run, variables) + match &template.resource_location { + ResourceLocation::Bundled { get_archive_fn } => { + let mut resources = get_archive_fn()?; + write_files_from_entries(logger, &mut resources, project_name, dry_run, variables) + } + ResourceLocation::Directory { path } => { + write_files_from_directory(logger, path, project_name, dry_run, variables) + } + } } fn get_opts_interactively(opts: NewOpts) -> DfxResult { diff --git a/src/dfx/src/main.rs b/src/dfx/src/main.rs index ef86de36d3..79a19d3542 100644 --- a/src/dfx/src/main.rs +++ b/src/dfx/src/main.rs @@ -137,7 +137,9 @@ fn get_args_altered_for_extension_run( fn inner_main() -> DfxResult { let em = ExtensionManager::new(dfx_version())?; let installed_extension_manifests = em.load_installed_extension_manifests()?; - project_templates::populate(builtin_templates()); + let builtin_templates = builtin_templates(); + let loaded_templates = installed_extension_manifests.loaded_templates(&em, &builtin_templates); + project_templates::populate(builtin_templates, loaded_templates); let args = get_args_altered_for_extension_run(&installed_extension_manifests)?; @@ -201,7 +203,7 @@ mod tests { #[test] fn validate_cli() { - project_templates::populate(builtin_templates()); + project_templates::populate(builtin_templates(), vec![]); CliOpts::command().debug_assert(); } From 8307f69cbe6fc5dd0703856163e34d0991d76c77 Mon Sep 17 00:00:00 2001 From: Eric Swanson <64809312+ericswanson-dfinity@users.noreply.github.com> Date: Tue, 26 Nov 2024 05:00:52 -0800 Subject: [PATCH 13/15] fix: commands with --all parameter skip remote canisters (#4016) --- CHANGELOG.md | 12 ++++++++ e2e/tests-dfx/cycles-ledger.bash | 7 +++++ e2e/tests-dfx/remote.bash | 28 ++++++++++++++++++- src/dfx/src/commands/canister/create.rs | 12 ++------ src/dfx/src/commands/canister/delete.rs | 5 +++- .../src/commands/canister/deposit_cycles.rs | 5 ++++ src/dfx/src/commands/canister/install.rs | 10 ++----- src/dfx/src/commands/canister/start.rs | 6 ++++ src/dfx/src/commands/canister/status.rs | 6 ++++ src/dfx/src/commands/canister/stop.rs | 6 ++++ .../src/commands/canister/uninstall_code.rs | 4 +++ .../src/commands/canister/update_settings.rs | 8 +++++- .../src/commands/ledger/fabricate_cycles.rs | 6 ++++ src/dfx/src/lib/operations/canister/mod.rs | 2 ++ .../canister/skip_remote_canister.rs | 17 +++++++++++ 15 files changed, 113 insertions(+), 21 deletions(-) create mode 100644 src/dfx/src/lib/operations/canister/skip_remote_canister.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index c2e6e01994..33374dd7dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,18 @@ You can use the `--replica` flag already to write scripts that anticipate that c An extension can define one or more project templates for `dfx new` to use. These can be new templates or replace the built-in project templates. +### fix: all commands with --all parameter skip remote canisters + +This affects the following commands: +- `dfx canister delete` +- `dfx canister deposit-cycles` +- `dfx canister start` +- `dfx canister status` +- `dfx canister stop` +- `dfx canister uninstall-code` +- `dfx canister update-settings` +- `dfx ledger fabricate-cycles` + # 0.24.3 ### feat: Bitcoin support in PocketIC diff --git a/e2e/tests-dfx/cycles-ledger.bash b/e2e/tests-dfx/cycles-ledger.bash index 7c20cbf057..3a279b6c0b 100644 --- a/e2e/tests-dfx/cycles-ledger.bash +++ b/e2e/tests-dfx/cycles-ledger.bash @@ -458,6 +458,13 @@ current_time_nanoseconds() { assert_eq "2399699700000 cycles." assert_command dfx canister status e2e_project_backend assert_contains "Balance: 3_100_002_100_000 Cycles" + + # deposit-cycles --all skips remote canisters + jq '.canisters.remote.remote.id.local="rdmx6-jaaaa-aaaaa-aaadq-cai"' dfx.json | sponge dfx.json + assert_command dfx canister deposit-cycles 10000 --all --identity bob + assert_contains "Skipping canister 'remote' because it is remote for network 'local'" + assert_contains "Depositing 10000 cycles onto e2e_project_backend" + assert_not_contains "Depositing 10000 cycles onto remote" } @test "top-up deduplication" { diff --git a/e2e/tests-dfx/remote.bash b/e2e/tests-dfx/remote.bash index 390070157e..8e8bc5568e 100644 --- a/e2e/tests-dfx/remote.bash +++ b/e2e/tests-dfx/remote.bash @@ -137,7 +137,7 @@ teardown() { assert_match "Canister 'remote' is a remote canister on network 'actuallylocal', and cannot be installed from here." } -@test "canister create --all, canister install --all skip remote canisters" { +@test "all commands with --all skip remote canisters" { install_asset remote/actual dfx_start setup_actuallylocal_shared_network @@ -201,6 +201,32 @@ teardown() { assert_command jq .remote canister_ids.json assert_eq "null" + assert_command dfx ledger fabricate-cycles --all --t 100 --network actuallylocal + assert_contains "Skipping canister 'remote' because it is remote for network 'actuallylocal'" + + assert_command dfx canister status --all --network actuallylocal + assert_contains "Skipping canister 'remote' because it is remote for network 'actuallylocal'" + + assert_command dfx canister update-settings --log-visibility public --all --network actuallylocal + assert_contains "Skipping canister 'remote' because it is remote for network 'actuallylocal'" + + assert_command dfx canister stop --all --network actuallylocal + assert_contains "Skipping canister 'remote' because it is remote for network 'actuallylocal'" + + assert_command dfx canister start --all --network actuallylocal + assert_contains "Skipping canister 'remote' because it is remote for network 'actuallylocal'" + + # have to stop to uninstall + assert_command dfx canister stop --all --network actuallylocal + + assert_command dfx canister uninstall-code --all --network actuallylocal + assert_contains "Skipping canister 'remote' because it is remote for network 'actuallylocal'" + + assert_command dfx build --all --network actuallylocal + + assert_command dfx canister delete --all --network actuallylocal + assert_contains "Skipping canister 'remote' because it is remote for network 'actuallylocal'" + # Assert frontend declarations are actually created dfx generate assert_file_exists "src/declarations/remote/remote.did" diff --git a/src/dfx/src/commands/canister/create.rs b/src/dfx/src/commands/canister/create.rs index a5293f0f1c..368ee604f5 100644 --- a/src/dfx/src/commands/canister/create.rs +++ b/src/dfx/src/commands/canister/create.rs @@ -6,7 +6,7 @@ use crate::lib::ic_attributes::{ get_compute_allocation, get_freezing_threshold, get_log_visibility, get_memory_allocation, get_reserved_cycles_limit, get_wasm_memory_limit, CanisterSettings, }; -use crate::lib::operations::canister::create_canister; +use crate::lib::operations::canister::{create_canister, skip_remote_canister}; use crate::lib::root_key::fetch_root_key_if_needed; use crate::util::clap::parsers::{ compute_allocation_parser, freezing_threshold_parser, log_visibility_parser, @@ -245,15 +245,7 @@ pub async fn exec( if pull_canisters_in_config.contains_key(canister_name) { continue; } - let canister_is_remote = - config_interface.is_remote_canister(canister_name, &network.name)?; - if canister_is_remote { - info!( - env.get_logger(), - "Skipping canister '{canister_name}' because it is remote for network '{}'", - &network.name, - ); - + if skip_remote_canister(env, canister_name)? { continue; } let specified_id = config_interface.get_specified_id(canister_name)?; diff --git a/src/dfx/src/commands/canister/delete.rs b/src/dfx/src/commands/canister/delete.rs index 74a2c66748..02d3dcb30f 100644 --- a/src/dfx/src/commands/canister/delete.rs +++ b/src/dfx/src/commands/canister/delete.rs @@ -3,7 +3,7 @@ use crate::lib::error::DfxResult; use crate::lib::ic_attributes::CanisterSettings; use crate::lib::operations::canister; use crate::lib::operations::canister::{ - deposit_cycles, start_canister, stop_canister, update_settings, + deposit_cycles, skip_remote_canister, start_canister, stop_canister, update_settings, }; use crate::lib::operations::cycles_ledger::wallet_deposit_to_cycles_ledger; use crate::lib::root_key::fetch_root_key_if_needed; @@ -352,6 +352,9 @@ pub async fn exec( } else if opts.all { if let Some(canisters) = &config.get_config().canisters { for canister in canisters.keys() { + if skip_remote_canister(env, canister)? { + continue; + } delete_canister( env, canister, diff --git a/src/dfx/src/commands/canister/deposit_cycles.rs b/src/dfx/src/commands/canister/deposit_cycles.rs index 1db8196e38..3a2bb26e0c 100644 --- a/src/dfx/src/commands/canister/deposit_cycles.rs +++ b/src/dfx/src/commands/canister/deposit_cycles.rs @@ -3,6 +3,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use crate::lib::error::DfxResult; use crate::lib::identity::wallet::get_or_create_wallet_canister; use crate::lib::operations::canister; +use crate::lib::operations::canister::skip_remote_canister; use crate::lib::root_key::fetch_root_key_if_needed; use crate::lib::{environment::Environment, operations::cycles_ledger}; use crate::util::clap::parsers::{cycle_amount_parser, icrc_subaccount_parser}; @@ -147,6 +148,10 @@ pub async fn exec( if let Some(canisters) = &config.get_config().canisters { for canister in canisters.keys() { + if skip_remote_canister(env, canister)? { + continue; + } + deposit_cycles( env, canister, diff --git a/src/dfx/src/commands/canister/install.rs b/src/dfx/src/commands/canister/install.rs index d992ca2d9e..bb8e1de50b 100644 --- a/src/dfx/src/commands/canister/install.rs +++ b/src/dfx/src/commands/canister/install.rs @@ -10,6 +10,7 @@ use crate::util::clap::install_mode::{InstallModeHint, InstallModeOpt}; use dfx_core::canister::{install_canister_wasm, install_mode_to_prompt}; use dfx_core::identity::CallSender; +use crate::lib::operations::canister::skip_remote_canister; use anyhow::bail; use candid::Principal; use clap::Parser; @@ -187,7 +188,6 @@ pub async fn exec( } else if opts.all { // Install all canisters. let config = env.get_config_or_anyhow()?; - let config_interface = config.get_config(); let env_file = config.get_output_env_file(opts.output_env_file)?; let pull_canisters_in_config = get_pull_canisters_in_config(env)?; if let Some(canisters) = &config.get_config().canisters { @@ -195,13 +195,7 @@ pub async fn exec( if pull_canisters_in_config.contains_key(canister) { continue; } - if config_interface.is_remote_canister(canister, &network.name)? { - info!( - env.get_logger(), - "Skipping canister '{}' because it is remote for network '{}'", - canister, - &network.name, - ); + if skip_remote_canister(env, canister)? { continue; } diff --git a/src/dfx/src/commands/canister/start.rs b/src/dfx/src/commands/canister/start.rs index 529817ca59..e3ff066710 100644 --- a/src/dfx/src/commands/canister/start.rs +++ b/src/dfx/src/commands/canister/start.rs @@ -1,6 +1,7 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::operations::canister; +use crate::lib::operations::canister::skip_remote_canister; use crate::lib::root_key::fetch_root_key_if_needed; use candid::Principal; use clap::Parser; @@ -51,8 +52,13 @@ pub async fn exec( start_canister(env, canister, call_sender).await } else if opts.all { let config = env.get_config_or_anyhow()?; + if let Some(canisters) = &config.get_config().canisters { for canister in canisters.keys() { + if skip_remote_canister(env, canister)? { + continue; + } + start_canister(env, canister, call_sender).await?; } } diff --git a/src/dfx/src/commands/canister/status.rs b/src/dfx/src/commands/canister/status.rs index d3183759b9..ecd8d7773d 100644 --- a/src/dfx/src/commands/canister/status.rs +++ b/src/dfx/src/commands/canister/status.rs @@ -1,6 +1,7 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::operations::canister; +use crate::lib::operations::canister::skip_remote_canister; use crate::lib::root_key::fetch_root_key_if_needed; use candid::Principal; use clap::Parser; @@ -95,8 +96,13 @@ pub async fn exec( canister_status(env, canister, call_sender).await } else if opts.all { let config = env.get_config_or_anyhow()?; + if let Some(canisters) = &config.get_config().canisters { for canister in canisters.keys() { + if skip_remote_canister(env, canister)? { + continue; + } + canister_status(env, canister, call_sender).await?; } } diff --git a/src/dfx/src/commands/canister/stop.rs b/src/dfx/src/commands/canister/stop.rs index 7f71472908..4e44a539a8 100644 --- a/src/dfx/src/commands/canister/stop.rs +++ b/src/dfx/src/commands/canister/stop.rs @@ -1,6 +1,7 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::operations::canister; +use crate::lib::operations::canister::skip_remote_canister; use crate::lib::root_key::fetch_root_key_if_needed; use candid::Principal; use clap::Parser; @@ -52,8 +53,13 @@ pub async fn exec( stop_canister(env, canister, call_sender).await } else if opts.all { let config = env.get_config_or_anyhow()?; + if let Some(canisters) = &config.get_config().canisters { for canister in canisters.keys() { + if skip_remote_canister(env, canister)? { + continue; + } + stop_canister(env, canister, call_sender).await?; } } diff --git a/src/dfx/src/commands/canister/uninstall_code.rs b/src/dfx/src/commands/canister/uninstall_code.rs index 4b386d7a36..602e3182e7 100644 --- a/src/dfx/src/commands/canister/uninstall_code.rs +++ b/src/dfx/src/commands/canister/uninstall_code.rs @@ -1,6 +1,7 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::operations::canister; +use crate::lib::operations::canister::skip_remote_canister; use crate::lib::root_key::fetch_root_key_if_needed; use candid::Principal; use clap::Parser; @@ -56,6 +57,9 @@ pub async fn exec( if let Some(canisters) = &config.get_config().canisters { for canister in canisters.keys() { + if skip_remote_canister(env, canister)? { + continue; + } uninstall_code(env, canister, call_sender).await?; } } diff --git a/src/dfx/src/commands/canister/update_settings.rs b/src/dfx/src/commands/canister/update_settings.rs index f6867c58af..db3a3184eb 100644 --- a/src/dfx/src/commands/canister/update_settings.rs +++ b/src/dfx/src/commands/canister/update_settings.rs @@ -6,7 +6,9 @@ use crate::lib::ic_attributes::{ get_compute_allocation, get_freezing_threshold, get_log_visibility, get_memory_allocation, get_reserved_cycles_limit, get_wasm_memory_limit, CanisterSettings, }; -use crate::lib::operations::canister::{get_canister_status, update_settings}; +use crate::lib::operations::canister::{ + get_canister_status, skip_remote_canister, update_settings, +}; use crate::lib::root_key::fetch_root_key_if_needed; use crate::util::clap::parsers::{ compute_allocation_parser, freezing_threshold_parser, memory_allocation_parser, @@ -220,8 +222,12 @@ pub async fn exec( // Update all canister settings. let config = env.get_config_or_anyhow()?; let config_interface = config.get_config(); + if let Some(canisters) = &config_interface.canisters { for canister_name in canisters.keys() { + if skip_remote_canister(env, canister_name)? { + continue; + } let mut controllers = controllers.clone(); let canister_id = canister_id_store.get(canister_name)?; let compute_allocation = get_compute_allocation( diff --git a/src/dfx/src/commands/ledger/fabricate_cycles.rs b/src/dfx/src/commands/ledger/fabricate_cycles.rs index 447bc9b405..ef0f239efa 100644 --- a/src/dfx/src/commands/ledger/fabricate_cycles.rs +++ b/src/dfx/src/commands/ledger/fabricate_cycles.rs @@ -3,6 +3,7 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::nns_types::icpts::ICPTs; use crate::lib::operations::canister; +use crate::lib::operations::canister::skip_remote_canister; use crate::lib::root_key::fetch_root_key_or_anyhow; use crate::util::clap::parsers::{cycle_amount_parser, e8s_parser, trillion_cycle_amount_parser}; use crate::util::currency_conversion::as_cycles_with_current_exchange_rate; @@ -120,8 +121,13 @@ pub async fn exec(env: &dyn Environment, opts: FabricateCyclesOpts) -> DfxResult deposit_minted_cycles(env, canister, &CallSender::SelectedId, cycles).await } else if opts.all { let config = env.get_config_or_anyhow()?; + if let Some(canisters) = &config.get_config().canisters { for canister in canisters.keys() { + if skip_remote_canister(env, canister)? { + continue; + } + deposit_minted_cycles(env, canister, &CallSender::SelectedId, cycles).await?; } } diff --git a/src/dfx/src/lib/operations/canister/mod.rs b/src/dfx/src/lib/operations/canister/mod.rs index 501dc9284d..9551ddc4fb 100644 --- a/src/dfx/src/lib/operations/canister/mod.rs +++ b/src/dfx/src/lib/operations/canister/mod.rs @@ -2,10 +2,12 @@ pub(crate) mod create_canister; pub(crate) mod deploy_canisters; pub(crate) mod install_canister; pub mod motoko_playground; +mod skip_remote_canister; pub use create_canister::create_canister; use ic_utils::interfaces::management_canister::Snapshot; pub use install_canister::install_wallet; +pub use skip_remote_canister::skip_remote_canister; use crate::lib::canister_info::CanisterInfo; use crate::lib::environment::Environment; diff --git a/src/dfx/src/lib/operations/canister/skip_remote_canister.rs b/src/dfx/src/lib/operations/canister/skip_remote_canister.rs new file mode 100644 index 0000000000..6ce603cf1a --- /dev/null +++ b/src/dfx/src/lib/operations/canister/skip_remote_canister.rs @@ -0,0 +1,17 @@ +use crate::lib::environment::Environment; +use crate::lib::error::DfxResult; +use slog::info; + +pub fn skip_remote_canister(env: &dyn Environment, canister: &str) -> DfxResult { + let config = env.get_config_or_anyhow()?; + let config_interface = config.get_config(); + let network = env.get_network_descriptor(); + let canister_is_remote = config_interface.is_remote_canister(canister, &network.name)?; + if canister_is_remote { + info!( + env.get_logger(), + "Skipping canister '{canister}' because it is remote for network '{}'", &network.name, + ); + } + Ok(canister_is_remote) +} From 3200e133bfda0a06088aaede4387b5f1c9141ae5 Mon Sep 17 00:00:00 2001 From: Eric Swanson <64809312+ericswanson-dfinity@users.noreply.github.com> Date: Tue, 26 Nov 2024 09:57:17 -0800 Subject: [PATCH 14/15] refactor: remove dead code (#4017) --- src/dfx-core/src/config/model/local_server_descriptor.rs | 8 -------- src/dfx/src/commands/start.rs | 9 --------- 2 files changed, 17 deletions(-) diff --git a/src/dfx-core/src/config/model/local_server_descriptor.rs b/src/dfx-core/src/config/model/local_server_descriptor.rs index e14afa4727..f9eaf562f9 100644 --- a/src/dfx-core/src/config/model/local_server_descriptor.rs +++ b/src/dfx-core/src/config/model/local_server_descriptor.rs @@ -239,14 +239,6 @@ impl LocalServerDescriptor { } } - pub fn with_replica_port(self, port: u16) -> Self { - let replica = ConfigDefaultsReplica { - port: Some(port), - ..self.replica - }; - Self { replica, ..self } - } - pub fn with_bitcoin_enabled(self) -> LocalServerDescriptor { let bitcoin = ConfigDefaultsBitcoin { enabled: true, diff --git a/src/dfx/src/commands/start.rs b/src/dfx/src/commands/start.rs index 768f6d286b..083ccd3a05 100644 --- a/src/dfx/src/commands/start.rs +++ b/src/dfx/src/commands/start.rs @@ -5,7 +5,6 @@ use crate::actors::{ start_shutdown_controller, }; use crate::config::dfx_version_str; -use crate::error_invalid_argument; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::info::replica_rev; @@ -188,7 +187,6 @@ pub fn exec( env.get_logger(), network_descriptor, host, - None, enable_bitcoin, bitcoin_node, enable_canister_http, @@ -495,7 +493,6 @@ pub fn apply_command_line_parameters( logger: &Logger, network_descriptor: NetworkDescriptor, host: Option, - replica_port: Option, enable_bitcoin: bool, bitcoin_nodes: Vec, enable_canister_http: bool, @@ -520,12 +517,6 @@ pub fn apply_command_line_parameters( .map_err(|e| anyhow!("Invalid argument: Invalid host: {}", e))?; local_server_descriptor = local_server_descriptor.with_bind_address(host); } - if let Some(replica_port) = replica_port { - let replica_port: u16 = replica_port - .parse() - .map_err(|err| error_invalid_argument!("Invalid port number: {}", err))?; - local_server_descriptor = local_server_descriptor.with_replica_port(replica_port); - } if enable_bitcoin || !bitcoin_nodes.is_empty() { local_server_descriptor = local_server_descriptor.with_bitcoin_enabled(); } From 3405dc693391c144c97ab77d8a67cf70a3606343 Mon Sep 17 00:00:00 2001 From: Vincent Zhang <118719397+vincent-dfinity@users.noreply.github.com> Date: Thu, 28 Nov 2024 07:33:46 +0800 Subject: [PATCH 15/15] chore: improve error message for 'dfx deploy'. (#4018) * Improve error message for 'dfx deploy'. * Fix lint. * Rename the variable. Co-authored-by: Eric Swanson <64809312+ericswanson-dfinity@users.noreply.github.com> * Add test for failing to create canister without enough cycles. * Fixed shellcheck. * Show the correct command according to the network. --------- Co-authored-by: Eric Swanson <64809312+ericswanson-dfinity@users.noreply.github.com> --- CHANGELOG.md | 11 +++++++++++ e2e/tests-dfx/cycles-ledger.bash | 13 +++++++++++++ src/dfx/src/lib/diagnosis.rs | 33 ++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33374dd7dd..3145adec2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,17 @@ This affects the following commands: - `dfx canister update-settings` - `dfx ledger fabricate-cycles` +### chore: improve `dfx deploy` messages. + +If users run `dfx deploy` without enough cycles, show additional messages to indicate what to do next. +``` +Error explanation: +Insufficient cycles balance to create the canister. +How to resolve the error: +Please top up your cycles balance by converting ICP to cycles like below: +'dfx cycles convert --amount=0.123'. +``` + # 0.24.3 ### feat: Bitcoin support in PocketIC diff --git a/e2e/tests-dfx/cycles-ledger.bash b/e2e/tests-dfx/cycles-ledger.bash index 3a279b6c0b..83fa0ddd99 100644 --- a/e2e/tests-dfx/cycles-ledger.bash +++ b/e2e/tests-dfx/cycles-ledger.bash @@ -591,9 +591,22 @@ current_time_nanoseconds() { assert_command dfx deploy + # Test canister creation failure before topping up cycles. + cd .. + dfx_new canister_creation_failed + + # shellcheck disable=SC2030,SC2031 + export DFX_DISABLE_AUTO_WALLET=1 + assert_command_fail dfx canister create canister_creation_failed_backend --with-cycles 1T --identity alice + assert_contains "Insufficient cycles balance to create the canister." + + ## Back to top up cycles. + cd ../temporary + assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 13_400_000_000_000;})" --identity cycle-giver assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$ALICE\"; subaccount = opt blob \"$ALICE_SUBACCT1_CANDID\"};cycles = 2_600_000_000_000;})" --identity cycle-giver + # shellcheck disable=SC2103 cd .. dfx_new # setup done diff --git a/src/dfx/src/lib/diagnosis.rs b/src/dfx/src/lib/diagnosis.rs index 150f1c9b56..b346341cf7 100644 --- a/src/dfx/src/lib/diagnosis.rs +++ b/src/dfx/src/lib/diagnosis.rs @@ -1,6 +1,8 @@ +use crate::lib::cycles_ledger_types::create_canister::CreateCanisterError; use crate::lib::error_code; use anyhow::Error as AnyhowError; use dfx_core::error::root_key::FetchRootKeyError; +use dfx_core::network::provider::get_network_context; use ic_agent::agent::{RejectCode, RejectResponse}; use ic_agent::AgentError; use ic_asset::error::{GatherAssetDescriptorsError, SyncError, UploadContentError}; @@ -72,6 +74,12 @@ pub fn diagnose(err: &AnyhowError) -> Diagnosis { } } + if let Some(create_canister_err) = err.downcast_ref::() { + if insufficient_cycles(create_canister_err) { + return diagnose_insufficient_cycles(); + } + } + NULL_DIAGNOSIS } @@ -262,3 +270,28 @@ fn diagnose_ledger_not_found() -> Diagnosis { (Some(explanation.to_string()), Some(suggestion.to_string())) } + +fn insufficient_cycles(err: &CreateCanisterError) -> bool { + matches!(err, CreateCanisterError::InsufficientFunds { balance: _ }) +} + +fn diagnose_insufficient_cycles() -> Diagnosis { + let network = match get_network_context() { + Ok(value) => { + if value == "local" { + "".to_string() + } else { + format!(" --network {}", value) + } + } + Err(_) => "".to_string(), + }; + + let explanation = "Insufficient cycles balance to create the canister."; + let suggestion = format!( + "Please top up your cycles balance by converting ICP to cycles like below: +'dfx cycles convert --amount=0.123{}'", + network + ); + (Some(explanation.to_string()), Some(suggestion)) +}