Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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 @@ -7,6 +7,10 @@ bump. Currently experimental: project bundling, project dependencies

# Unreleased

* feat: a new `status_visibility` canister setting controls who may read a canister's status (its running state, cycles, memory usage, and settings) through the management canister. It takes the same forms as `log_visibility` — `controllers` (the default), `public`, or `{ allowed_viewers: [...] }` — and can be set in a manifest's `settings:` block or with `icp canister settings update --status-visibility / --add-status-viewer / --remove-status-viewer / --set-status-viewer`. `icp canister status` and `icp canister settings show` now report it. See the [canister settings reference](docs/reference/canister-settings.md#status_visibility).
* This raises the minimum replica version: reading a canister's status now requires one that reports `status_visibility`, and against an older replica `canister_status` fails to decode — which affects `icp deploy`, `icp canister status`, and `icp canister settings show`/`sync`, not just the new setting. Every mainnet subnet reports it. A `managed` network resolves the launcher to `latest` unless it pins `version:`, so only a pinned launcher older than `15.0.0-2026-08-13-03-55` is affected; raise the pin to that version or later.
* fix: canister settings from the manifest are no longer silently discarded when a canister is created through the legacy management-canister fallback (a CloudEngine subnet with no registered engine operator). That path went through `ic-utils`, which encodes `create_canister`'s argument as a bare `canister_settings` record rather than the `record { settings : opt canister_settings }` the interface spec defines, so the replica read no settings at all and created the canister with defaults. `icp deploy` masked this by syncing settings afterwards; `icp canister create` does not, and left the canister unconfigured.
* fix: `icp canister status` again falls back to the publicly readable state-tree information when the caller may not read the status. Replicas carrying the status-visibility feature reject those calls with a new error code (`IC0542`), which the fallback did not recognise, so the command failed with `Error looking up canister <id>` instead of printing the controllers and module hash. `IC0541`, returned on subnets with administrators, is now recognised too.
* feat: `script` build steps now receive `ICP_CLI_ENVIRONMENT`, the name of the environment the canisters are being built for, so a build can vary by environment the way a sync step already could.
* feat: `icp completions <SHELL>` prints a shell completion script for `bash`, `zsh`, `fish`, `powershell`, or `elvish` to stdout. See the [installation guide](docs/guides/installation.md#shell-completions) for where to put it.
* fix: `icp canister logs` output formats are corrected. `--json` now emits machine-readable JSON and the default emits the human-readable lines (the two were swapped), and `--follow --json` emits newline-delimited JSON, one record per line, streamed as each record arrives. This is breaking for scripts: parsing the default output as JSON now requires `--json`, and consumers of `--follow --json` must read one JSON object per line.
Expand Down
85 changes: 51 additions & 34 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ httptest = "0.16.3"
ic-agent = { version = "0.49.1" }
ic-ed25519 = "0.6.0"
ic-ledger-types = "0.16.0"
ic-management-canister-types = { version = "0.8.0" }
ic-management-canister-types = { version = "0.9.0" }
ic-utils = { version = "0.49.1" }
icp = { path = "crates/icp" }
icp-canister-interfaces = { path = "crates/icp-canister-interfaces" }
Expand Down
8 changes: 7 additions & 1 deletion crates/icp-cli/src/commands/canister/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,12 @@ impl CreateArgs {
.or(default.settings.reserved_cycles_limit.clone())
.map(|c| Nat::from(c.get())),
// TODO This should be configurable from the CLI
log_visibility: default.settings.log_visibility.clone().map(Into::into),
log_visibility: default.settings.log_visibility.clone().map(|v| v.0.into()),
status_visibility: default
.settings
.status_visibility
.clone()
.map(|v| v.0.into()),
memory_allocation: self
.settings
.memory_allocation
Expand Down Expand Up @@ -255,6 +260,7 @@ impl CreateArgs {
.map(|c| Nat::from(c.get())),
// TODO This should be configurable from the CLI
log_visibility: None,
status_visibility: None,
memory_allocation: self
.settings
.memory_allocation
Expand Down
18 changes: 18 additions & 0 deletions crates/icp-cli/src/commands/canister/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use clap::Subcommand;
use icp::canister::Visibility;

pub(crate) mod call;
pub(crate) mod create;
Expand All @@ -16,6 +17,23 @@ pub(crate) mod status;
pub(crate) mod stop;
pub(crate) mod top_up;

/// Renders a visibility setting for `canister status` and `canister settings show`.
/// Viewers are sorted so repeated calls print the same order.
pub(crate) fn format_visibility(visibility: &Visibility) -> String {
match visibility {
Visibility::Controllers => "Controllers".to_string(),
Visibility::Public => "Public".to_string(),
Visibility::AllowedViewers(viewers) if viewers.is_empty() => {
"Allowed viewers list is empty".to_string()
}
Visibility::AllowedViewers(viewers) => {
let mut viewers: Vec<String> = viewers.iter().map(|p| p.to_string()).collect();
viewers.sort();
format!("Allowed viewers: {}", viewers.join(", "))
}
}
}

/// Perform canister operations against a network
#[derive(Debug, Subcommand)]
#[allow(clippy::large_enum_variant)]
Expand Down
36 changes: 19 additions & 17 deletions crates/icp-cli/src/commands/canister/settings/show.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
use clap::Args;
use ic_agent::export::Principal;
use ic_management_canister_types::{CanisterIdRecord, DefiniteCanisterSettings, LogVisibility};
use ic_management_canister_types::{CanisterIdRecord, DefiniteCanisterSettings};
use icp::context::Context;
use std::fmt::Write;

use crate::{commands::args::CanisterCommandArgs, operations::proxy_management};
use crate::{
commands::{args::CanisterCommandArgs, canister::format_visibility},
operations::proxy_management,
};

/// Show the settings of a canister.
///
/// Queries the canister_status endpoint of the management canister and
/// displays only the settings fields. Requires the caller to be a controller.
/// displays only the settings fields. Requires the caller to be allowed to read
/// the canister's status, which by default means being one of its controllers.
#[derive(Debug, Args)]
pub(crate) struct ShowArgs {
#[command(flatten)]
Expand Down Expand Up @@ -90,20 +94,18 @@ fn build_output(s: &DefiniteCanisterSettings) -> String {
.unwrap();
writeln!(&mut buf, "Log memory limit: {}", s.log_memory_limit).unwrap();

let log_visibility = match &s.log_visibility {
LogVisibility::Controllers => "Controllers".to_string(),
LogVisibility::Public => "Public".to_string(),
LogVisibility::AllowedViewers(viewers) => {
if viewers.is_empty() {
"Allowed viewers list is empty".to_string()
} else {
let mut v: Vec<String> = viewers.iter().map(|p| p.to_string()).collect();
v.sort();
format!("Allowed viewers: {}", v.join(", "))
}
}
};
writeln!(&mut buf, "Log visibility: {log_visibility}").unwrap();
writeln!(
&mut buf,
"Log visibility: {}",
format_visibility(&s.log_visibility.clone().into())
)
.unwrap();
writeln!(
&mut buf,
"Status visibility: {}",
format_visibility(&s.status_visibility.clone().into())
Comment thread
marc0olo marked this conversation as resolved.
)
.unwrap();

if s.environment_variables.is_empty() {
writeln!(&mut buf, "Environment variables: N/A").unwrap();
Expand Down
Loading
Loading