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
72 changes: 72 additions & 0 deletions e2e/bats/controller.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env bats

load utils/_

setup() {
# We want to work from a temporary directory, different for every test.
cd $(mktemp -d -t dfx-e2e-XXXXXXXX)

# Each test gets its own home directory in order to have its own identities.
mkdir $(pwd)/home-for-test
export HOME=$(pwd)/home-for-test

dfx_new hello
}

teardown() {
dfx_stop
rm -rf $(pwd)/home-for-test
}

@test "set controller" {
# Create two identities and get their Principals
assert_command dfx identity new jose
assert_command dfx identity new juana
JOSE_PRINCIPAL=$(dfx --identity jose identity get-principal)
JUANA_PRINCIPAL=$(dfx --identity juana identity get-principal)

assert_command dfx identity use jose

dfx_start
dfx canister create hello
dfx build hello
dfx canister install hello
ID=$(dfx canister id hello)

# Set controller using canister name and identity name
assert_command dfx canister set-controller hello juana
assert_match "Set \"juana\" as controller of \"hello\"."

# Juana is controller, Jose cannot reinstall
assert_command_fail dfx canister install hello -m reinstall
if [ "$USE_IC_REF" ]
then
assert_match "${JOSE_PRINCIPAL} is not authorized to manage canister ${ID}"
else
assert_match "Only the controller of canister ${ID} can control it."
fi

# Juana can reinstall
assert_command dfx --identity juana canister install hello -m reinstall

assert_command dfx identity use juana
# Set controller using canister id and principal
assert_command dfx canister set-controller ${ID} ${JOSE_PRINCIPAL}
assert_match "Set \"${JOSE_PRINCIPAL}\" as controller of \"${ID}\"."
assert_command_fail dfx canister install hello -m reinstall

# Set controller using combination of name/id and identity/principal
assert_command dfx --identity jose canister set-controller hello ${JUANA_PRINCIPAL}
assert_match "Set \"${JUANA_PRINCIPAL}\" as controller of \"hello\"."

assert_command dfx --identity juana canister set-controller ${ID} jose
assert_match "Set \"jose\" as controller of \"${ID}\"."

# Set controller using invalid principal/identity fails
assert_command_fail dfx --identity jose canister set-controller hello bob
assert_match "Identity bob does not exist"

# Set controller using invalid canister name/id fails
assert_command_fail dfx --identity jose canister set-controller hello_assets juana
assert_match "Cannot find canister id. Please issue 'dfx canister create hello_assets'."
}
2 changes: 2 additions & 0 deletions src/dfx/src/commands/canister/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod delete;
mod id;
mod install;
mod request_status;
mod set_controller;
mod start;
mod status;
mod stop;
Expand All @@ -23,6 +24,7 @@ fn builtins() -> Vec<CliCommand> {
CliCommand::new(id::construct(), id::exec),
CliCommand::new(install::construct(), install::exec),
CliCommand::new(request_status::construct(), request_status::exec),
CliCommand::new(set_controller::construct(), set_controller::exec),
CliCommand::new(start::construct(), start::exec),
CliCommand::new(status::construct(), status::exec),
CliCommand::new(stop::construct(), stop::exec),
Expand Down
62 changes: 62 additions & 0 deletions src/dfx/src/commands/canister/set_controller.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use crate::lib::environment::Environment;
use crate::lib::error::{DfxError, DfxResult};
use crate::lib::identity::identity_manager::IdentityManager;
use crate::lib::message::UserMessage;
use crate::lib::models::canister_id_store::CanisterIdStore;
use crate::lib::waiter::waiter_with_timeout;
use crate::util::expiry_duration;
use clap::{App, Arg, ArgMatches, SubCommand};
use ic_agent::Identity;
use ic_types::principal::Principal as CanisterId;
use ic_utils::call::AsyncCall;
use ic_utils::interfaces::ManagementCanister;
use tokio::runtime::Runtime;

pub fn construct() -> App<'static, 'static> {
SubCommand::with_name("set-controller")
.about(UserMessage::SetController.to_str())
.arg(
Arg::with_name("canister")
.takes_value(true)
.help(UserMessage::SetControllerCanister.to_str())
.required(true),
)
.arg(
Arg::with_name("new-controller")
.takes_value(true)
.help(UserMessage::NewController.to_str())
.required(true),
)
}

pub fn exec(env: &dyn Environment, args: &ArgMatches<'_>) -> DfxResult {
let canister = args.value_of("canister").unwrap();
let canister_id = match CanisterId::from_text(canister) {
Ok(id) => id,
Err(_) => CanisterIdStore::for_env(env)?.get(canister)?,
};

let new_controller = args.value_of("new-controller").unwrap();
let controller_principal = match CanisterId::from_text(new_controller) {
Ok(principal) => principal,
Err(_) => IdentityManager::new(env)?
.instantiate_identity_from_name(new_controller)?
.sender()?,
};

let timeout = expiry_duration();

let mgr = ManagementCanister::create(
env.get_agent()
.ok_or(DfxError::CommandMustBeRunInAProject)?,
);

let mut runtime = Runtime::new().expect("Unable to create a runtime");
runtime.block_on(
mgr.set_controller(&canister_id, &controller_principal)
.call_and_wait(waiter_with_timeout(timeout)),
)?;

println!("Set {:?} as controller of {:?}.", new_controller, canister);
Ok(())
}
15 changes: 10 additions & 5 deletions src/dfx/src/lib/identity/identity_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,16 @@ impl IdentityManager {

/// Create an Identity instance for use with an Agent
pub fn instantiate_selected_identity(&self) -> DfxResult<Box<impl Identity + Send + Sync>> {
let pem_path = self.get_selected_identity_pem_path();
self.instantiate_identity_from_name(self.selected_identity.as_str())
}

/// Provide a valid Identity name and create its Identity instance for use with an Agent
pub fn instantiate_identity_from_name(
&self,
identity_name: &str,
) -> DfxResult<Box<impl Identity + Send + Sync>> {
self.require_identity_exists(identity_name)?;
let pem_path = self.get_identity_pem_path(identity_name);
Ok(Box::new(BasicIdentity::from_pem_file(&pem_path).map_err(
|e| DfxError::IdentityError(IdentityErrorKind::AgentPemError(e, pem_path.clone())),
)?))
Expand Down Expand Up @@ -206,10 +215,6 @@ impl IdentityManager {
fn get_identity_pem_path(&self, identity: &str) -> PathBuf {
self.get_identity_dir_path(identity).join(IDENTITY_PEM)
}

fn get_selected_identity_pem_path(&self) -> PathBuf {
self.get_identity_pem_path(&self.selected_identity)
}
}

fn initialize(
Expand Down
5 changes: 5 additions & 0 deletions src/dfx/src/lib/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ user_message!(
DeleteCanisterName => "Specifies the name of the canister to delete. You must specify either a canister name or the --all flag.",
DeleteAll => "Deletes all of the canisters configured in the dfx.json file.",

// dfx canister set-controller
SetController => "Sets the provided identity's name or its principal as the new controller of a canister on the Internet Computer network.",
SetControllerCanister => "Specifies the canister name or the canister identifier for the canister to be controlled.",
NewController => "Specifies the identity name or the principal of the new controller.",

// dfx canister status
CanisterStatus => "Returns the current status of the canister on the Internet Computer network: Running, Stopping, or Stopped.",
StatusCanisterName => "Specifies the name of the canister to return information for. You must specify either a canister name or the --all flag.",
Expand Down