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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Unreleased

* feat: `icp canister create --proxy` to create canisters via a proxy canister
* feat: Add `--proxy` to `icp canister` subcommands and `icp deploy` to route management canister calls through a proxy canister

# v0.2.2

Expand Down
1 change: 1 addition & 0 deletions crates/icp-cli/src/commands/canister/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ pub(crate) async fn exec(ctx: &Context, args: &CallArgs) -> Result<(), anyhow::E
&method,
arg_bytes,
args.proxy,
None,
args.cycles.get(),
)
.await?
Expand Down
15 changes: 9 additions & 6 deletions crates/icp-cli/src/commands/canister/delete.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
use candid::Principal;
use clap::Args;
use ic_management_canister_types::CanisterIdRecord;
use icp::context::{CanisterSelection, Context};

use crate::commands::args;
use crate::{commands::args, operations::proxy_management};

/// Delete a canister from a network
#[derive(Debug, Args)]
pub(crate) struct DeleteArgs {
#[command(flatten)]
pub(crate) cmd_args: args::CanisterCommandArgs,

/// Principal of a proxy canister to route the management canister call through.
#[arg(long)]
pub(crate) proxy: Option<Principal>,
}

pub(crate) async fn exec(ctx: &Context, args: &DeleteArgs) -> Result<(), anyhow::Error> {
Expand All @@ -28,11 +34,8 @@ pub(crate) async fn exec(ctx: &Context, args: &DeleteArgs) -> Result<(), anyhow:
)
.await?;

// Management Interface
let mgmt = ic_utils::interfaces::ManagementCanister::create(&agent);

// Instruct management canister to delete canister
mgmt.delete_canister(&cid).await?;
proxy_management::delete_canister(&agent, args.proxy, CanisterIdRecord { canister_id: cid })
.await?;

