From 2dfb6b9cb2b7443a36ab3eb2044f88f941dd13c2 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Tue, 29 Aug 2023 12:58:55 -0700 Subject: [PATCH 1/6] feat: adds dfx canister url subcommand --- CHANGELOG.md | 6 +++ src/dfx/src/commands/canister/url.rs | 73 ++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/dfx/src/commands/canister/url.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index c1e9125021..e6bf0d7490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ ### fix: dfx deploy urls printed for asset canisters +### feat: new subcommand: dfx canister url + +Dfx Canister URL is a new subcommand that allows you to print the URL of a canister on a specific network. Currently works for local and ic networks. + +Asset canisters will print the URL of the asset canister itself, while non-asset canisters will print the URL of the Candid UI interface. + ### chore: --emulator parameter is deprecated and will be discontinued soon Added warning that the `--emulator` is deprecated and will be discontinued soon. diff --git a/src/dfx/src/commands/canister/url.rs b/src/dfx/src/commands/canister/url.rs new file mode 100644 index 0000000000..21339b77de --- /dev/null +++ b/src/dfx/src/commands/canister/url.rs @@ -0,0 +1,73 @@ +use crate::lib::environment::Environment; +use crate::lib::error::DfxResult; +use candid::Principal; +use clap::Parser; +use dfx_core::config::model::network_descriptor::NetworkDescriptor; +use dfx_core::network::provider::command_line_provider_to_url; + +/// Prints the URL of a canister. +#[derive(Parser)] +pub struct CanisterURLOpts { + /// Specifies the name of the canister. + canister: String, +} + +fn print_local(canister_id: &str, address: &str) -> String { + return format!("{}/?canisterId={}", address, canister_id); +} + +fn print_ic(canister_id: &str) -> String { + return format!("https://{}.icp0.io/", canister_id); +} + +pub fn exec(env: &dyn Environment, opts: CanisterURLOpts) -> DfxResult { + // Suppress warnings + std::env::var("DFX_WARNING").unwrap_or_else(|_| "".to_string()); + env.get_config_or_anyhow()?; + + let canister_name = opts.canister.as_str(); + let canister_id_store = env.get_canister_id_store()?; + let canister_id = + Principal::from_text(canister_name).or_else(|_| canister_id_store.get(canister_name))?; + let network_descriptor = env.get_network_descriptor(); + let is_ic = NetworkDescriptor::is_ic( + network_descriptor.name.as_str(), + &network_descriptor.providers, + ); + + if is_ic { + println!("{}", print_ic(&canister_id.to_text())); + } else { + let address = command_line_provider_to_url("local").unwrap(); + println!("{}", print_local(&canister_id.to_text(), &address)); + } + Ok(()) +} + +#[cfg(test)] +mod test { + #[test] + fn print_local() { + // Should print the URL of the canister. + assert_eq!( + super::print_local("rrkah-fqaaa-aaaaa-aaaaq-cai", "http://127.0.0.1:8000",), + "http://127.0.0.1:8000/?canisterId=rrkah-fqaaa-aaaaa-aaaaq-cai" + ); + assert_eq!( + super::print_local("ryjl3-tyaaa-aaaaa-aaaba-cai", "http://127.0.0.1:4943"), + "http://127.0.0.1:4943/?canisterId=ryjl3-tyaaa-aaaaa-aaaba-cai" + ); + } + #[test] + fn print_ic() { + // Should print the URL of the canister. + assert_eq!( + super::print_ic("rrkah-fqaaa-aaaaa-aaaaq-cai"), + "https://rrkah-fqaaa-aaaaa-aaaaq-cai.icp0.io/" + ); + assert_eq!( + super::print_ic("ryjl3-tyaaa-aaaaa-aaaba-cai"), + "https://ryjl3-tyaaa-aaaaa-aaaba-cai.icp0.io/" + ); + } +} From 21e36ac336be3e46e2cfb868b2ab62bd69223387 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Thu, 31 Aug 2023 13:15:54 -0700 Subject: [PATCH 2/6] feat: dfx_core url functions and new command --- src/dfx-core/src/canister/mod.rs | 1 + src/dfx-core/src/canister/url.rs | 55 ++++++++++ src/dfx/src/commands/canister/mod.rs | 3 + src/dfx/src/commands/canister/url.rs | 158 ++++++++++++++++++++++----- src/dfx/src/commands/deploy.rs | 71 +----------- 5 files changed, 189 insertions(+), 99 deletions(-) create mode 100644 src/dfx-core/src/canister/url.rs diff --git a/src/dfx-core/src/canister/mod.rs b/src/dfx-core/src/canister/mod.rs index 032f578915..5f88adf5b3 100644 --- a/src/dfx-core/src/canister/mod.rs +++ b/src/dfx-core/src/canister/mod.rs @@ -1,3 +1,4 @@ +pub mod url; use crate::{ cli::ask_for_consent, error::canister::{CanisterBuilderError, CanisterInstallError}, diff --git a/src/dfx-core/src/canister/url.rs b/src/dfx-core/src/canister/url.rs new file mode 100644 index 0000000000..b74be460b6 --- /dev/null +++ b/src/dfx-core/src/canister/url.rs @@ -0,0 +1,55 @@ +use url::Host::Domain; +use url::ParseError; +use url::Url; + +const MAINNET_CANDID_INTERFACE_PRINCIPAL: &str = "a4gq6-oaaaa-aaaab-qaa4q-cai"; + +pub fn format_frontend_url(provider: &Url, canister_id: &str) -> Url { + let mut url = Url::clone(&provider); + if let Some(Domain(domain)) = url.host() { + if domain.ends_with("icp-api.io") || domain.ends_with("ic0.app") { + let new_domain = domain.replace("icp-api.io", "icp0.io"); + let new_domain = new_domain.replace("ic0.app", "icp0.io"); + let host = format!("{}.{}", canister_id, new_domain); + let _ = url.set_host(Some(&host)); + } else { + let host = format!("{}.{}", canister_id, domain); + let _ = url.set_host(Some(&host)); + } + } else { + let query = format!("canisterId={}", canister_id); + url.set_query(Some(&query)); + } + url +} + +pub fn format_ui_canister_url_ic(canister_id: &str) -> Result { + let url_result = Url::parse( + format!( + "https://{}.raw.icp0.io/?id={}", + MAINNET_CANDID_INTERFACE_PRINCIPAL, canister_id + ) + .as_str(), + ); + return url_result; +} + +pub fn format_ui_canister_url_custom( + canister_id: &str, + provider: &Url, + ui_canister_id: &str, +) -> Url { + let mut url = Url::clone(&provider); + + if let Some(Domain(domain)) = url.host() { + let host = format!("{}.{}", ui_canister_id, domain); + let query = format!("id={}", canister_id); + url.set_host(Some(&host)).unwrap(); + url.set_query(Some(&query)); + } else { + let query = format!("canisterId={}&id={}", ui_canister_id, canister_id); + url.set_query(Some(&query)); + } + + return url; +} diff --git a/src/dfx/src/commands/canister/mod.rs b/src/dfx/src/commands/canister/mod.rs index bad25894c6..e7e5b56fec 100644 --- a/src/dfx/src/commands/canister/mod.rs +++ b/src/dfx/src/commands/canister/mod.rs @@ -23,6 +23,7 @@ mod status; mod stop; mod uninstall_code; mod update_settings; +pub mod url; /// Manages canisters deployed on a network replica. #[derive(Parser)] @@ -58,6 +59,7 @@ pub enum SubCommand { Stop(stop::CanisterStopOpts), UninstallCode(uninstall_code::UninstallCodeOpts), UpdateSettings(update_settings::UpdateSettingsOpts), + Url(url::CanisterURLOpts), } pub fn exec(env: &dyn Environment, opts: CanisterOpts) -> DfxResult { @@ -90,6 +92,7 @@ pub fn exec(env: &dyn Environment, opts: CanisterOpts) -> DfxResult { SubCommand::Stop(v) => stop::exec(env, v, &call_sender).await, SubCommand::UninstallCode(v) => uninstall_code::exec(env, v, &call_sender).await, SubCommand::UpdateSettings(v) => update_settings::exec(env, v, &call_sender).await, + SubCommand::Url(v) => url::exec(env, v), } }) } diff --git a/src/dfx/src/commands/canister/url.rs b/src/dfx/src/commands/canister/url.rs index 21339b77de..34479ba02d 100644 --- a/src/dfx/src/commands/canister/url.rs +++ b/src/dfx/src/commands/canister/url.rs @@ -1,72 +1,170 @@ -use crate::lib::environment::Environment; +use crate::lib::canister_info::CanisterInfo; use crate::lib::error::DfxResult; +use crate::lib::network::network_opt::NetworkOpt; +use crate::lib::{environment::Environment, named_canister}; +use anyhow::Context; use candid::Principal; use clap::Parser; +use dfx_core::canister::url::{ + format_frontend_url, format_ui_canister_url_custom, format_ui_canister_url_ic, +}; +use dfx_core::config::model::canister_id_store::CanisterIdStore; use dfx_core::config::model::network_descriptor::NetworkDescriptor; -use dfx_core::network::provider::command_line_provider_to_url; +use dfx_core::network::provider::{create_network_descriptor, LocalBindDetermination}; +use fn_error_context::context; +use url::Url; /// Prints the URL of a canister. #[derive(Parser)] pub struct CanisterURLOpts { /// Specifies the name of the canister. canister: String, + #[command(flatten)] + network: NetworkOpt, } -fn print_local(canister_id: &str, address: &str) -> String { - return format!("{}/?canisterId={}", address, canister_id); +#[context("Failed to construct frontend url for canister {} on network '{}'.", canister_id, network.name)] +pub fn construct_frontend_url( + network: &NetworkDescriptor, + canister_id: &Principal, +) -> DfxResult { + let url = Url::parse(&network.providers[0]).with_context(|| { + format!( + "Failed to parse url for network provider {}.", + &network.providers[0] + ) + })?; + + Ok(format_frontend_url(&url, &canister_id.to_string())) } -fn print_ic(canister_id: &str) -> String { - return format!("https://{}.icp0.io/", canister_id); +#[context("Failed to construct ui canister url for {} on network '{}'.", canister_id, network.name)] +pub fn construct_ui_canister_url( + network: &NetworkDescriptor, + canister_id: &Principal, + ui_canister_id: Option, +) -> DfxResult { + let provider = Url::parse(&network.providers[0]).with_context(|| { + format!( + "Failed to parse url for network provider {}.", + &network.providers[0] + ) + })?; + if network.is_ic { + let formatted_url = format_ui_canister_url_ic(&canister_id.to_string())?; + return Ok(formatted_url); + } else { + if let Some(ui_canister_id) = ui_canister_id { + let formatted_url = format_ui_canister_url_custom( + &canister_id.to_string(), + &provider, + &ui_canister_id.to_string().as_str(), + ); + return Ok(formatted_url); + } else { + return Err(anyhow::anyhow!( + "Canister {} does not have a ui canister id", + canister_id + )); + } + } } pub fn exec(env: &dyn Environment, opts: CanisterURLOpts) -> DfxResult { - // Suppress warnings - std::env::var("DFX_WARNING").unwrap_or_else(|_| "".to_string()); env.get_config_or_anyhow()?; - + let network_descriptor = create_network_descriptor( + env.get_config(), + env.get_networks_config(), + opts.network.to_network_name(), + None, + LocalBindDetermination::AsConfigured, + )?; let canister_name = opts.canister.as_str(); - let canister_id_store = env.get_canister_id_store()?; + let canister_id_store = + CanisterIdStore::new(env.get_logger(), &network_descriptor, env.get_config())?; let canister_id = Principal::from_text(canister_name).or_else(|_| canister_id_store.get(canister_name))?; - let network_descriptor = env.get_network_descriptor(); - let is_ic = NetworkDescriptor::is_ic( - network_descriptor.name.as_str(), - &network_descriptor.providers, - ); + let config = env.get_config_or_anyhow()?; + let canister_info = CanisterInfo::load(&config, canister_name, Some(canister_id))?; - if is_ic { - println!("{}", print_ic(&canister_id.to_text())); + let ui_canister_id = named_canister::get_ui_canister_id(&canister_id_store); + // If the canister is an assets canister or has a frontend section, we can display a frontend url. + if let Some(canisters) = &config.get_config().canisters { + let canister_config = canisters.get(canister_name).unwrap(); + let is_assets = canister_info.is_assets() || canister_config.frontend.is_some(); + if is_assets { + let url = construct_frontend_url(&network_descriptor, &canister_id)?; + println!("{}", url.as_str()); + Ok(()) + } else { + let url = construct_ui_canister_url(&network_descriptor, &canister_id, ui_canister_id)?; + println!("{}", url.as_str()); + Ok(()) + } } else { - let address = command_line_provider_to_url("local").unwrap(); - println!("{}", print_local(&canister_id.to_text(), &address)); + Err(anyhow::anyhow!( + "Canister {} does not have a frontend section", + canister_name + )) } - Ok(()) } #[cfg(test)] mod test { + use candid::Principal; + use dfx_core::config::model::network_descriptor::{NetworkDescriptor, NetworkTypeDescriptor}; + + fn test_url(url: &str) -> String { + let local_network = NetworkDescriptor { + name: String::from("Test Network"), // Initialize with a String + providers: vec![String::from(url)], // Initialize with a Vec of Strings + is_ic: false, + r#type: NetworkTypeDescriptor::Persistent, + local_server_descriptor: Option::None, + }; + // Should print the URL of the canister. + let canister = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai"); + let mut output = String::new(); + match canister { + Ok(canister) => { + let url = super::construct_frontend_url(&local_network, &canister); + + match url { + Ok(u) => { + output.push_str(u.as_str()); + } + Err(_) => { + println!("Error: Failed to construct frontend url for canister"); + } + } + } + Err(_) => { + println!("Error: Failed to parse canister id"); + } + } + return output; + } + #[test] fn print_local() { - // Should print the URL of the canister. - assert_eq!( - super::print_local("rrkah-fqaaa-aaaaa-aaaaq-cai", "http://127.0.0.1:8000",), - "http://127.0.0.1:8000/?canisterId=rrkah-fqaaa-aaaaa-aaaaq-cai" - ); assert_eq!( - super::print_local("ryjl3-tyaaa-aaaaa-aaaba-cai", "http://127.0.0.1:4943"), + test_url("http://127.0.0.1:4943"), "http://127.0.0.1:4943/?canisterId=ryjl3-tyaaa-aaaaa-aaaba-cai" ); } + #[test] fn print_ic() { - // Should print the URL of the canister. assert_eq!( - super::print_ic("rrkah-fqaaa-aaaaa-aaaaq-cai"), - "https://rrkah-fqaaa-aaaaa-aaaaq-cai.icp0.io/" + test_url("https://icp-api.io"), + "https://ryjl3-tyaaa-aaaaa-aaaba-cai.icp0.io/" + ); + assert_eq!( + test_url("https://ic0.app"), + "https://ryjl3-tyaaa-aaaaa-aaaba-cai.icp0.io/" ); assert_eq!( - super::print_ic("ryjl3-tyaaa-aaaaa-aaaba-cai"), + test_url("https://icp0.io"), "https://ryjl3-tyaaa-aaaaa-aaaba-cai.icp0.io/" ); } diff --git a/src/dfx/src/commands/deploy.rs b/src/dfx/src/commands/deploy.rs index 7a429dcd85..7280e24437 100644 --- a/src/dfx/src/commands/deploy.rs +++ b/src/dfx/src/commands/deploy.rs @@ -1,3 +1,4 @@ +use crate::commands::canister::url::{construct_frontend_url, construct_ui_canister_url}; use crate::lib::agent::create_agent_environment; use crate::lib::canister_info::CanisterInfo; use crate::lib::error::DfxResult; @@ -15,18 +16,14 @@ use clap::Parser; use console::Style; use dfx_core::config::model::network_descriptor::NetworkDescriptor; use dfx_core::identity::CallSender; -use fn_error_context::context; use ic_utils::interfaces::management_canister::builders::InstallMode; use slog::info; use std::collections::BTreeMap; use std::path::PathBuf; use std::str::FromStr; use tokio::runtime::Runtime; -use url::Host::Domain; use url::Url; -const MAINNET_CANDID_INTERFACE_PRINCIPAL: &str = "a4gq6-oaaaa-aaaab-qaa4q-cai"; - /// Deploys all or a specific canister from the code in your project. By default, all canisters are deployed. #[derive(Parser)] pub struct DeployOpts { @@ -219,9 +216,7 @@ fn display_urls(env: &dyn Environment) -> DfxResult { if !canister_info.is_assets() { let url = construct_ui_canister_url(network, &canister_id, ui_canister_id)?; - if let Some(ui_canister_url) = url { - candid_urls.insert(canister_name, ui_canister_url); - } + candid_urls.insert(canister_name, url); } } } @@ -246,65 +241,3 @@ fn display_urls(env: &dyn Environment) -> DfxResult { Ok(()) } - -#[context("Failed to construct frontend url for canister {} on network '{}'.", canister_id, network.name)] -fn construct_frontend_url(network: &NetworkDescriptor, canister_id: &Principal) -> DfxResult { - let mut url = Url::parse(&network.providers[0]).with_context(|| { - format!( - "Failed to parse url for network provider {}.", - &network.providers[0] - ) - })?; - - if let Some(Domain(domain)) = url.host() { - let host = format!("{}.{}", canister_id, domain); - url.set_host(Some(&host)) - .with_context(|| format!("Failed to set host to {}.", host))?; - } else { - let query = format!("canisterId={}", canister_id); - url.set_query(Some(&query)); - }; - - Ok(url) -} - -#[context("Failed to construct ui canister url for {} on network '{}'.", canister_id, network.name)] -fn construct_ui_canister_url( - network: &NetworkDescriptor, - canister_id: &Principal, - ui_canister_id: Option, -) -> DfxResult> { - if network.is_ic { - let url = format!( - "https://{}.raw.icp0.io/?id={}", - MAINNET_CANDID_INTERFACE_PRINCIPAL, canister_id - ); - let url = Url::parse(&url).with_context(|| { - format!( - "Failed to parse candid url {} for canister {}.", - &url, canister_id - ) - })?; - Ok(Some(url)) - } else if let Some(ui_canister_id) = ui_canister_id { - let mut url = Url::parse(&network.providers[0]).with_context(|| { - format!( - "Failed to parse network provider {}.", - &network.providers[0] - ) - })?; - if let Some(Domain(domain)) = url.host() { - let host = format!("{}.{}", ui_canister_id, domain); - let query = format!("id={}", canister_id); - url.set_host(Some(&host)) - .with_context(|| format!("Failed to set host to {}", &host))?; - url.set_query(Some(&query)); - } else { - let query = format!("canisterId={}&id={}", ui_canister_id, canister_id); - url.set_query(Some(&query)); - } - Ok(Some(url)) - } else { - Ok(None) - } -} From 8024c1485d01d2f921e2b418f2c32d6fd609dc21 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Thu, 31 Aug 2023 16:57:38 -0700 Subject: [PATCH 3/6] unit tests and refactoring deploy --- src/dfx-core/src/canister/url.rs | 54 +++++++++++++++++++++++- src/dfx/src/commands/canister/url.rs | 61 ---------------------------- src/dfx/src/commands/deploy.rs | 30 ++++++++++++-- 3 files changed, 79 insertions(+), 66 deletions(-) diff --git a/src/dfx-core/src/canister/url.rs b/src/dfx-core/src/canister/url.rs index b74be460b6..efec080c64 100644 --- a/src/dfx-core/src/canister/url.rs +++ b/src/dfx-core/src/canister/url.rs @@ -12,7 +12,15 @@ pub fn format_frontend_url(provider: &Url, canister_id: &str) -> Url { let new_domain = new_domain.replace("ic0.app", "icp0.io"); let host = format!("{}.{}", canister_id, new_domain); let _ = url.set_host(Some(&host)); - } else { + } + else if domain.contains("localhost") { + let port = url.port().unwrap_or(4943); + let host = format!("localhost:{}", port); + let query = format!("canisterId={}", canister_id); + url.set_host(Some(&host)).unwrap(); + url.set_query(Some(&query)); + } + else { let host = format!("{}.{}", canister_id, domain); let _ = url.set_host(Some(&host)); } @@ -53,3 +61,47 @@ pub fn format_ui_canister_url_custom( return url; } + +#[cfg(test)] +mod test { + use url::Url; + use crate::canister::url::format_frontend_url; + + #[test] + fn print_local_frontend() { + let provider1 = &Url::parse("http://127.0.0.1:4943").unwrap(); + let provider2 = &Url::parse("http://localhost:4943").unwrap(); + let provider3 = &Url::parse("http://127.0.0.1:8000").unwrap(); + assert_eq!( + format_frontend_url(provider1, "ryjl3-tyaaa-aaaaa-aaaba-cai").as_str(), + "http://127.0.0.1:4943/?canisterId=ryjl3-tyaaa-aaaaa-aaaba-cai" + ); + assert_eq!( + format_frontend_url(provider2, "ryjl3-tyaaa-aaaaa-aaaba-cai").as_str(), + "http://localhost:4943/?canisterId=ryjl3-tyaaa-aaaaa-aaaba-cai" + ); + assert_eq!( + format_frontend_url(provider3, "ryjl3-tyaaa-aaaaa-aaaba-cai").as_str(), + "http://127.0.0.1:8000/?canisterId=ryjl3-tyaaa-aaaaa-aaaba-cai" + ); + } + + #[test] + fn print_ic_frontend() { + let provider1 = &Url::parse("https://ic0.app").unwrap(); + let provider2 = &Url::parse("https://icp-api.io").unwrap(); + let provider3 = &Url::parse("https://icp0.io").unwrap(); + assert_eq!( + format_frontend_url(provider1, "ryjl3-tyaaa-aaaaa-aaaba-cai").as_str(), + "https://ryjl3-tyaaa-aaaaa-aaaba-cai.icp0.io/" + ); + assert_eq!( + format_frontend_url(provider2, "ryjl3-tyaaa-aaaaa-aaaba-cai").as_str(), + "https://ryjl3-tyaaa-aaaaa-aaaba-cai.icp0.io/" + ); + assert_eq!( + format_frontend_url(provider3, "ryjl3-tyaaa-aaaaa-aaaba-cai").as_str(), + "https://ryjl3-tyaaa-aaaaa-aaaba-cai.icp0.io/" + ); + } +} diff --git a/src/dfx/src/commands/canister/url.rs b/src/dfx/src/commands/canister/url.rs index 34479ba02d..a256b9c9ae 100644 --- a/src/dfx/src/commands/canister/url.rs +++ b/src/dfx/src/commands/canister/url.rs @@ -108,64 +108,3 @@ pub fn exec(env: &dyn Environment, opts: CanisterURLOpts) -> DfxResult { )) } } - -#[cfg(test)] -mod test { - use candid::Principal; - use dfx_core::config::model::network_descriptor::{NetworkDescriptor, NetworkTypeDescriptor}; - - fn test_url(url: &str) -> String { - let local_network = NetworkDescriptor { - name: String::from("Test Network"), // Initialize with a String - providers: vec![String::from(url)], // Initialize with a Vec of Strings - is_ic: false, - r#type: NetworkTypeDescriptor::Persistent, - local_server_descriptor: Option::None, - }; - // Should print the URL of the canister. - let canister = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai"); - let mut output = String::new(); - match canister { - Ok(canister) => { - let url = super::construct_frontend_url(&local_network, &canister); - - match url { - Ok(u) => { - output.push_str(u.as_str()); - } - Err(_) => { - println!("Error: Failed to construct frontend url for canister"); - } - } - } - Err(_) => { - println!("Error: Failed to parse canister id"); - } - } - return output; - } - - #[test] - fn print_local() { - assert_eq!( - test_url("http://127.0.0.1:4943"), - "http://127.0.0.1:4943/?canisterId=ryjl3-tyaaa-aaaaa-aaaba-cai" - ); - } - - #[test] - fn print_ic() { - assert_eq!( - test_url("https://icp-api.io"), - "https://ryjl3-tyaaa-aaaaa-aaaba-cai.icp0.io/" - ); - assert_eq!( - test_url("https://ic0.app"), - "https://ryjl3-tyaaa-aaaaa-aaaba-cai.icp0.io/" - ); - assert_eq!( - test_url("https://icp0.io"), - "https://ryjl3-tyaaa-aaaaa-aaaba-cai.icp0.io/" - ); - } -} diff --git a/src/dfx/src/commands/deploy.rs b/src/dfx/src/commands/deploy.rs index 7280e24437..3babc56745 100644 --- a/src/dfx/src/commands/deploy.rs +++ b/src/dfx/src/commands/deploy.rs @@ -1,4 +1,3 @@ -use crate::commands::canister::url::{construct_frontend_url, construct_ui_canister_url}; use crate::lib::agent::create_agent_environment; use crate::lib::canister_info::CanisterInfo; use crate::lib::error::DfxResult; @@ -14,6 +13,7 @@ use anyhow::{anyhow, bail, Context}; use candid::Principal; use clap::Parser; use console::Style; +use dfx_core::canister::url::{format_frontend_url, format_ui_canister_url_ic, format_ui_canister_url_custom}; use dfx_core::config::model::network_descriptor::NetworkDescriptor; use dfx_core::identity::CallSender; use ic_utils::interfaces::management_canister::builders::InstallMode; @@ -209,14 +209,36 @@ fn display_urls(env: &dyn Environment) -> DfxResult { // If the canister is an assets canister or has a frontend section, we can display a frontend url. let is_assets = canister_info.is_assets() || canister_config.frontend.is_some(); + let provider = Url::parse(&network.providers[0]).with_context(|| { + format!( + "Failed to parse url for network provider {}.", + &network.providers[0] + ) + })?; if is_assets { - let url = construct_frontend_url(network, &canister_id)?; + let url = format_frontend_url(&provider, &canister_id.to_string()); frontend_urls.insert(canister_name, url); } if !canister_info.is_assets() { - let url = construct_ui_canister_url(network, &canister_id, ui_canister_id)?; - candid_urls.insert(canister_name, url); + let is_local = env.get_network_descriptor().name == "local"; + if is_local { + let ui_canister_id = ui_canister_id.ok_or_else(|| { + anyhow!( + "The ui canister id is not set in the canister_id_store.json file." + ) + })?; + let url = format_ui_canister_url_custom( + &&canister_id.to_string(), + &provider, + &ui_canister_id.to_string(), + ); + candid_urls.insert(canister_name, url); + } + else { + let url = format_ui_canister_url_ic(&canister_id.to_string())?; + candid_urls.insert(canister_name, url); + } } } } From 1e204de6218ab1a31afa199f6a9a30db81b024f5 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Fri, 1 Sep 2023 12:07:51 -0700 Subject: [PATCH 4/6] e2e tests --- e2e/tests-dfx/canister_url.bash | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 e2e/tests-dfx/canister_url.bash diff --git a/e2e/tests-dfx/canister_url.bash b/e2e/tests-dfx/canister_url.bash new file mode 100644 index 0000000000..c327f18c01 --- /dev/null +++ b/e2e/tests-dfx/canister_url.bash @@ -0,0 +1,32 @@ +#!/usr/bin/env bats + +load ../utils/_ + +setup() { + standard_setup + + dfx_new hello +} + +teardown() { + dfx_stop + + standard_teardown +} + +@test "canister url performs as expected on local deploy" { + dfx_new_frontend hello + dfx_start + dfx deploy + assert_command dfx canister url hello_backend + assert_eq "http://127.0.0.1:4943/?canisterId=be2us-64aaa-aaaaa-qaabq-cai&id=bkyz2-fmaaa-aaaaa-qaaaq-cai" + assert_command dfx canister url hello_frontend + assert_eq "http://127.0.0.1:4943/?canisterId=bd3sg-teaaa-aaaaa-qaaba-cai" +} + +@test "canister url performs as expected on remote canisters" { + # set dfx.json to string + echo '{"canisters": {"whoami": {"type": "pull", "id": "ivcos-eqaaa-aaaab-qablq-cai"}}}' > dfx.json + assert_command dfx canister url whoami --network ic + assert_eq "https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.icp0.io/?id=ivcos-eqaaa-aaaab-qablq-cai" +} From d48ead5693ff76262771c7c4ef10b9f05234aab1 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Fri, 1 Sep 2023 12:41:32 -0700 Subject: [PATCH 5/6] more e2e tests --- e2e/tests-dfx/canister_url.bash | 17 +++++++++++++++++ src/dfx/src/commands/canister/mod.rs | 2 +- src/dfx/src/commands/canister/url.rs | 4 ++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/e2e/tests-dfx/canister_url.bash b/e2e/tests-dfx/canister_url.bash index c327f18c01..30485b5844 100644 --- a/e2e/tests-dfx/canister_url.bash +++ b/e2e/tests-dfx/canister_url.bash @@ -30,3 +30,20 @@ teardown() { assert_command dfx canister url whoami --network ic assert_eq "https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.icp0.io/?id=ivcos-eqaaa-aaaab-qablq-cai" } + +@test "missing ui canister error" { + dfx_start + dfx canister create hello_backend + assert_command_fail dfx canister url hello_backend + assert_contains "Network local does not have a ui canister id" +} + +@test "missing local id error" { + assert_command_fail dfx canister url hello_backend + assert_contains "Cannot find canister id. Please issue 'dfx canister create hello_backend'" +} + +@test "missing ic id error" { + assert_command_fail dfx canister url hello_backend --network ic + assert_contains "Cannot find canister id. Please issue 'dfx canister create hello_backend --network ic'." +} diff --git a/src/dfx/src/commands/canister/mod.rs b/src/dfx/src/commands/canister/mod.rs index e7e5b56fec..c22fd3f8ec 100644 --- a/src/dfx/src/commands/canister/mod.rs +++ b/src/dfx/src/commands/canister/mod.rs @@ -64,7 +64,7 @@ pub enum SubCommand { pub fn exec(env: &dyn Environment, opts: CanisterOpts) -> DfxResult { let agent_env; - let env = if matches!(&opts.subcmd, SubCommand::Id(_)) { + let env = if matches!(&opts.subcmd, SubCommand::Id(_) | SubCommand::Url(_)) { env } else { agent_env = create_agent_environment(env, opts.network.to_network_name())?; diff --git a/src/dfx/src/commands/canister/url.rs b/src/dfx/src/commands/canister/url.rs index a256b9c9ae..c59d06a3bd 100644 --- a/src/dfx/src/commands/canister/url.rs +++ b/src/dfx/src/commands/canister/url.rs @@ -63,8 +63,8 @@ pub fn construct_ui_canister_url( return Ok(formatted_url); } else { return Err(anyhow::anyhow!( - "Canister {} does not have a ui canister id", - canister_id + "Network {} does not have a ui canister id", + network.name )); } } From 76fea27c6a8f301cd77af77b09c06f384681c4a0 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Fri, 1 Sep 2023 12:42:25 -0700 Subject: [PATCH 6/6] cargo fmt --- src/dfx-core/src/canister/url.rs | 8 +++----- src/dfx/src/commands/deploy.rs | 7 ++++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/dfx-core/src/canister/url.rs b/src/dfx-core/src/canister/url.rs index efec080c64..a1f60c05fb 100644 --- a/src/dfx-core/src/canister/url.rs +++ b/src/dfx-core/src/canister/url.rs @@ -12,15 +12,13 @@ pub fn format_frontend_url(provider: &Url, canister_id: &str) -> Url { let new_domain = new_domain.replace("ic0.app", "icp0.io"); let host = format!("{}.{}", canister_id, new_domain); let _ = url.set_host(Some(&host)); - } - else if domain.contains("localhost") { + } else if domain.contains("localhost") { let port = url.port().unwrap_or(4943); let host = format!("localhost:{}", port); let query = format!("canisterId={}", canister_id); url.set_host(Some(&host)).unwrap(); url.set_query(Some(&query)); - } - else { + } else { let host = format!("{}.{}", canister_id, domain); let _ = url.set_host(Some(&host)); } @@ -64,8 +62,8 @@ pub fn format_ui_canister_url_custom( #[cfg(test)] mod test { - use url::Url; use crate::canister::url::format_frontend_url; + use url::Url; #[test] fn print_local_frontend() { diff --git a/src/dfx/src/commands/deploy.rs b/src/dfx/src/commands/deploy.rs index 3babc56745..4d2e338491 100644 --- a/src/dfx/src/commands/deploy.rs +++ b/src/dfx/src/commands/deploy.rs @@ -13,7 +13,9 @@ use anyhow::{anyhow, bail, Context}; use candid::Principal; use clap::Parser; use console::Style; -use dfx_core::canister::url::{format_frontend_url, format_ui_canister_url_ic, format_ui_canister_url_custom}; +use dfx_core::canister::url::{ + format_frontend_url, format_ui_canister_url_custom, format_ui_canister_url_ic, +}; use dfx_core::config::model::network_descriptor::NetworkDescriptor; use dfx_core::identity::CallSender; use ic_utils::interfaces::management_canister::builders::InstallMode; @@ -234,8 +236,7 @@ fn display_urls(env: &dyn Environment) -> DfxResult { &ui_canister_id.to_string(), ); candid_urls.insert(canister_name, url); - } - else { + } else { let url = format_ui_canister_url_ic(&canister_id.to_string())?; candid_urls.insert(canister_name, url); }