Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Candid UI URL to dfx info #3748

Merged
merged 5 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

# UNRELEASED

### feat: `dfx info candid-ui-url`

`dfx info candid-ui-url` displays the URL to the Candid UI canister for an explicitly specified `--network <network name>` (or `local` by default).

### chore: Improve help text of `dfx identity new` to include which characters are valid in identity names

# 0.20.1
Expand Down
18 changes: 17 additions & 1 deletion docs/cli-reference/dfx-info.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,22 @@ dfx info [type] [flag]

These are the types of information that the `dfx info` command can display.

| Option | Description |
| Information | Description |
|---------------------|------------------------------------------------|
| candid-ui-url | The URL of the Candid UI canister. |
| networks-json-path | Path to network definition file networks.json. |
| replica-port | The listening port of the replica. |
| replica-rev | The revision of the bundled replica. |
| webserver-port | The local webserver (icx-proxy) port. |

## Options

You can use the following options with the `dfx info` command.

| Option | Description |
|------------------------------------|--------------------------------------------------------------------------------------------------------|
| `--network <network>` | Overrides the environment to connect to. By default, the local canister execution environment is used. |

## Examples

You can display the webserver port by running the following command:
Expand All @@ -29,3 +38,10 @@ You can display the webserver port by running the following command:
$ dfx info webserver-port
4943
```

You can display the URL of Candid UI on mainnet by running the following command:

``` bash
$ dfx info candid-ui-url --network ic
https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.icp0.io
```
15 changes: 15 additions & 0 deletions e2e/tests-dfx/info.bash
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,18 @@ teardown() {
assert_command dfx info replica-rev
assert_eq "$expected_rev"
}

@test "displays Candid UI URL" {
assert_command dfx info candid-ui-url --ic
# shellcheck disable=SC2154
assert_eq "https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.icp0.io/" "$stdout"

# Before deployment the UI canister does not exist yet
assert_command_fail dfx info candid-ui-url
assert_contains "Candid UI not installed on network local."

dfx_start
assert_command dfx deploy e2e_project_backend
assert_command dfx info candid-ui-url
assert_eq "http://127.0.0.1:$(dfx info webserver-port)/?canisterId=$(dfx canister id __Candid_UI)"
}
55 changes: 14 additions & 41 deletions src/dfx/src/commands/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use crate::lib::agent::create_agent_environment;
use crate::lib::canister_info::CanisterInfo;
use crate::lib::environment::Environment;
use crate::lib::error::DfxResult;
use crate::lib::named_canister::get_ui_canister_url;
use crate::lib::network::network_opt::NetworkOpt;
use crate::lib::operations::canister::deploy_canisters::deploy_canisters;
use crate::lib::operations::canister::deploy_canisters::DeployMode::{
ComputeEvidence, ForceReinstallSingleCanister, NormalDeploy, PrepareForProposal,
};
use crate::lib::root_key::fetch_root_key_if_needed;
use crate::lib::{environment::Environment, named_canister};
use crate::util::clap::argument_from_cli::ArgumentFromCliLongOpt;
use crate::util::clap::parsers::{cycle_amount_parser, icrc_subaccount_parser};
use crate::util::clap::subnet_selection_opt::SubnetSelectionOpt;
Expand All @@ -29,8 +30,6 @@ use tokio::runtime::Runtime;
use url::Host::{Domain, Ipv4, Ipv6};
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 {
Expand Down Expand Up @@ -217,8 +216,6 @@ fn display_urls(env: &dyn Environment) -> DfxResult {
let mut frontend_urls = BTreeMap::new();
let mut candid_urls: BTreeMap<&String, Url> = BTreeMap::new();

let ui_canister_id = named_canister::get_ui_canister_id(&canister_id_store);

if let Some(canisters) = &config.get_config().canisters {
for (canister_name, canister_config) in canisters {
let canister_is_remote = config
Expand All @@ -243,7 +240,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)?;
let url = construct_ui_canister_url(env, &canister_id)?;
if let Some(ui_canister_url) = url {
candid_urls.insert(canister_name, ui_canister_url);
}
Expand Down Expand Up @@ -316,43 +313,19 @@ fn construct_frontend_url(
Ok((url, url2))
}

#[context("Failed to construct ui canister url for {} on network '{}'.", canister_id, network.name)]
#[context("Failed to construct ui canister url for {} on network '{}'.", canister_id, env.get_network_descriptor().name)]
fn construct_ui_canister_url(
network: &NetworkDescriptor,
env: &dyn Environment,
canister_id: &Principal,
ui_canister_id: Option<Principal>,
) -> DfxResult<Option<Url>> {
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));
let mut url = get_ui_canister_url(env)?;
if let Some(base_url) = url.as_mut() {
let query_with_canister_id = if let Some(query) = base_url.query() {
format!("{query}&id={canister_id}")
} else {
let query = format!("canisterId={}&id={}", ui_canister_id, canister_id);
url.set_query(Some(&query));
}
Ok(Some(url))
} else {
Ok(None)
}
format!("id={canister_id}")
};
base_url.set_query(Some(&query_with_canister_id));
};
Ok(url)
}
23 changes: 20 additions & 3 deletions src/dfx/src/commands/info/mod.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
mod replica_port;
mod webserver_port;
use crate::commands::info::replica_port::get_replica_port;
use crate::commands::info::webserver_port::get_webserver_port;
use crate::commands::info::{replica_port::get_replica_port, webserver_port::get_webserver_port};
use crate::lib::agent::create_anonymous_agent_environment;
use crate::lib::error::DfxResult;
use crate::lib::info;
use crate::lib::named_canister::get_ui_canister_url;
use crate::lib::network::network_opt::NetworkOpt;
use crate::Environment;
use anyhow::Context;
use anyhow::{bail, Context};
use clap::{Parser, Subcommand};
use dfx_core::config::model::dfinity::NetworksConfig;

#[derive(Subcommand, Clone, Debug)]
enum InfoType {
/// Show the URL of the Candid UI canister
CandidUiUrl,
/// Show the port of the local replica
ReplicaPort,
/// Show the revision of the replica shipped with this dfx binary
Expand All @@ -27,10 +31,23 @@ enum InfoType {
pub struct InfoOpts {
#[command(subcommand)]
info_type: InfoType,

#[command(flatten)]
network: NetworkOpt,
}

pub fn exec(env: &dyn Environment, opts: InfoOpts) -> DfxResult {
let value = match opts.info_type {
InfoType::CandidUiUrl => {
let env = create_anonymous_agent_environment(env, opts.network.to_network_name())?;
match get_ui_canister_url(&env)? {
Some(url) => url.to_string(),
None => bail!(
"Candid UI not installed on network {}.",
env.get_network_descriptor().name
),
}
}
InfoType::ReplicaPort => get_replica_port(env)?,
InfoType::ReplicaRev => info::replica_rev().to_string(),
InfoType::WebserverPort => get_webserver_port(env)?,
Expand Down
34 changes: 34 additions & 0 deletions src/dfx/src/lib/named_canister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ use ic_utils::interfaces::management_canister::builders::InstallMode;
use ic_utils::interfaces::ManagementCanister;
use slog::info;
use std::io::Read;
use url::{Host::Domain, Url};

pub const UI_CANISTER: &str = "__Candid_UI";
pub const MAINNET_UI_CANISTER_INTERFACE_PRINCIPAL: &str = "a4gq6-oaaaa-aaaab-qaa4q-cai";

#[context("Failed to install candid UI canister.")]
pub async fn install_ui_canister(
Expand Down Expand Up @@ -82,3 +84,35 @@ pub async fn install_ui_canister(
pub fn get_ui_canister_id(id_store: &CanisterIdStore) -> Option<Principal> {
id_store.find(UI_CANISTER)
}

pub fn get_ui_canister_url(env: &dyn Environment) -> DfxResult<Option<Url>> {
let network_descriptor = env.get_network_descriptor();

if network_descriptor.is_ic {
let url = format!(
"https://{}.raw.icp0.io",
MAINNET_UI_CANISTER_INTERFACE_PRINCIPAL
);
let url =
Url::parse(&url).with_context(|| format!("Failed to parse Candid UI url {}.", &url))?;
Ok(Some(url))
} else if let Some(candid_ui_id) = get_ui_canister_id(&env.get_canister_id_store()?) {
let mut url = Url::parse(&network_descriptor.providers[0]).with_context(|| {
format!(
"Failed to parse network provider {}.",
&network_descriptor.providers[0]
)
})?;
if let Some(Domain(domain)) = url.host() {
let host = format!("{}.{}", candid_ui_id, domain);
url.set_host(Some(&host))
.with_context(|| format!("Failed to set host to {}", &host))?;
} else {
let query = format!("canisterId={}", candid_ui_id);
url.set_query(Some(&query));
}
Ok(Some(url))
} else {
Ok(None)
}
}