// Remove canister ID from the id store if it was referenced by name
if let CanisterSelection::Named(canister_name) = &selections.canister {
Expand Down
19 changes: 15 additions & 4 deletions crates/icp-cli/src/commands/canister/install.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::io::IsTerminal;

use anyhow::{Context as _, anyhow, bail};
use candid::Principal;
use clap::Args;
use dialoguer::Confirm;
use ic_utils::interfaces::management_canister::builders::CanisterInstallMode;
use ic_management_canister_types::CanisterInstallMode;
use icp::context::{CanisterSelection, Context};
use icp::manifest::InitArgsFormat;
use icp::prelude::*;
Expand Down Expand Up @@ -47,6 +48,10 @@ pub(crate) struct InstallArgs {

#[command(flatten)]
pub(crate) cmd_args: args::CanisterCommandArgs,

/// Principal of a proxy canister to route the management canister call through.
#[arg(long)]
pub(crate) proxy: Option<Principal>,
}

pub(crate) async fn exec(ctx: &Context, args: &InstallArgs) -> Result<(), anyhow::Error> {
Expand Down Expand Up @@ -121,9 +126,14 @@ pub(crate) async fn exec(ctx: &Context, args: &InstallArgs) -> Result<(), anyhow
.transpose()?;

let canister_display = args.cmd_args.canister.to_string();
let (install_mode, status) =
resolve_install_mode_and_status(&agent, &canister_display, &canister_id, &args.mode)
.await?;
let (install_mode, status) = resolve_install_mode_and_status(
&agent,
args.proxy,
&canister_display,
&canister_id,
&args.mode,
)
.await?;

// Candid interface compatibility check for upgrades
if !args.yes && matches!(install_mode, CanisterInstallMode::Upgrade(_)) {
Expand Down Expand Up @@ -156,6 +166,7 @@ pub(crate) async fn exec(ctx: &Context, args: &InstallArgs) -> Result<(), anyhow

install_canister(
&agent,
args.proxy,
&canister_id,
&canister_display,
&wasm,
Expand Down
35 changes: 20 additions & 15 deletions crates/icp-cli/src/commands/canister/logs.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
use std::io::stdout;

use anyhow::{Context as _, anyhow};
use candid::Principal;
use clap::Args;
use ic_utils::interfaces::ManagementCanister;
use ic_utils::interfaces::management_canister::{
CanisterLogFilter, CanisterLogRecord, FetchCanisterLogsArgs, FetchCanisterLogsResult,
};
use ic_agent::Agent;
use ic_management_canister_types::{CanisterLogFilter, CanisterLogRecord, FetchCanisterLogsArgs};
use icp::context::Context;
use icp::signal::stop_signal;
use itertools::Itertools;
use serde::Serialize;
use time::{OffsetDateTime, format_description::well_known::Rfc3339};
use tokio::select;

use crate::commands::args;
use crate::{commands::args, operations::proxy_management};

/// Fetch and display canister logs
#[derive(Debug, Args)]
Expand Down Expand Up @@ -50,6 +49,13 @@ pub(crate) struct LogsArgs {
/// Output command results as JSON
#[arg(long)]
pub(crate) json: bool,

/// Principal of a proxy canister to route the management canister call through.
///
/// Hidden until the IC supports fetch_canister_logs in replicated mode.
/// Tracking: https://github.com/dfinity/portal/pull/6106
#[arg(long, hide = true)]
pub(crate) proxy: Option<Principal>,
}

fn parse_timestamp(s: &str) -> Result<u64, String> {
Expand Down Expand Up @@ -103,14 +109,12 @@ pub(crate) async fn exec(ctx: &Context, args: &LogsArgs) -> Result<(), anyhow::E
)
.await?;

let mgmt = ManagementCanister::create(&agent);

if args.follow {
// Follow mode: continuously fetch and display new logs
follow_logs(args, &mgmt, &canister_id, args.interval).await
follow_logs(args, &agent, args.proxy, &canister_id, args.interval).await
} else {
// Single fetch mode: fetch all logs once
fetch_and_display_logs(args, &mgmt, &canister_id, build_filter(args)?).await
fetch_and_display_logs(args, &agent, args.proxy, &canister_id, build_filter(args)?).await
}
}

Expand Down Expand Up @@ -161,16 +165,16 @@ fn build_filter(args: &LogsArgs) -> Result<Option<CanisterLogFilter>, anyhow::Er

async fn fetch_and_display_logs(
args: &LogsArgs,
mgmt: &ManagementCanister<'_>,
agent: &Agent,
proxy: Option<Principal>,
canister_id: &candid::Principal,
filter: Option<CanisterLogFilter>,
) -> Result<(), anyhow::Error> {
let fetch_args = FetchCanisterLogsArgs {
canister_id: *canister_id,
filter,
};
let (result,): (FetchCanisterLogsResult,) = mgmt
.fetch_canister_logs(&fetch_args)
let result = proxy_management::fetch_canister_logs(agent, proxy, fetch_args)
.await
.context("Failed to fetch canister logs")?;

Expand Down Expand Up @@ -207,7 +211,8 @@ const FOLLOW_LOOKBACK_NANOS: u64 = 60 * 60 * 1_000_000_000; // 1 hour

async fn follow_logs(
args: &LogsArgs,
mgmt: &ManagementCanister<'_>,
agent: &Agent,
proxy: Option<Principal>,
canister_id: &candid::Principal,
interval_seconds: u64,
) -> Result<(), anyhow::Error> {
Expand Down Expand Up @@ -237,8 +242,7 @@ async fn follow_logs(
canister_id: *canister_id,
filter,
};
let (result,): (FetchCanisterLogsResult,) = mgmt
.fetch_canister_logs(&fetch_args)
let result = proxy_management::fetch_canister_logs(agent, proxy, fetch_args)
.await
.context("Failed to fetch canister logs")?;

Expand Down Expand Up @@ -440,6 +444,7 @@ mod tests {
since_index,
until_index,
json: false,
proxy: None,
}
}

Expand Down
75 changes: 58 additions & 17 deletions crates/icp-cli/src/commands/canister/migrate_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use std::time::{Duration, Instant};
use anyhow::bail;
use clap::Args;
use dialoguer::Confirm;
use ic_management_canister_types::CanisterStatusType;
use ic_utils::interfaces::ManagementCanister;
use ic_management_canister_types::{
CanisterIdRecord, CanisterSettings, CanisterStatusType, UpdateSettingsArgs,
};
use icp::context::Context;
use icp_canister_interfaces::nns_migration::{MigrationStatus, NNS_MIGRATION_PRINCIPAL};
use indicatif::{ProgressBar, ProgressStyle};
Expand All @@ -17,6 +18,7 @@ use crate::operations::canister_migration::{
get_subnet_for_canister, migrate_canister, migration_status,
};
use crate::operations::misc::format_timestamp;
use crate::operations::proxy_management;
use icp::context::CanisterSelection;

/// Minimum cycles required for migration (10T).
Expand Down Expand Up @@ -45,6 +47,10 @@ pub(crate) struct MigrateIdArgs {
/// Exit as soon as the migrated canister is deleted (don't wait for full completion)
#[arg(long)]
skip_watch: bool,

/// Principal of a proxy canister to route the management canister calls through.
#[arg(long)]
proxy: Option<candid::Principal>,
}

pub(crate) async fn exec(ctx: &Context, args: &MigrateIdArgs) -> Result<(), anyhow::Error> {
Expand Down Expand Up @@ -105,11 +111,23 @@ pub(crate) async fn exec(ctx: &Context, args: &MigrateIdArgs) -> Result<(), anyh
}
}

let mgmt = ManagementCanister::create(&agent);

// Fetch status of both canisters
let (source_status,) = mgmt.canister_status(&source_cid).await?;
let (target_status,) = mgmt.canister_status(&target_cid).await?;
let source_status = proxy_management::canister_status(
&agent,
args.proxy,
CanisterIdRecord {
canister_id: source_cid,
},
)
.await?;
let target_status = proxy_management::canister_status(
&agent,
args.proxy,
CanisterIdRecord {
canister_id: target_cid,
},
)
.await?;

// Check both are stopped
ensure_canister_stopped(source_status.status, &source_name)?;
Expand Down Expand Up @@ -156,7 +174,14 @@ pub(crate) async fn exec(ctx: &Context, args: &MigrateIdArgs) -> Result<(), anyh
}

// Check target canister has no snapshots
let (snapshots,) = mgmt.list_canister_snapshots(&target_cid).await?;
let snapshots = proxy_management::list_canister_snapshots(
&agent,
args.proxy,
CanisterIdRecord {
canister_id: target_cid,
},
)
.await?;
if !snapshots.is_empty() {
bail!(
"The target canister '{target_name}' ({target_cid}) has {} snapshot(s). \
Expand Down Expand Up @@ -188,23 +213,39 @@ pub(crate) async fn exec(ctx: &Context, args: &MigrateIdArgs) -> Result<(), anyh
info!("Adding NNS migration canister as controller of '{source_name}'...");
let mut new_controllers = source_controllers;
new_controllers.push(NNS_MIGRATION_PRINCIPAL);
let mut builder = mgmt.update_settings(&source_cid);
for controller in new_controllers {
builder = builder.with_controller(controller);
}
builder.await?;
proxy_management::update_settings(
&agent,
args.proxy,
UpdateSettingsArgs {
canister_id: source_cid,
settings: CanisterSettings {
controllers: Some(new_controllers),
..Default::default()
},
sender_canister_version: None,
},
)
.await?;
}

let target_controllers = target_status.settings.controllers;
if !target_controllers.contains(&NNS_MIGRATION_PRINCIPAL) {
info!("Adding NNS migration canister as controller of '{target_name}'...");
let mut new_controllers = target_controllers;
new_controllers.push(NNS_MIGRATION_PRINCIPAL);
let mut builder = mgmt.update_settings(&target_cid);
for controller in new_controllers {
builder = builder.with_controller(controller);
}
builder.await?;
proxy_management::update_settings(
&agent,
args.proxy,
UpdateSettingsArgs {
canister_id: target_cid,
settings: CanisterSettings {
controllers: Some(new_controllers),
..Default::default()
},
sender_canister_version: None,
},
)
.await?;
}

// Initiate migration
Expand Down
10 changes: 6 additions & 4 deletions crates/icp-cli/src/commands/canister/settings/sync.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::bail;
use candid::Principal;
use clap::Args;
use ic_utils::interfaces::ManagementCanister;
use icp::context::{CanisterSelection, Context};

use crate::commands::args::CanisterCommandArgs;
Expand All @@ -10,6 +10,10 @@ use crate::commands::args::CanisterCommandArgs;
pub(crate) struct SyncArgs {
#[command(flatten)]
cmd_args: CanisterCommandArgs,

/// Principal of a proxy canister to route the management canister calls through.
#[arg(long)]
proxy: Option<Principal>,
}

pub(crate) async fn exec(ctx: &Context, args: &SyncArgs) -> Result<(), anyhow::Error> {
Expand Down Expand Up @@ -37,8 +41,6 @@ pub(crate) async fn exec(ctx: &Context, args: &SyncArgs) -> Result<(), anyhow::E
)
.await?;

let mgmt = ManagementCanister::create(&agent);

crate::operations::settings::sync_settings(&mgmt, &cid, &canister).await?;
crate::operations::settings::sync_settings(&agent, args.proxy, &cid, &canister).await?;
Ok(())
}
Loading
Loading