Skip to content

Commit 8797478

Browse files
raymondkclaude
andcommitted
feat: list allowed viewers one per line
`Allowed viewers: <a>, <b>` put two 63-character principals on the label's line, which wraps in any normal terminal. The policy now stays on the label and each viewer gets its own line, nested two spaces past the label the way the other lists in those reports are: Status visibility: Allowed viewers viewer: 7tjgl-udln4-... viewer: cgbip-rubo2-... The label sits at a different indent in `canister status` than in `canister settings show`, so `format_visibility` takes the label's indent and nests relative to it. `log_visibility` is rendered by the same function, so it changes with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QooMLZ15EWRrknWtXfFZNp
1 parent 554a8eb commit 8797478

5 files changed

Lines changed: 81 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ air-gapped signing
1010

1111
* feat: a canister can now declare `upgrade_args` alongside `init_args`, in its own manifest and as a per-canister environment override. It is passed when `icp deploy` upgrades the canister, where `init_args` is passed when it installs or reinstalls it. It takes exactly the forms `init_args` does (inline Candid string, or `{ value | path, format }`), and paths resolve against the canister's own directory the same way. A canister that declares no `upgrade_args` is upgraded with its `init_args`, as before, and `--args` / `--args-file` still override whichever applies.
1212
* 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).
13+
* `icp canister status` and `icp canister settings show` now list allowed viewers one per line under the setting (`viewer: <principal>`) rather than comma-separated on the label's line. This applies to `log_visibility` as well, so scripts matching that line need updating.
1314
* 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.
1415
* 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.
1516
* 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.

crates/icp-cli/src/commands/canister/mod.rs

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@ pub(crate) mod stop;
1818
pub(crate) mod top_up;
1919

