feat(icp): programmatic deploy API (manifest + create/install + sync) — DRAFT proposal#657
Conversation
…sync) Adds a minimal, self-contained public library surface on the `icp` crate so an external consumer (e.g. a backend deploying marketplace app bundles) can deploy without shelling out to the `icp` binary: - manifest: `project::load_project` + `project::NoRecipes` (prebuilt bundles) - deploy wasms: new `deploy` module (create_canister[_on_subnet], resolve_install_mode, install_wasm with chunking, canister_status) - sync: confirmed drivable via existing public `canister::sync` + PackageCache Includes `crates/icp/examples/deploy_bundle.rs`, which compiles as an external consumer and wires the three capabilities end-to-end, proving the public surface is sufficient. DRAFT proposal for the maintainers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Dear @pietrodimarco-dfinity, In order to potentially merge your code in this open-source repository and therefore proceed with your contribution, we need to have your approval on DFINITY's CLA. If you decide to agree with it, please visit this issue and read the instructions there. Once you have signed it, re-trigger the workflow on this PR to see if your code can be merged. — The DFINITY Foundation |
There was a problem hiding this comment.
Pull request overview
Adds a public Rust API for loading manifests, creating/installing canisters, and synchronizing marketplace bundles without invoking the CLI.
Changes:
- Adds manifest loading with recipe rejection.
- Adds direct and chunked canister deployment APIs.
- Adds an external-consumer deployment example.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
crates/icp/src/project.rs |
Adds NoRecipes and load_project. |
crates/icp/src/lib.rs |
Exposes the deployment module. |
crates/icp/src/deploy.rs |
Implements create, status, and install operations. |
crates/icp/src/canister/recipe/mod.rs |
Adds unsupported-recipe errors. |
crates/icp/examples/deploy_bundle.rs |
Demonstrates programmatic bundle deployment. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ocs, tests - install_wasm: clear chunk store on every failure in the chunked path (best-effort), preserving the original error over any cleanup error; split out upload_and_install_chunked. - deploy docs: state create is direct/cycles-free, suitable for CloudEngine-style subnets only; cycles-ledger/CMC funding is out of scope (use the binary's operations::create). - deploy_bundle example: restructure into create-all / install-all / sync-all phases so the full name->id map is known before controllers, install and sync; resolve+retain manifest controllers and append the deployer without duplicates (From<Settings> drops controllers). - project docs: custom Resolve impls are internal-only (recipe input types are crate-private); external consumers should use NoRecipes. - deploy: extract pure needs_chunked_install and add unit tests for the chunking-threshold boundaries. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New: public bundle extractor (
|
aaf8825 to
853f409
Compare
|
Update: I've removed the bundle extractor ( If a consume-counterpart to 🤖 Generated with Claude Code |
Summary (DRAFT — proposal for the maintainers)
A backend service (dfinity/control-panel) wants to deploy marketplace app bundles programmatically — read + validate a project manifest, create canisters and install their wasm, and sync asset canisters — from Rust, in-process, without shelling out to the
icpbinary.Today the create/install logic lives only in the
icp-clibinary (operations::{create,install}), so an external consumer cannot reach it. This PR adds a minimal, self-contained public library surface on theicpcrate for the three capabilities the maintainer identified, and an example that compiles as an external consumer to prove the surface is sufficient.This is a draft to start the conversation — API naming, whether the binary should converge onto these functions, and distribution (git dep vs. publishing) are all open for the maintainers to shape.
The three capabilities and what each maps to
1. Read, parse and validate the manifest — already public; added a one-call entry point.
icp::project::load_project(dir) -> Result<Project, ProjectLoadError>: loadsicp.yaml, consolidates + validates it into aProject. Wraps the existingload_manifest_from_path+consolidate_manifest, which were public but required the caller to also supply arecipe::Resolve.icp::project::NoRecipes: a publicrecipe::Resolvethat rejects recipe references, for consumers deploying only prebuilt bundles (no@scope/nameregistry recipes). Backed by a newrecipe::ResolveError::Unsupportedvariant.2. Deploy the wasms (create canisters + install code) — new; was binary-only.
New module
icp::deploy(crates/icp/src/deploy.rs) with clean management-canister calls:create_canister(agent, effective_canister_id, settings) -> Principalcreate_canister_on_subnet(agent, subnet, settings) -> Principal(convenience)effective_canister_id_for_subnet(agent, subnet) -> Principalcanister_status(agent, canister_id) -> CanisterStatusResultresolve_install_mode(agent, canister_id) -> CanisterInstallMode(the--mode autologic)install_wasm(agent, canister_id, wasm, mode, init_args)— directinstall_code, transparently switching to chunked install for modules > 2 MiBDeployErrorThis deliberately does not port the binary's cycles-ledger / cycles-minting-canister / proxy-canister funding paths or its progress bars — a plain management-canister create+install is enough for local replicas and cloud-engine subnets, which is what this consumer targets. It modestly duplicates logic in
operations::{create,install}; the binary could converge onto these functions later.3. Sync assets via the wasm plugin — already public; confirmed drivable externally.
No library changes were needed.
icp::canister::sync::{Syncer, Synchronize, Params, SynchronizeError}andicp::package::PackageCacheare already public, and the example drives an asset sync end-to-end through them (the binary'soperations::syncis just a progress-bar wrapper over the sameSynchronize).Example (the proof)
crates/icp/examples/deploy_bundle.rscompiles as an external consumer oficp(so it can only touchpubitems) and wires the full flow in code:load_project→ for each canistercreate_canister_on_subnet+resolve_install_mode+install_wasm→ run eachsyncstep viaSyncer. It performs live calls when given real arguments and prints usage otherwise; CI only needs it to compile, which is the whole point — it forces thepubsurface to be sufficient.Build with:
cargo build -p icp --examples.What is minimal / TODO
install_wasmis a plaininstall_code; unlike the binary it does not stop/start the canister around an upgrade or auto-detect enhanced-orthogonal-persistence. Documented on the function; callers needing those can stop the canister first.// TODO(maintainers)markers are not left in code, but this is the main behavioral gap vs. the binary.API gaps found while doing this
manifest::recipeispub(crate), sorecipe::Recipe/RecipeTypecannot be named outside the crate, which means an external consumer cannot implementrecipe::Resolveor fully destructureConsolidateManifestError::Recipe.NoRecipessidesteps this for prebuilt bundles; supporting registry recipes externally would need those types (and ideally a public registry resolver) made public.icp::canister::Settings→CanisterSettings(From) dropscontrollers(they areControllerRefs resolved elsewhere), so a programmatic caller must setcontrollerson theCanisterSettingsitself — the example does this explicitly.Distribution
publish = falseis left as-is (the crate depends on git revisions ofdfinity/ic, which blocks crates.io). Consumers would use a git dependency on this crate. Whether to restructure for publishing is a maintainer decision.🤖 Generated with Claude Code