Skip to content

feat: deploy directly to cloud engines via engine operators - #683

Open
NikolaMilosa wants to merge 3 commits into
mainfrom
nim-deploying-to-engines-through-operator
Open

feat: deploy directly to cloud engines via engine operators#683
NikolaMilosa wants to merge 3 commits into
mainfrom
nim-deploying-to-engines-through-operator

Conversation

@NikolaMilosa

Copy link
Copy Markdown

Motivation

Before this PR, to deploy to a cloud engine we had to directly call the mgmt canister. It would accept the call only if the caller is a subnet admin. Subnet admins are scars resources (we allow 10 per cloud engine) and they give way more power than just creating canisters. For example a subnet admin can go and delete any canister in the cloud engine. To tackle that problem the team added the engine operator canister. Each engine has a dedicated system engine operator canister which is meant to have its own dedicated ACL and do permission checking.

The engine canister, which is the central registry for engines, will provide the mapping from subnet-id to a dedicated engine-operator-id which the icp cli should use to reach the the correct engine operator for a subnet the user wants to deploy to.

Since different environments may deploy the central canister at different locations it is possible to override the engine canister id (the registry canister for engines) by specifying the ENGINE_CANISTER_ID=... variable.

Example call:

ENGINE_CANISTER_ID=5s2ji-faaaa-aaaaa-qaaaq-cai ~/icp-cli-real/target/debug/icp canister create -e dev --detached --subnet 7pnox-zummy-6hxlq-u6qwm-nloqq-wz3bx-slcow-l3vri-2g6iz-ln6wh-zae
WARN fetching the root key from https://opencloud-internal.demo.farm.dfinity.systems/; its provenance is not verified (trust-on-first-use)
Created canister with ID jwksz-eqaaa-aaaab-aaaaq-cai

With this, we can get rid of subnet admins.

Copilot AI review requested due to automatic review settings July 29, 2026 18:28
@NikolaMilosa
NikolaMilosa requested a review from a team as a code owner July 29, 2026 18:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds CloudEngine deployment through per-subnet engine-operator canisters resolved from a configurable registry.

Changes:

  • Adds engine registry interfaces and configuration.
  • Routes CloudEngine creation through engine operators.
  • Retains legacy management-canister fallback.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
crates/icp-cli/src/operations/create.rs Implements operator-based CloudEngine creation.
crates/icp-canister-interfaces/src/lib.rs Exports the new interface module.
crates/icp-canister-interfaces/src/engine_canister.rs Defines registry types, constants, and resolution configuration.
Comments suppressed due to low confidence (1)

crates/icp-canister-interfaces/src/engine_canister.rs:77

  • This test is environment-dependent: engine_canister_id() honors ENGINE_CANISTER_ID, so CI or a developer using a valid alternate registry makes the assertion fail even though the resolver is behaving correctly. Test the built-in constant directly; override behavior should be covered separately with controlled environment isolation.
        assert_eq!(
            engine_canister_id().unwrap(),
            Principal::from_text(ENGINE_CANISTER_CID).unwrap()
        );

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/icp-cli/src/operations/create.rs Outdated
Comment on lines +238 to +245
match self.create_on_cloud_engine(settings, selected_subnet).await {
Ok(cid) => cid,
Err(e) => {
warn!(
"engine-operator creation failed ({e}); \
falling back to management-canister creation"
);
self.create_mgmt(settings, selected_subnet).await?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I addressed this but I don't really know if it is valid. Would be nice to have a second opinion from the reviewer. Also to see if my addressing was correct.

Comment thread crates/icp-canister-interfaces/src/engine_canister.rs
@NikolaMilosa

Copy link
Copy Markdown
Author

Opencloud stack has had this deployed to production so now a cloud engine owner can link their identity and do something like this:

󰣇 (on omarchy) ~/Downloads ❯ ./icp-test identity list
* opencloud-prod-nikola 5rbsx-2blhm-7gpfb-ekmug-3jb64-4bzdx-kzpqo-yisej-3zgdu-ce2ce-pae
  engine-admin-2        5rbsx-2blhm-7gpfb-ekmug-3jb64-4bzdx-kzpqo-yisej-3zgdu-ce2ce-pae
  opencloud-dev         begoe-c6xh2-q3pwn-dd6hb-sitwl-zw2o7-imsaq-q2npf-253kp-bwlar-aqe
  anonymous             2vxsx-fae
# They would have to find their subnet id from settings
󰣇 (on omarchy) ~/Downloads ❯ ./icp-test canister create --detached -e ic --subnet fj2ae-lldsj-bshv6-najg4-b7d4a-dba6x-xrf5n-qav4g-2en7f-w6in7-yqe
Created canister with ID vj246-waaaa-aaabn-aaaca-cai

After that the canister is visible in the UI and is present on the engine
image

// double-create and double-charge. Those errors propagate too.
match self.resolve_engine_operator(selected_subnet).await {
Ok(operator) => self.create_via_operator(settings, operator).await?,
Err(e @ CreateOperationError::NoEngineOperator { .. }) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't this a hard error if we're counting on the operator to be installed on all the cloud engines?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, maybe it makes sense to keep the fallback while things are still in flux.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since our canisters are very new we don't have them deep integrated in the IC ecosystem. For example you can have a perfectly fine running engine (from the protocol perspective) that doesn't have any of these canisters. This is an optimistic handling where we still try to fulfill the users request. In reality, we will change that no-one on mainnet (who is managed by our canisters) will have subnet admin powers.

If we make these hard errors we assume that cloud engines are coupled to these canisters. In reality they are, but if you used proposals today you could do the same without our canisters.

Icp tool should ideally not fail if someone else creates an engine that is completely valid.

Would you prefer it to be different?

/// default: a configured-but-invalid value must never silently route to the
/// built-in registry (and thus a different environment).
pub fn engine_canister_id() -> Result<Principal, String> {
match env::var(ENGINE_CANISTER_ID_ENV) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually collect all the known environment variables very early and avoid having deeply nested modules changes their behavior based on an environment variable.

Also, I assume you want an environment variable because for testnets you might have a different canister id - isn't it possible to force the canister id to be the same on testnets as it is on mainnet like we do for other well known canisters?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a way, I can look into it and maybe we can have it, but still overriding would be useful. For example we may deploy a staging canister to a different id to do some testing ahead of time.

Don't you think?

///
/// Overridable at runtime via the [`ENGINE_CANISTER_ID_ENV`] environment
/// variable — see [`engine_canister_id`].
pub const ENGINE_CANISTER_CID: &str = "q6cfj-fyaaa-aaaar-qb77q-cai";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a chance this canister will move to the system subnet at some point and that we will be forced to change its id?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The chance is at 100% but we don't know when and we don't know what canister id will it have.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants