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
4 changes: 1 addition & 3 deletions crates/icp-cli/src/operations/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,7 @@ impl CreateOperation {
.get_subnet_by_id(&selected_subnet)
.await
.context(GetSubnetSnafu)?;
let cid = if let Some(SubnetType::Unknown(kind)) = subnet_info.subnet_type()
&& kind == "cloud_engine"
{
let cid = if let Some(SubnetType::CloudEngine) = subnet_info.subnet_type() {
self.create_mgmt(settings, &subnet_info).await?
} else {
self.create_ledger(settings, selected_subnet).await?
Expand Down
80 changes: 79 additions & 1 deletion crates/icp-cli/tests/canister_create_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ use predicates::{
prelude::PredicateBooleanExt,
str::{contains, starts_with},
};
use test_tag::tag;

use crate::common::{ENVIRONMENT_RANDOM_PORT, NETWORK_RANDOM_PORT, TestContext, clients};
use crate::common::{
ENVIRONMENT_DOCKER_ENGINE, ENVIRONMENT_RANDOM_PORT, NETWORK_DOCKER_ENGINE, NETWORK_RANDOM_PORT,
TestContext, clients,
};
use icp::{fs::write_string, prelude::*};

mod common;
Expand Down Expand Up @@ -537,3 +541,77 @@ async fn canister_create_detached() {
.assert()
.failure();
}

#[tag(docker)]
#[tokio::test]
async fn canister_create_cloud_engine() {
let ctx = TestContext::new();

let project_dir = ctx.create_project_dir("icp");

let pm = formatdoc! {r#"
canisters:
- name: my-canister
build:
steps:
- type: script
command: echo hi

{NETWORK_DOCKER_ENGINE}
{ENVIRONMENT_DOCKER_ENGINE}
"#};

write_string(&project_dir.join("icp.yaml"), &pm).expect("failed to write project manifest");

ctx.docker_pull_engine_network();
let _guard = ctx
.start_network_in(&project_dir, "docker-engine-network")
.await;
ctx.ping_until_healthy(&project_dir, "docker-engine-network");

// Find the CloudEngine subnet by querying the topology endpoint
// TODO replace with a subnet selection parameter once we have one
let topology_url = ctx.gateway_url().join("/_/topology").unwrap();
let topology: serde_json::Value = reqwest::get(topology_url)
.await
.expect("failed to fetch topology")
.json()
.await
.expect("failed to parse topology");

let subnet_configs = topology["subnet_configs"]
.as_object()
.expect("subnet_configs should be an object");
let cloud_engine_subnet_id = subnet_configs
.iter()
.find_map(|(id, config)| {
(config["subnet_kind"].as_str()? == "CloudEngine").then_some(id.clone())
})
.expect("no CloudEngine subnet found in topology");

// Create the canister on the CloudEngine subnet
// Only the admin can do this. In local envs, the admin is the anonymous principal
ctx.icp()
.current_dir(&project_dir)
.args([
"canister",
"create",
"my-canister",
"--subnet",
&cloud_engine_subnet_id,
"--environment",
"docker-engine-environment",
])
.assert()
.success();

let id_mapping_path = project_dir
.join(".icp")
.join("cache")
.join("mappings")
.join("docker-engine-environment.ids.json");
assert!(
id_mapping_path.exists(),
"ID mapping file should exist at {id_mapping_path}"
);
}
19 changes: 13 additions & 6 deletions crates/icp-cli/tests/common/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,10 @@ impl TestContext {
.expect("Failed to write network descriptor file");
}

pub(crate) fn gateway_url(&self) -> &Url {
self.http_gateway_url.get().unwrap()
}

pub(crate) fn agent(&self) -> Agent {
let agent = Agent::builder()
.with_url(self.http_gateway_url.get().unwrap().as_str())
Expand Down Expand Up @@ -441,18 +445,21 @@ impl TestContext {
}

pub(crate) fn docker_pull_network(&self) {
self.docker_pull_image("ghcr.io/dfinity/icp-cli-network-launcher:v11.0.0");
}

pub(crate) fn docker_pull_engine_network(&self) {
self.docker_pull_image("ghcr.io/dfinity/icp-cli-network-launcher:engine-beta");
}

fn docker_pull_image(&self, image: &str) {
let platform = if cfg!(target_arch = "aarch64") {
"linux/arm64"
} else {
"linux/amd64"
};
Command::new("docker")
.args([
"pull",
"--platform",
platform,
"ghcr.io/dfinity/icp-cli-network-launcher:v11.0.0",
])
.args(["pull", "--platform", platform, image])
.assert()
.success();
}
Expand Down
16 changes: 16 additions & 0 deletions crates/icp-cli/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ environments:
network: docker-network
"#;

pub(crate) const NETWORK_DOCKER_ENGINE: &str = r#"
networks:
- name: docker-engine-network
mode: managed
image: ghcr.io/dfinity/icp-cli-network-launcher:engine-beta
port-mapping:
- 0:4943
- 0:4942
"#;

pub(crate) const ENVIRONMENT_DOCKER_ENGINE: &str = r#"
environments:
- name: docker-engine-environment
network: docker-engine-network
"#;

/// This ID is dependent on the toplogy being served by pocket-ic
/// NOTE: If the topology is changed (another subnet is added, etc) the ID may change.
/// References:
Expand Down
86 changes: 85 additions & 1 deletion crates/icp-cli/tests/deploy_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ use predicates::{
ord::eq,
str::{PredicateStrExt, contains},
};
use test_tag::tag;

use crate::common::{ENVIRONMENT_RANDOM_PORT, NETWORK_RANDOM_PORT, TestContext, clients};
use crate::common::{
ENVIRONMENT_DOCKER_ENGINE, ENVIRONMENT_RANDOM_PORT, NETWORK_DOCKER_ENGINE, NETWORK_RANDOM_PORT,
TestContext, clients,
};
use icp::{
fs::{create_dir_all, write_string},
prelude::*,
Expand Down Expand Up @@ -599,3 +603,83 @@ async fn deploy_upgrade_rejects_incompatible_candid() {
.success()
.stdout(eq("(\"Hello, 42!\")").trim());
}

#[tag(docker)]
#[tokio::test]
async fn deploy_cloud_engine() {
let ctx = TestContext::new();

let project_dir = ctx.create_project_dir("icp");

let wasm = ctx.make_asset("example_icp_mo.wasm");

let pm = formatdoc! {r#"
canisters:
- name: my-canister
build:
steps:
- type: script
command: cp '{wasm}' "$ICP_WASM_OUTPUT_PATH"

{NETWORK_DOCKER_ENGINE}
{ENVIRONMENT_DOCKER_ENGINE}
"#};

write_string(&project_dir.join("icp.yaml"), &pm).expect("failed to write project manifest");

ctx.docker_pull_engine_network();
let _guard = ctx
.start_network_in(&project_dir, "docker-engine-network")
.await;
ctx.ping_until_healthy(&project_dir, "docker-engine-network");

// Find the CloudEngine subnet by querying the topology endpoint
// TODO replace with a subnet selection parameter once we have one
let topology_url = ctx.gateway_url().join("/_/topology").unwrap();
let topology: serde_json::Value = reqwest::get(topology_url)
.await
.expect("failed to fetch topology")
.json()
.await
.expect("failed to parse topology");

let subnet_configs = topology["subnet_configs"]
.as_object()
.expect("subnet_configs should be an object");
let cloud_engine_subnet_id = subnet_configs
.iter()
.find_map(|(id, config)| {
(config["subnet_kind"].as_str()? == "CloudEngine").then_some(id.clone())
})
.expect("no CloudEngine subnet found in topology");

// Deploy to the CloudEngine subnet
// Only the admin can do this. In local envs, the admin is the anonymous principal
ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
&cloud_engine_subnet_id,
"--environment",
"docker-engine-environment",
])
.assert()
.success();

// Query canister to verify it works
ctx.icp()
.current_dir(&project_dir)
.args([
"canister",
"call",
"--environment",
"docker-engine-environment",
"my-canister",
"greet",
"(\"test\")",
])
.assert()
.success()
.stdout(eq("(\"Hello, test!\")").trim());
}
Loading