diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index c0a925af43..b08f6d9524 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -21,6 +21,16 @@ After `dfx new `, the canister names are: - `_backend` (previously ``) - `_frontend` (previously `_assets`) +=== feat: new command: dfx canister metadata + +For example, to query a canister's candid service definition: `dfx canister metadata hello_backend candid:service` + +=== refactor: deprecate /_/candid internal webserver + +The dfx internal webserver now only services the /_/candid endpoint. This +is now deprecated. If you were using this to query candid definitions, you +can instead use `dfx canister metadata`. + === feat: Enable threshold ecdsa signature ECDSA signature signing is now enabled by default in new projects, or by running `dfx start --clean`. diff --git a/docs/cli-reference/dfx-canister.md b/docs/cli-reference/dfx-canister.md index dbedc4be29..86f8750d82 100644 --- a/docs/cli-reference/dfx-canister.md +++ b/docs/cli-reference/dfx-canister.md @@ -26,6 +26,7 @@ For reference information and examples that illustrate using `dfx canister` comm | [`id`](#dfx-canister-id) | Displays the identifier of a canister. | | [`info`](#dfx-canister-info) | Get the hash of a canister’s WASM module and its current controller. | | [`install`](#dfx-canister-install) | Installs compiled code in a canister. | +| [`metadata`](#dfx-canister-metadata) | Displays metadata in a canister. | | [`request-status`](#dfx-canister-request-status) | Requests the status of a call to a canister. | | [`send`](#dfx-canister-send) | Send a previously-signed message. | | [`sign`](#dfx-canister-send) | Sign a canister call and generate message file. | @@ -506,6 +507,51 @@ With this setting, all of the canisters in the current projects are assigned a 5 The default value for this option is 0—indicating that no specific allocation or scheduling is in effect. If all of your canisters use the default setting, processing occurs in a round-robin fashion. +## dfx canister metadata + +Use the `dfx canister metadata` command to display metadata stored in a canister's WASM module. + +### Basic usage + +``` bash +dfx canister metadata canister metadata-name +``` + +### Flags + +You can use the following optional flags with the `dfx canister metadata` command. + +| Flag | Description | +|-------------------|-------------------------------| +| `-h`, `--help` | Displays usage information. | + +### Arguments + +You can use the following argument with the `dfx canister metadata` command. + +| Argument | Description | +|-----------------|----------------------------------------------------------------------------------| +| `canister` | Specifies the name or id of the canister for which you want to display metadata. | +| `metadata-name` | Specifies the name of the metadata which you want to display. | + + +### Examples + +To display the candid service metadata for the `hello_world` canister, you can run the following command: + +``` bash +dfx canister metadata hello_world candid:service +``` + +The command displays output similar to the following: + +``` +service : { + greet: (text) -> (text); +} +``` + + ## dfx canister request-status Use the `dfx canister request-status` command to request the status of a specified call to a canister. This command requires you to specify the request identifier you received after invoking a method on the canister. The request identifier is an hexadecimal string starting with `0x`. diff --git a/e2e/tests-dfx/build.bash b/e2e/tests-dfx/build.bash index 4d7c21c851..98ca44ffc6 100644 --- a/e2e/tests-dfx/build.bash +++ b/e2e/tests-dfx/build.bash @@ -142,6 +142,10 @@ teardown() { dfx canister install --all assert_command dfx canister call custom fromQuery assert_command dfx canister call custom2 fromQuery + + # dfx sets the candid:service metadata + dfx canister metadata custom candid:service >installed.did + assert_command diff main.did installed.did } @test "custom canister build script picks local executable first" { diff --git a/e2e/tests-dfx/metadata.bash b/e2e/tests-dfx/metadata.bash new file mode 100644 index 0000000000..4cebc1d9dd --- /dev/null +++ b/e2e/tests-dfx/metadata.bash @@ -0,0 +1,26 @@ +#!/usr/bin/env bats + +load ../utils/_ + +setup() { + standard_setup + + dfx_new +} + +teardown() { + dfx_stop + + standard_teardown +} + + +@test "can read canister metadata from replica" { + dfx_new hello + dfx_start + + assert_command dfx deploy + + dfx canister metadata hello_backend candid:service >metadata.txt + assert_command diff .dfx/local/canisters/hello_backend/hello_backend.did ./metadata.txt +} diff --git a/e2e/tests-dfx/rust.bash b/e2e/tests-dfx/rust.bash index 756b073bf5..a23cb50ccc 100644 --- a/e2e/tests-dfx/rust.bash +++ b/e2e/tests-dfx/rust.bash @@ -23,6 +23,11 @@ teardown() { assert_command dfx canister install hello_backend assert_command dfx canister call hello_backend greet dfinity assert_match '("Hello, dfinity!")' + + # dfx sets the candid:service metadata + dfx canister metadata hello_backend candid:service >installed.did + assert_command diff src/hello_backend/hello_backend.did installed.did + } @test "rust canister can resolve dependencies" { diff --git a/src/dfx/src/commands/canister/metadata.rs b/src/dfx/src/commands/canister/metadata.rs new file mode 100644 index 0000000000..5b1520fb59 --- /dev/null +++ b/src/dfx/src/commands/canister/metadata.rs @@ -0,0 +1,41 @@ +use crate::lib::error::DfxResult; +use crate::lib::models::canister_id_store::CanisterIdStore; +use crate::lib::root_key::fetch_root_key_if_needed; +use crate::Environment; + +use anyhow::{anyhow, Context}; +use clap::Parser; +use ic_types::Principal; +use std::io::{stdout, Write}; + +/// Displays metadata in a canister. +#[derive(Parser)] +pub struct CanisterMetadataOpts { + /// Specifies the name of the canister to call. + canister_name: String, + + /// Specifies the name of the metadata to retrieve. + metadata_name: String, +} + +pub async fn exec(env: &dyn Environment, opts: CanisterMetadataOpts) -> DfxResult { + let agent = env + .get_agent() + .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + + let callee_canister = opts.canister_name.as_str(); + let canister_id_store = CanisterIdStore::for_env(env)?; + + let canister_id = Principal::from_text(callee_canister) + .or_else(|_| canister_id_store.get(callee_canister))?; + + fetch_root_key_if_needed(env).await?; + let metadata = agent + .read_state_canister_metadata(canister_id, &opts.metadata_name, false) + .await + .with_context(|| format!("Failed to read controllers of canister {}.", canister_id))?; + + stdout().write_all(&metadata)?; + + Ok(()) +} diff --git a/src/dfx/src/commands/canister/mod.rs b/src/dfx/src/commands/canister/mod.rs index c290070f95..1e0146bd6e 100644 --- a/src/dfx/src/commands/canister/mod.rs +++ b/src/dfx/src/commands/canister/mod.rs @@ -13,6 +13,7 @@ mod deposit_cycles; mod id; mod info; mod install; +mod metadata; mod request_status; mod send; mod sign; @@ -51,6 +52,7 @@ enum SubCommand { Id(id::CanisterIdOpts), Info(info::InfoOpts), Install(install::CanisterInstallOpts), + Metadata(metadata::CanisterMetadataOpts), RequestStatus(request_status::RequestStatusOpts), Send(send::CanisterSendOpts), Sign(sign::CanisterSignOpts), @@ -75,6 +77,7 @@ pub fn exec(env: &dyn Environment, opts: CanisterOpts) -> DfxResult { SubCommand::Id(v) => id::exec(&agent_env, v).await, SubCommand::Install(v) => install::exec(&agent_env, v, &call_sender).await, SubCommand::Info(v) => info::exec(&agent_env, v).await, + SubCommand::Metadata(v) => metadata::exec(&agent_env, v).await, SubCommand::RequestStatus(v) => request_status::exec(&agent_env, v).await, SubCommand::Send(v) => send::exec(&agent_env, v, &call_sender).await, SubCommand::Sign(v) => sign::exec(&agent_env, v, &call_sender).await,