2020
/// Renders a visibility setting for `canister status` and `canister settings show`.
21-
/// Viewers are sorted so repeated calls print the same order.
22-
pub(crate) fn format_visibility(visibility: &Visibility) -> String {
21+
///
22+
/// The policy goes on the label's own line; allowed viewers are listed one per
23+
/// line below it, indented two spaces past `indent` — the indent of the label
24+
/// itself — so they nest the way the other lists in those reports do. Viewers
25+
/// are sorted so repeated calls print the same order.
26+
pub(crate) fn format_visibility(visibility: &Visibility, indent: &str) -> String {
2327
match visibility {
2428
Visibility::Controllers => "Controllers".to_string(),
2529
Visibility::Public => "Public".to_string(),
@@ -29,7 +33,11 @@ pub(crate) fn format_visibility(visibility: &Visibility) -> String {
2933
Visibility::AllowedViewers(viewers) => {
3034
let mut viewers: Vec<String> = viewers.iter().map(|p| p.to_string()).collect();
3135
viewers.sort();
32-
format!("Allowed viewers: {}", viewers.join(", "))
36+
let mut out = "Allowed viewers".to_string();
37+
for viewer in viewers {
38+
out.push_str(&format!("\n{indent} viewer: {viewer}"));
39+
}
40+
out
3341
}
3442
}
3543
}
@@ -56,3 +64,54 @@ pub(crate) enum Command {
5664
Stop(stop::StopArgs),
5765
TopUp(top_up::TopUpArgs),
5866
}
67+
68+
#[cfg(test)]
69+
mod tests {
70+
use candid::Principal;
71+
72+
use super::*;
73+
74+
fn principal(text: &str) -> Principal {
75+
Principal::from_text(text).unwrap()
76+
}
77+
78+
/// Allowed viewers are listed one per line, sorted, and nested two spaces
79+
/// past the label — which sits at a different indent in `canister status`
80+
/// than in `canister settings show`.
81+
#[test]
82+
fn allowed_viewers_are_listed_one_per_line() {
83+
let viewers = Visibility::AllowedViewers(vec![
84+
principal("ryjl3-tyaaa-aaaaa-aaaba-cai"),
85+
principal("aaaaa-aa"),
86+
]);
87+
88+
// `settings show`, where the label is not indented.
89+
assert_eq!(
90+
format!("Status visibility: {}", format_visibility(&viewers, "")),
91+
"Status visibility: Allowed viewers\n \
92+
viewer: aaaaa-aa\n \
93+
viewer: ryjl3-tyaaa-aaaaa-aaaba-cai"
94+
);
95+
// `canister status`, where it sits two spaces in.
96+
assert_eq!(
97+
format!(" Status visibility: {}", format_visibility(&viewers, " ")),
98+
" Status visibility: Allowed viewers\n \
99+
viewer: aaaaa-aa\n \
100+
viewer: ryjl3-tyaaa-aaaaa-aaaba-cai"
101+
);
102+
}
103+
104+
/// The other policies stay on the label's line.
105+
#[test]
106+
fn fixed_policies_are_rendered_inline() {
107+
assert_eq!(
108+
format_visibility(&Visibility::Controllers, " "),
109+
"Controllers"
110+
);
111+
assert_eq!(format_visibility(&Visibility::Public, " "), "Public");
112+
assert_eq!(
113+
format_visibility(&Visibility::AllowedViewers(vec![]), " "),
114+
"Allowed viewers list is empty"
115+
);
116+
}
117+
}

crates/icp-cli/src/commands/canister/settings/show.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,13 @@ fn build_output(s: &DefiniteCanisterSettings) -> String {
9696
writeln!(
9797
&mut buf,
9898
"Log visibility: {}",
99-
format_visibility(&s.log_visibility.clone().into())
99+
format_visibility(&s.log_visibility.clone().into(), "")
100100
)
101101
.unwrap();
102102
writeln!(
103103
&mut buf,
104104
"Status visibility: {}",
105-
format_visibility(&s.status_visibility.clone().into())
105+
format_visibility(&s.status_visibility.clone().into(), "")
106106
)
107107
.unwrap();
108108

crates/icp-cli/src/commands/canister/status.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -537,12 +537,12 @@ fn build_output(result: &SerializableCanisterStatusResult) -> Result<String, any
537537
writeln!(
538538
&mut buf,
539539
" Log visibility: {}",
540-
format_visibility(&settings.log_visibility.0)
540+
format_visibility(&settings.log_visibility.0, " ")
541541
)?;
542542
writeln!(
543543
&mut buf,
544544
" Status visibility: {}",
545-
format_visibility(&settings.status_visibility.0)
545+
format_visibility(&settings.status_visibility.0, " ")
546546
)?;
547547

548548
// Display environment variables configured for this canister

crates/icp-cli/tests/canister_settings_tests.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ async fn canister_settings_update_log_visibility() {
503503
.assert()
504504
.success()
505505
.stdout(
506-
contains("Log visibility: Allowed viewers:").and(contains(principal_alice.as_str())),
506+
contains("Log visibility: Allowed viewers").and(contains(principal_alice.as_str())),
507507
);
508508

509509
// Add and remove log viewer.
@@ -538,7 +538,7 @@ async fn canister_settings_update_log_visibility() {
538538
.assert()
539539
.success()
540540
.stdout(
541-
contains("Log visibility: Allowed viewers:")
541+
contains("Log visibility: Allowed viewers")
542542
.and(contains(principal_alice.as_str()).not())
543543
.and(contains(principal_bob.as_str())),
544544
);
@@ -606,7 +606,7 @@ async fn canister_settings_update_log_visibility() {
606606
.assert()
607607
.success()
608608
.stdout(
609-
contains("Log visibility: Allowed viewers:")
609+
contains("Log visibility: Allowed viewers")
610610
.and(contains(principal_alice.as_str()))
611611
.and(contains(principal_bob.as_str())),
612612
);
@@ -676,7 +676,7 @@ async fn canister_settings_update_log_visibility() {
676676
.assert()
677677
.success()
678678
.stdout(
679-
contains("Log visibility: Allowed viewers:")
679+
contains("Log visibility: Allowed viewers")
680680
.and(contains(principal_alice.as_str()))
681681
.and(contains(principal_bob.as_str())),
682682
);
@@ -1394,7 +1394,11 @@ async fn canister_settings_sync_log_visibility() {
13941394
write_string(&project_dir.join("icp.yaml"), &pm_with_allowed_viewers)
13951395
.expect("failed to write project manifest");
13961396
sync(&ctx, &project_dir);
1397-
confirm_log_visibility(&ctx, &project_dir, "Allowed viewers: 2vxsx-fae, aaaaa-aa");
1397+
confirm_log_visibility(
1398+
&ctx,
1399+
&project_dir,
1400+
"Allowed viewers\n viewer: 2vxsx-fae\n viewer: aaaaa-aa",
1401+
);
13981402

13991403
// status_visibility takes the same manifest forms, and a single sync has to
14001404
// apply both settings: either change alone would satisfy the "settings
@@ -1434,7 +1438,7 @@ async fn canister_settings_sync_log_visibility() {
14341438
.assert()
14351439
.success()
14361440
.stdout(contains(
1437-
"Status visibility: Allowed viewers: 2vxsx-fae, aaaaa-aa",
1441+
"Status visibility: Allowed viewers\n viewer: 2vxsx-fae\n viewer: aaaaa-aa",
14381442
));
14391443
}
14401444

@@ -1547,7 +1551,7 @@ async fn canister_settings_update_status_visibility() {
15471551
&["--add-status-viewer", principal_alice.as_str()],
15481552
);
15491553
status_as_alice(&ctx, &project_dir)
1550-
.stdout(contains("Status: Running").and(contains("Status visibility: Allowed viewers:")));
1554+
.stdout(contains("Status: Running").and(contains("Status visibility: Allowed viewers")));
15511555

15521556
// Add and remove in one call, again relative to the current list. Alice
15531557
// loses access, so the fallback comes back.
@@ -1562,7 +1566,7 @@ async fn canister_settings_update_status_visibility() {
15621566
],
15631567
);
15641568
confirm(&ctx, &project_dir).stdout(
1565-
contains("Status visibility: Allowed viewers:")
1569+
contains("Status visibility: Allowed viewers")
15661570
.and(contains(principal_bob.as_str()))
15671571
.and(contains(principal_alice.as_str()).not()),
15681572
);
@@ -1575,7 +1579,7 @@ async fn canister_settings_update_status_visibility() {
15751579
&["--set-status-viewer", principal_alice.as_str()],
15761580
);
15771581
confirm(&ctx, &project_dir).stdout(
1578-
contains("Status visibility: Allowed viewers:")
1582+
contains("Status visibility: Allowed viewers")
15791583
.and(contains(principal_alice.as_str()))
15801584
.and(contains(principal_bob.as_str()).not()),
15811585
);
@@ -1600,7 +1604,7 @@ async fn canister_settings_update_status_visibility() {
16001604
update(&ctx, &project_dir, &["--freezing-threshold", "7d"]);
16011605
confirm(&ctx, &project_dir).stdout(
16021606
contains("Status visibility: Controllers")
1603-
.and(contains("Log visibility: Allowed viewers:"))
1607+
.and(contains("Log visibility: Allowed viewers"))
16041608
.and(contains(principal_alice.as_str())),
16051609
);
16061610
}

0 commit comments

Comments
 (0)