Skip to content
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
10 changes: 10 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ After `dfx new <project>`, the canister names are:
- `<project>_backend` (previously `<project>`)
- `<project>_frontend` (previously `<project>_assets`)

=== feat: new command: dfx canister metadata <canister> <name>

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`.
Expand Down
46 changes: 46 additions & 0 deletions docs/cli-reference/dfx-canister.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down Expand Up @@ -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`.
Expand Down
4 changes: 4 additions & 0 deletions e2e/tests-dfx/build.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down
26 changes: 26 additions & 0 deletions e2e/tests-dfx/metadata.bash
Original file line number Diff line number Diff line change
@@ -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
}
5 changes: 5 additions & 0 deletions e2e/tests-dfx/rust.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down
41 changes: 41 additions & 0 deletions src/dfx/src/commands/canister/metadata.rs
Original file line number Diff line number Diff line change
@@ -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)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure the best way to display this. Some of the metadata may not be printable ascii. Maybe we can check if metadata is printable, if not, we display the hex instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to have an --outfile flag instead/in addition?

Copy link
Author

@ghost ghost Jul 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chenyan-dfinity @sesi200 I wondered about these too. Not sure of the best way either.

The thinking was that by writing the raw data bytes to stdout, they can be written to a file or piped to another program.

While the metadata may not be printable ascii, what if the developer wants to run the actual contents through hexdump or some other tool? If dfx canister metadata ... | hexdump -C, and the metadata is not entirely printable ascii, what would the expected behavior be?

What would the proposed rules be? Would they more clear than always writing as-is to stdout? The below seem overly complicated to me.

  • if --outfile specified, write data to specified file as-is. --outfile - means write data as-is to stdout.
  • If no --outfile specified, and metadata is printable ascii, write that to stdout.
  • If no --outfile specified, and metadata is not all printable ascii, then write an explanation to stderr and display metadata as hex to stdout.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no strong opinion either way and have no problem merging this as-is. I was mostly thinking about invoking dfx from somewhere that doesn't have such trivial redirect mechanics, but I might just be overthinking it.

My approach would have been to print to --outfile if specified, otherwise print to stdout. Reason being that I'd want to give a way to not see garbage printed if it is expected.


Ok(())
}
3 changes: 3 additions & 0 deletions src/dfx/src/commands/canister/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod deposit_cycles;
mod id;
mod info;
mod install;
mod metadata;
mod request_status;
mod send;
mod sign;
Expand Down Expand Up @@ -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),
Expand All @@ -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,
Expand Down