1#[doc(inline)]
4pub use super::attributes::{
5 ComputeAllocation, FreezingThreshold, MemoryAllocation, ReservedCyclesLimit, WasmMemoryLimit,
6};
7use super::{ChunkHash, LogVisibility, ManagementCanister};
8use crate::call::CallFuture;
9use crate::{
10 call::AsyncCall, canister::Argument, interfaces::management_canister::MgmtMethod, Canister,
11};
12use async_trait::async_trait;
13use candid::{utils::ArgumentEncoder, CandidType, Deserialize, Nat};
14use futures_util::{
15 future::ready,
16 stream::{self, FuturesUnordered},
17 FutureExt, Stream, StreamExt, TryStreamExt,
18};
19use ic_agent::{agent::CallResponse, export::Principal, AgentError};
20pub use ic_management_canister_types::{
21 CanisterInstallMode, CanisterSettings, EnvironmentVariable, InstallCodeArgs, UpgradeFlags,
22 WasmMemoryPersistence,
23};
24use sha2::{Digest, Sha256};
25use std::{
26 collections::BTreeSet,
27 convert::{From, TryInto},
28 future::IntoFuture,
29 pin::Pin,
30};
31
32#[derive(Debug)]
34pub struct CreateCanisterBuilder<'agent, 'canister: 'agent> {
35 canister: &'canister Canister<'agent>,
36 effective_canister_id: Principal,
37 controllers: Option<Result<Vec<Principal>, AgentError>>,
38 compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
39 memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
40 freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
41 reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
42 wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
43 wasm_memory_threshold: Option<Result<WasmMemoryLimit, AgentError>>,
44 log_visibility: Option<Result<LogVisibility, AgentError>>,
45 environment_variables: Option<Result<Vec<EnvironmentVariable>, AgentError>>,
46 is_provisional_create: bool,
47 amount: Option<u128>,
48 specified_id: Option<Principal>,
49}
50
51impl<'agent, 'canister: 'agent> CreateCanisterBuilder<'agent, 'canister> {
52 pub fn builder(canister: &'canister Canister<'agent>) -> Self {
54 Self {
55 canister,
56 effective_canister_id: Principal::management_canister(),
57 controllers: None,
58 compute_allocation: None,
59 memory_allocation: None,
60 freezing_threshold: None,
61 reserved_cycles_limit: None,
62 wasm_memory_limit: None,
63 wasm_memory_threshold: None,
64 log_visibility: None,
65 environment_variables: None,
66 is_provisional_create: false,
67 amount: None,
68 specified_id: None,
69 }
70 }
71
72 #[allow(clippy::wrong_self_convention)]
79 pub fn as_provisional_create_with_amount(self, amount: Option<u128>) -> Self {
80 Self {
81 is_provisional_create: true,
82 amount,
83 ..self
84 }
85 }
86
87 pub fn as_provisional_create_with_specified_id(self, specified_id: Principal) -> Self {
92 Self {
93 is_provisional_create: true,
94 specified_id: Some(specified_id),
95 effective_canister_id: specified_id,
96 ..self
97 }
98 }
99
100 pub fn with_effective_canister_id<C, E>(self, effective_canister_id: C) -> Self
102 where
103 E: std::fmt::Display,
104 C: TryInto<Principal, Error = E>,
105 {
106 match effective_canister_id.try_into() {
107 Ok(effective_canister_id) => Self {
108 effective_canister_id,
109 ..self
110 },
111 Err(_) => self,
112 }
113 }
114
115 pub fn with_optional_controller<C, E>(self, controller: Option<C>) -> Self
118 where
119 E: std::fmt::Display,
120 C: TryInto<Principal, Error = E>,
121 {
122 let controller_to_add: Option<Result<Principal, _>> = controller.map(|ca| {
123 ca.try_into()
124 .map_err(|e| AgentError::MessageError(format!("{e}")))
125 });
126 let controllers: Option<Result<Vec<Principal>, _>> =
127 match (controller_to_add, self.controllers) {
128 (_, Some(Err(sticky))) => Some(Err(sticky)),
129 (Some(Err(e)), _) => Some(Err(e)),
130 (None, _) => None,
131 (Some(Ok(controller)), Some(Ok(controllers))) => {
132 let mut controllers = controllers;
133 controllers.push(controller);
134 Some(Ok(controllers))
135 }
136 (Some(Ok(controller)), None) => Some(Ok(vec![controller])),
137 };
138 Self {
139 controllers,
140 ..self
141 }
142 }
143
144 pub fn with_controller<C, E>(self, controller: C) -> Self
146 where
147 E: std::fmt::Display,
148 C: TryInto<Principal, Error = E>,
149 {
150 self.with_optional_controller(Some(controller))
151 }
152
153 pub fn with_optional_compute_allocation<C, E>(self, compute_allocation: Option<C>) -> Self
156 where
157 E: std::fmt::Display,
158 C: TryInto<ComputeAllocation, Error = E>,
159 {
160 Self {
161 compute_allocation: compute_allocation.map(|ca| {
162 ca.try_into()
163 .map_err(|e| AgentError::MessageError(format!("{e}")))
164 }),
165 ..self
166 }
167 }
168
169 pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
171 where
172 E: std::fmt::Display,
173 C: TryInto<ComputeAllocation, Error = E>,
174 {
175 self.with_optional_compute_allocation(Some(compute_allocation))
176 }
177
178 pub fn with_optional_memory_allocation<E, C>(self, memory_allocation: Option<C>) -> Self
181 where
182 E: std::fmt::Display,
183 C: TryInto<MemoryAllocation, Error = E>,
184 {
185 Self {
186 memory_allocation: memory_allocation.map(|ma| {
187 ma.try_into()
188 .map_err(|e| AgentError::MessageError(format!("{e}")))
189 }),
190 ..self
191 }
192 }
193
194 pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
196 where
197 E: std::fmt::Display,
198 C: TryInto<MemoryAllocation, Error = E>,
199 {
200 self.with_optional_memory_allocation(Some(memory_allocation))
201 }
202
203 pub fn with_optional_freezing_threshold<E, C>(self, freezing_threshold: Option<C>) -> Self
206 where
207 E: std::fmt::Display,
208 C: TryInto<FreezingThreshold, Error = E>,
209 {
210 Self {
211 freezing_threshold: freezing_threshold.map(|ma| {
212 ma.try_into()
213 .map_err(|e| AgentError::MessageError(format!("{e}")))
214 }),
215 ..self
216 }
217 }
218
219 pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
221 where
222 E: std::fmt::Display,
223 C: TryInto<FreezingThreshold, Error = E>,
224 {
225 self.with_optional_freezing_threshold(Some(freezing_threshold))
226 }
227
228 pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
230 where
231 E: std::fmt::Display,
232 C: TryInto<ReservedCyclesLimit, Error = E>,
233 {
234 self.with_optional_reserved_cycles_limit(Some(limit))
235 }
236
237 pub fn with_optional_reserved_cycles_limit<E, C>(self, limit: Option<C>) -> Self
240 where
241 E: std::fmt::Display,
242 C: TryInto<ReservedCyclesLimit, Error = E>,
243 {
244 Self {
245 reserved_cycles_limit: limit.map(|limit| {
246 limit
247 .try_into()
248 .map_err(|e| AgentError::MessageError(format!("{e}")))
249 }),
250 ..self
251 }
252 }
253
254 pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
256 where
257 E: std::fmt::Display,
258 C: TryInto<WasmMemoryLimit, Error = E>,
259 {
260 self.with_optional_wasm_memory_limit(Some(wasm_memory_limit))
261 }
262
263 pub fn with_optional_wasm_memory_limit<E, C>(self, wasm_memory_limit: Option<C>) -> Self
266 where
267 E: std::fmt::Display,
268 C: TryInto<WasmMemoryLimit, Error = E>,
269 {
270 Self {
271 wasm_memory_limit: wasm_memory_limit.map(|limit| {
272 limit
273 .try_into()
274 .map_err(|e| AgentError::MessageError(format!("{e}")))
275 }),
276 ..self
277 }
278 }
279
280 pub fn with_wasm_memory_threshold<C, E>(self, wasm_memory_threshold: C) -> Self
282 where
283 E: std::fmt::Display,
284 C: TryInto<WasmMemoryLimit, Error = E>,
285 {
286 self.with_optional_wasm_memory_threshold(Some(wasm_memory_threshold))
287 }
288
289 pub fn with_optional_wasm_memory_threshold<E, C>(self, wasm_memory_threshold: Option<C>) -> Self
292 where
293 E: std::fmt::Display,
294 C: TryInto<WasmMemoryLimit, Error = E>,
295 {
296 Self {
297 wasm_memory_threshold: wasm_memory_threshold.map(|limit| {
298 limit
299 .try_into()
300 .map_err(|e| AgentError::MessageError(format!("{e}")))
301 }),
302 ..self
303 }
304 }
305
306 pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
308 where
309 E: std::fmt::Display,
310 C: TryInto<LogVisibility, Error = E>,
311 {
312 self.with_optional_log_visibility(Some(log_visibility))
313 }
314
315 pub fn with_optional_log_visibility<E, C>(self, log_visibility: Option<C>) -> Self
318 where
319 E: std::fmt::Display,
320 C: TryInto<LogVisibility, Error = E>,
321 {
322 Self {
323 log_visibility: log_visibility.map(|visibility| {
324 visibility
325 .try_into()
326 .map_err(|e| AgentError::MessageError(format!("{e}")))
327 }),
328 ..self
329 }
330 }
331
332 pub fn with_environment_variables<E, C>(self, environment_variables: C) -> Self
334 where
335 E: std::fmt::Display,
336 C: TryInto<Vec<EnvironmentVariable>, Error = E>,
337 {
338 self.with_optional_environment_variables(Some(environment_variables))
339 }
340
341 pub fn with_optional_environment_variables<E, C>(self, environment_variables: Option<C>) -> Self
344 where
345 E: std::fmt::Display,
346 C: TryInto<Vec<EnvironmentVariable>, Error = E>,
347 {
348 Self {
349 environment_variables: environment_variables.map(|vars| {
350 vars.try_into()
351 .map_err(|e| AgentError::MessageError(format!("{e}")))
352 }),
353 ..self
354 }
355 }
356
357 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = (Principal,)>, AgentError> {
360 let controllers = match self.controllers {
361 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
362 Some(Ok(x)) => Some(x),
363 None => None,
364 };
365 let compute_allocation = match self.compute_allocation {
366 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
367 Some(Ok(x)) => Some(Nat::from(u8::from(x))),
368 None => None,
369 };
370 let memory_allocation = match self.memory_allocation {
371 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
372 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
373 None => None,
374 };
375 let freezing_threshold = match self.freezing_threshold {
376 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
377 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
378 None => None,
379 };
380 let reserved_cycles_limit = match self.reserved_cycles_limit {
381 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
382 Some(Ok(x)) => Some(Nat::from(u128::from(x))),
383 None => None,
384 };
385 let wasm_memory_limit = match self.wasm_memory_limit {
386 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
387 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
388 None => None,
389 };
390 let wasm_memory_threshold = match self.wasm_memory_threshold {
391 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
392 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
393 None => None,
394 };
395 let log_visibility = match self.log_visibility {
396 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
397 Some(Ok(x)) => Some(x),
398 None => None,
399 };
400 let environment_variables = match self.environment_variables {
401 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
402 Some(Ok(x)) => Some(x),
403 None => None,
404 };
405
406 #[derive(Deserialize, CandidType)]
407 struct Out {
408 canister_id: Principal,
409 }
410
411 let async_builder = if self.is_provisional_create {
412 #[derive(CandidType)]
413 struct In {
414 amount: Option<Nat>,
415 settings: CanisterSettings,
416 specified_id: Option<Principal>,
417 }
418 let in_arg = In {
419 amount: self.amount.map(Nat::from),
420 settings: CanisterSettings {
421 controllers,
422 compute_allocation,
423 memory_allocation,
424 freezing_threshold,
425 reserved_cycles_limit,
426 wasm_memory_limit,
427 wasm_memory_threshold,
428 log_visibility,
429 environment_variables,
430 },
431 specified_id: self.specified_id,
432 };
433 self.canister
434 .update(MgmtMethod::ProvisionalCreateCanisterWithCycles.as_ref())
435 .with_arg(in_arg)
436 .with_effective_canister_id(self.effective_canister_id)
437 } else {
438 self.canister
439 .update(MgmtMethod::CreateCanister.as_ref())
440 .with_arg(CanisterSettings {
441 controllers,
442 compute_allocation,
443 memory_allocation,
444 freezing_threshold,
445 reserved_cycles_limit,
446 wasm_memory_limit,
447 wasm_memory_threshold,
448 log_visibility,
449 environment_variables,
450 })
451 .with_effective_canister_id(self.effective_canister_id)
452 };
453
454 Ok(async_builder
455 .build()
456 .map(|result: (Out,)| (result.0.canister_id,)))
457 }
458
459 pub async fn call(self) -> Result<CallResponse<(Principal,)>, AgentError> {
461 self.build()?.call().await
462 }
463
464 pub async fn call_and_wait(self) -> Result<(Principal,), AgentError> {
466 self.build()?.call_and_wait().await
467 }
468}
469
470#[cfg_attr(target_family = "wasm", async_trait(?Send))]
471#[cfg_attr(not(target_family = "wasm"), async_trait)]
472impl<'agent, 'canister: 'agent> AsyncCall for CreateCanisterBuilder<'agent, 'canister> {
473 type Value = (Principal,);
474
475 async fn call(self) -> Result<CallResponse<(Principal,)>, AgentError> {
476 self.build()?.call().await
477 }
478
479 async fn call_and_wait(self) -> Result<(Principal,), AgentError> {
480 self.build()?.call_and_wait().await
481 }
482}
483
484impl<'agent, 'canister: 'agent> IntoFuture for CreateCanisterBuilder<'agent, 'canister> {
485 type IntoFuture = CallFuture<'agent, (Principal,)>;
486 type Output = Result<(Principal,), AgentError>;
487
488 fn into_future(self) -> Self::IntoFuture {
489 AsyncCall::call_and_wait(self)
490 }
491}
492
493#[doc(hidden)]
494#[deprecated(since = "0.42.0", note = "Please use UpgradeFlags instead")]
495pub type CanisterUpgradeOptions = UpgradeFlags;
496
497#[doc(hidden)]
498#[deprecated(since = "0.42.0", note = "Please use CanisterInstallMode instead")]
499pub type InstallMode = CanisterInstallMode;
500
501#[doc(hidden)]
502#[deprecated(since = "0.42.0", note = "Please use InstallCodeArgs instead")]
503pub type CanisterInstall = InstallCodeArgs;
504
505#[derive(Debug)]
507pub struct InstallCodeBuilder<'agent, 'canister: 'agent> {
508 canister: &'canister Canister<'agent>,
509 canister_id: Principal,
510 wasm: &'canister [u8],
511 arg: Argument,
512 mode: Option<CanisterInstallMode>,
513}
514
515impl<'agent, 'canister: 'agent> InstallCodeBuilder<'agent, 'canister> {
516 pub fn builder(
518 canister: &'canister Canister<'agent>,
519 canister_id: &Principal,
520 wasm: &'canister [u8],
521 ) -> Self {
522 Self {
523 canister,
524 canister_id: *canister_id,
525 wasm,
526 arg: Default::default(),
527 mode: None,
528 }
529 }
530
531 pub fn with_arg<Argument: CandidType>(
534 mut self,
535 arg: Argument,
536 ) -> InstallCodeBuilder<'agent, 'canister> {
537 self.arg.set_idl_arg(arg);
538 self
539 }
540 pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
543 assert!(self.arg.0.is_none(), "argument is being set more than once");
544 self.arg = Argument::from_candid(tuple);
545 self
546 }
547 pub fn with_raw_arg(mut self, arg: Vec<u8>) -> InstallCodeBuilder<'agent, 'canister> {
549 self.arg.set_raw_arg(arg);
550 self
551 }
552
553 pub fn with_mode(self, mode: CanisterInstallMode) -> Self {
555 Self {
556 mode: Some(mode),
557 ..self
558 }
559 }
560
561 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
564 Ok(self
565 .canister
566 .update(MgmtMethod::InstallCode.as_ref())
567 .with_arg(InstallCodeArgs {
568 mode: self.mode.unwrap_or(CanisterInstallMode::Install),
569 canister_id: self.canister_id,
570 wasm_module: self.wasm.to_owned(),
571 arg: self.arg.serialize()?,
572 sender_canister_version: None,
573 })
574 .with_effective_canister_id(self.canister_id)
575 .build())
576 }
577
578 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
580 self.build()?.call().await
581 }
582
583 pub async fn call_and_wait(self) -> Result<(), AgentError> {
585 self.build()?.call_and_wait().await
586 }
587}
588
589#[cfg_attr(target_family = "wasm", async_trait(?Send))]
590#[cfg_attr(not(target_family = "wasm"), async_trait)]
591impl<'agent, 'canister: 'agent> AsyncCall for InstallCodeBuilder<'agent, 'canister> {
592 type Value = ();
593
594 async fn call(self) -> Result<CallResponse<()>, AgentError> {
595 self.build()?.call().await
596 }
597
598 async fn call_and_wait(self) -> Result<(), AgentError> {
599 self.build()?.call_and_wait().await
600 }
601}
602
603impl<'agent, 'canister: 'agent> IntoFuture for InstallCodeBuilder<'agent, 'canister> {
604 type IntoFuture = CallFuture<'agent, ()>;
605 type Output = Result<(), AgentError>;
606
607 fn into_future(self) -> Self::IntoFuture {
608 AsyncCall::call_and_wait(self)
609 }
610}
611
612#[derive(Debug)]
614pub struct InstallChunkedCodeBuilder<'agent, 'canister> {
615 canister: &'canister Canister<'agent>,
616 target_canister: Principal,
617 store_canister: Option<Principal>,
618 chunk_hashes_list: Vec<ChunkHash>,
619 wasm_module_hash: Vec<u8>,
620 arg: Argument,
621 mode: CanisterInstallMode,
622}
623
624impl<'agent: 'canister, 'canister> InstallChunkedCodeBuilder<'agent, 'canister> {
625 pub fn builder(
627 canister: &'canister Canister<'agent>,
628 target_canister: Principal,
629 wasm_module_hash: &[u8],
630 ) -> Self {
631 Self {
632 canister,
633 target_canister,
634 wasm_module_hash: wasm_module_hash.to_vec(),
635 store_canister: None,
636 chunk_hashes_list: vec![],
637 arg: Argument::new(),
638 mode: CanisterInstallMode::Install,
639 }
640 }
641
642 pub fn with_chunk_hashes(mut self, chunk_hashes: Vec<ChunkHash>) -> Self {
644 self.chunk_hashes_list = chunk_hashes;
645 self
646 }
647
648 pub fn with_store_canister(mut self, store_canister: Principal) -> Self {
650 self.store_canister = Some(store_canister);
651 self
652 }
653
654 pub fn with_arg(mut self, argument: impl CandidType) -> Self {
657 self.arg.set_idl_arg(argument);
658 self
659 }
660
661 pub fn with_args(mut self, argument: impl ArgumentEncoder) -> Self {
664 assert!(self.arg.0.is_none(), "argument is being set more than once");
665 self.arg = Argument::from_candid(argument);
666 self
667 }
668
669 pub fn with_raw_arg(mut self, argument: Vec<u8>) -> Self {
671 self.arg.set_raw_arg(argument);
672 self
673 }
674
675 pub fn with_install_mode(mut self, mode: CanisterInstallMode) -> Self {
677 self.mode = mode;
678 self
679 }
680
681 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
683 #[derive(CandidType)]
684 struct In {
685 mode: CanisterInstallMode,
686 target_canister: Principal,
687 store_canister: Option<Principal>,
688 chunk_hashes_list: Vec<ChunkHash>,
689 wasm_module_hash: Vec<u8>,
690 arg: Vec<u8>,
691 sender_canister_version: Option<u64>,
692 }
693 let Self {
694 mode,
695 target_canister,
696 store_canister,
697 chunk_hashes_list,
698 wasm_module_hash,
699 arg,
700 ..
701 } = self;
702 Ok(self
703 .canister
704 .update(MgmtMethod::InstallChunkedCode.as_ref())
705 .with_arg(In {
706 mode,
707 target_canister,
708 store_canister,
709 chunk_hashes_list,
710 wasm_module_hash,
711 arg: arg.serialize()?,
712 sender_canister_version: None,
713 })
714 .with_effective_canister_id(target_canister)
715 .build())
716 }
717
718 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
720 self.build()?.call().await
721 }
722
723 pub async fn call_and_wait(self) -> Result<(), AgentError> {
725 self.build()?.call_and_wait().await
726 }
727}
728
729#[cfg_attr(target_family = "wasm", async_trait(?Send))]
730#[cfg_attr(not(target_family = "wasm"), async_trait)]
731impl<'agent, 'canister: 'agent> AsyncCall for InstallChunkedCodeBuilder<'agent, 'canister> {
732 type Value = ();
733
734 async fn call(self) -> Result<CallResponse<()>, AgentError> {
735 self.call().await
736 }
737
738 async fn call_and_wait(self) -> Result<(), AgentError> {
739 self.call_and_wait().await
740 }
741}
742
743impl<'agent, 'canister: 'agent> IntoFuture for InstallChunkedCodeBuilder<'agent, 'canister> {
744 type IntoFuture = CallFuture<'agent, ()>;
745 type Output = Result<(), AgentError>;
746
747 fn into_future(self) -> Self::IntoFuture {
748 AsyncCall::call_and_wait(self)
749 }
750}
751
752#[derive(Debug)]
758pub struct InstallBuilder<'agent, 'canister, 'builder> {
759 canister: &'canister ManagementCanister<'agent>,
760 canister_id: Principal,
761 wasm: &'builder [u8],
764 arg: Argument,
765 mode: CanisterInstallMode,
766}
767
768impl<'agent: 'canister, 'canister: 'builder, 'builder> InstallBuilder<'agent, 'canister, 'builder> {
769 const CHUNK_CUTOFF: usize = (1.85 * 1024. * 1024.) as usize;
772
773 pub fn builder(
775 canister: &'canister ManagementCanister<'agent>,
776 canister_id: &Principal,
777 wasm: &'builder [u8],
778 ) -> Self {
779 Self {
780 canister,
781 canister_id: *canister_id,
782 wasm,
783 arg: Default::default(),
784 mode: CanisterInstallMode::Install,
785 }
786 }
787
788 pub fn with_arg<Argument: CandidType>(mut self, arg: Argument) -> Self {
791 self.arg.set_idl_arg(arg);
792 self
793 }
794 pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
797 assert!(self.arg.0.is_none(), "argument is being set more than once");
798 self.arg = Argument::from_candid(tuple);
799 self
800 }
801 pub fn with_raw_arg(mut self, arg: Vec<u8>) -> Self {
803 self.arg.set_raw_arg(arg);
804 self
805 }
806
807 pub fn with_mode(self, mode: CanisterInstallMode) -> Self {
809 Self { mode, ..self }
810 }
811
812 pub async fn call_and_wait(self) -> Result<(), AgentError> {
815 self.call_and_wait_with_progress()
816 .await
817 .try_for_each(|_| ready(Ok(())))
818 .await
819 }
820
821 pub async fn call_and_wait_with_progress(
825 self,
826 ) -> impl Stream<Item = Result<(), AgentError>> + 'builder {
827 let stream_res = async move {
828 let arg = self.arg.serialize()?;
829 let stream: BoxStream<'_, _> =
830 if self.wasm.len() + arg.len() < Self::CHUNK_CUTOFF {
831 Box::pin(
832 async move {
833 self.canister
834 .install_code(&self.canister_id, self.wasm)
835 .with_raw_arg(arg)
836 .with_mode(self.mode)
837 .call_and_wait()
838 .await
839 }
840 .into_stream(),
841 )
842 } else {
843 let (existing_chunks,) = self.canister.stored_chunks(&self.canister_id).call_and_wait().await?;
844 let existing_chunks = existing_chunks.into_iter().map(|c| c.hash).collect::<BTreeSet<_>>();
845 let all_chunks = self.wasm.chunks(1024 * 1024).map(|x| (Sha256::digest(x).to_vec(), x)).collect::<Vec<_>>();
846 let mut to_upload_chunks = vec![];
847 for (hash, chunk) in &all_chunks {
848 if !existing_chunks.contains(hash) {
849 to_upload_chunks.push(*chunk);
850 }
851 }
852
853 let upload_chunks_stream = FuturesUnordered::new();
854 for chunk in to_upload_chunks {
855 upload_chunks_stream.push(async move {
856 let (_res,) = self
857 .canister
858 .upload_chunk(&self.canister_id, &ic_management_canister_types::UploadChunkArgs {
859 canister_id: self.canister_id,
860 chunk: chunk.to_vec(),
861 })
862 .call_and_wait()
863 .await?;
864 Ok(())
865 });
866 }
867 let install_chunked_code_stream = async move {
868 let results = all_chunks.iter().map(|(hash,_)| ChunkHash{ hash: hash.clone() }).collect();
869 self.canister
870 .install_chunked_code(
871 &self.canister_id,
872 &Sha256::digest(self.wasm),
873 )
874 .with_chunk_hashes(results)
875 .with_raw_arg(arg)
876 .with_install_mode(self.mode)
877 .call_and_wait()
878 .await
879 }
880 .into_stream();
881 let clear_store_stream = async move {
882 self.canister
883 .clear_chunk_store(&self.canister_id)
884 .call_and_wait()
885 .await
886 }
887 .into_stream();
888
889 Box::pin(
890 upload_chunks_stream
891 .chain(install_chunked_code_stream)
892 .chain(clear_store_stream ),
893 )
894 };
895 Ok(stream)
896 }.await;
897 match stream_res {
898 Ok(stream) => stream,
899 Err(err) => Box::pin(stream::once(async { Err(err) })),
900 }
901 }
902}
903
904impl<'agent: 'canister, 'canister: 'builder, 'builder> IntoFuture
905 for InstallBuilder<'agent, 'canister, 'builder>
906{
907 type IntoFuture = CallFuture<'builder, ()>;
908 type Output = Result<(), AgentError>;
909
910 fn into_future(self) -> Self::IntoFuture {
911 Box::pin(self.call_and_wait())
912 }
913}
914
915#[derive(Debug)]
917pub struct UpdateCanisterBuilder<'agent, 'canister: 'agent> {
918 canister: &'canister Canister<'agent>,
919 canister_id: Principal,
920 controllers: Option<Result<Vec<Principal>, AgentError>>,
921 compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
922 memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
923 freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
924 reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
925 wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
926 wasm_memory_threshold: Option<Result<WasmMemoryLimit, AgentError>>,
927 log_visibility: Option<Result<LogVisibility, AgentError>>,
928 environment_variables: Option<Result<Vec<EnvironmentVariable>, AgentError>>,
929}
930
931impl<'agent, 'canister: 'agent> UpdateCanisterBuilder<'agent, 'canister> {
932 pub fn builder(canister: &'canister Canister<'agent>, canister_id: &Principal) -> Self {
934 Self {
935 canister,
936 canister_id: *canister_id,
937 controllers: None,
938 compute_allocation: None,
939 memory_allocation: None,
940 freezing_threshold: None,
941 reserved_cycles_limit: None,
942 wasm_memory_limit: None,
943 wasm_memory_threshold: None,
944 log_visibility: None,
945 environment_variables: None,
946 }
947 }
948
949 pub fn with_optional_controller<C, E>(self, controller: Option<C>) -> Self
952 where
953 E: std::fmt::Display,
954 C: TryInto<Principal, Error = E>,
955 {
956 let controller_to_add: Option<Result<Principal, _>> = controller.map(|ca| {
957 ca.try_into()
958 .map_err(|e| AgentError::MessageError(format!("{e}")))
959 });
960 let controllers: Option<Result<Vec<Principal>, _>> =
961 match (controller_to_add, self.controllers) {
962 (_, Some(Err(sticky))) => Some(Err(sticky)),
963 (Some(Err(e)), _) => Some(Err(e)),
964 (None, _) => None,
965 (Some(Ok(controller)), Some(Ok(controllers))) => {
966 let mut controllers = controllers;
967 controllers.push(controller);
968 Some(Ok(controllers))
969 }
970 (Some(Ok(controller)), None) => Some(Ok(vec![controller])),
971 };
972
973 Self {
974 controllers,
975 ..self
976 }
977 }
978
979 pub fn with_controller<C, E>(self, controller: C) -> Self
981 where
982 E: std::fmt::Display,
983 C: TryInto<Principal, Error = E>,
984 {
985 self.with_optional_controller(Some(controller))
986 }
987
988 pub fn with_optional_compute_allocation<C, E>(self, compute_allocation: Option<C>) -> Self
991 where
992 E: std::fmt::Display,
993 C: TryInto<ComputeAllocation, Error = E>,
994 {
995 Self {
996 compute_allocation: compute_allocation.map(|ca| {
997 ca.try_into()
998 .map_err(|e| AgentError::MessageError(format!("{e}")))
999 }),
1000 ..self
1001 }
1002 }
1003
1004 pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
1006 where
1007 E: std::fmt::Display,
1008 C: TryInto<ComputeAllocation, Error = E>,
1009 {
1010 self.with_optional_compute_allocation(Some(compute_allocation))
1011 }
1012
1013 pub fn with_optional_memory_allocation<E, C>(self, memory_allocation: Option<C>) -> Self
1016 where
1017 E: std::fmt::Display,
1018 C: TryInto<MemoryAllocation, Error = E>,
1019 {
1020 Self {
1021 memory_allocation: memory_allocation.map(|ma| {
1022 ma.try_into()
1023 .map_err(|e| AgentError::MessageError(format!("{e}")))
1024 }),
1025 ..self
1026 }
1027 }
1028
1029 pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
1031 where
1032 E: std::fmt::Display,
1033 C: TryInto<MemoryAllocation, Error = E>,
1034 {
1035 self.with_optional_memory_allocation(Some(memory_allocation))
1036 }
1037
1038 pub fn with_optional_freezing_threshold<E, C>(self, freezing_threshold: Option<C>) -> Self
1041 where
1042 E: std::fmt::Display,
1043 C: TryInto<FreezingThreshold, Error = E>,
1044 {
1045 Self {
1046 freezing_threshold: freezing_threshold.map(|ma| {
1047 ma.try_into()
1048 .map_err(|e| AgentError::MessageError(format!("{e}")))
1049 }),
1050 ..self
1051 }
1052 }
1053
1054 pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
1056 where
1057 E: std::fmt::Display,
1058 C: TryInto<FreezingThreshold, Error = E>,
1059 {
1060 self.with_optional_freezing_threshold(Some(freezing_threshold))
1061 }
1062
1063 pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
1065 where
1066 E: std::fmt::Display,
1067 C: TryInto<ReservedCyclesLimit, Error = E>,
1068 {
1069 self.with_optional_reserved_cycles_limit(Some(limit))
1070 }
1071
1072 pub fn with_optional_reserved_cycles_limit<E, C>(self, limit: Option<C>) -> Self
1075 where
1076 E: std::fmt::Display,
1077 C: TryInto<ReservedCyclesLimit, Error = E>,
1078 {
1079 Self {
1080 reserved_cycles_limit: limit.map(|ma| {
1081 ma.try_into()
1082 .map_err(|e| AgentError::MessageError(format!("{e}")))
1083 }),
1084 ..self
1085 }
1086 }
1087
1088 pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
1090 where
1091 E: std::fmt::Display,
1092 C: TryInto<WasmMemoryLimit, Error = E>,
1093 {
1094 self.with_optional_wasm_memory_limit(Some(wasm_memory_limit))
1095 }
1096
1097 pub fn with_optional_wasm_memory_limit<E, C>(self, wasm_memory_limit: Option<C>) -> Self
1100 where
1101 E: std::fmt::Display,
1102 C: TryInto<WasmMemoryLimit, Error = E>,
1103 {
1104 Self {
1105 wasm_memory_limit: wasm_memory_limit.map(|limit| {
1106 limit
1107 .try_into()
1108 .map_err(|e| AgentError::MessageError(format!("{e}")))
1109 }),
1110 ..self
1111 }
1112 }
1113
1114 pub fn with_wasm_memory_threshold<C, E>(self, wasm_memory_threshold: C) -> Self
1116 where
1117 E: std::fmt::Display,
1118 C: TryInto<WasmMemoryLimit, Error = E>,
1119 {
1120 self.with_optional_wasm_memory_threshold(Some(wasm_memory_threshold))
1121 }
1122
1123 pub fn with_optional_wasm_memory_threshold<E, C>(self, wasm_memory_threshold: Option<C>) -> Self
1126 where
1127 E: std::fmt::Display,
1128 C: TryInto<WasmMemoryLimit, Error = E>,
1129 {
1130 Self {
1131 wasm_memory_threshold: wasm_memory_threshold.map(|limit| {
1132 limit
1133 .try_into()
1134 .map_err(|e| AgentError::MessageError(format!("{e}")))
1135 }),
1136 ..self
1137 }
1138 }
1139
1140 pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
1142 where
1143 E: std::fmt::Display,
1144 C: TryInto<LogVisibility, Error = E>,
1145 {
1146 self.with_optional_log_visibility(Some(log_visibility))
1147 }
1148
1149 pub fn with_optional_log_visibility<E, C>(self, log_visibility: Option<C>) -> Self
1152 where
1153 E: std::fmt::Display,
1154 C: TryInto<LogVisibility, Error = E>,
1155 {
1156 Self {
1157 log_visibility: log_visibility.map(|limit| {
1158 limit
1159 .try_into()
1160 .map_err(|e| AgentError::MessageError(format!("{e}")))
1161 }),
1162 ..self
1163 }
1164 }
1165
1166 pub fn with_environment_variables<C, E>(self, environment_variables: C) -> Self
1168 where
1169 E: std::fmt::Display,
1170 C: TryInto<Vec<EnvironmentVariable>, Error = E>,
1171 {
1172 self.with_optional_environment_variables(Some(environment_variables))
1173 }
1174
1175 pub fn with_optional_environment_variables<E, C>(self, environment_variables: Option<C>) -> Self
1178 where
1179 E: std::fmt::Display,
1180 C: TryInto<Vec<EnvironmentVariable>, Error = E>,
1181 {
1182 Self {
1183 environment_variables: environment_variables.map(|vars| {
1184 vars.try_into()
1185 .map_err(|e| AgentError::MessageError(format!("{e}")))
1186 }),
1187 ..self
1188 }
1189 }
1190
1191 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
1194 #[derive(CandidType)]
1195 struct In {
1196 canister_id: Principal,
1197 settings: CanisterSettings,
1198 }
1199
1200 let controllers = match self.controllers {
1201 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1202 Some(Ok(x)) => Some(x),
1203 None => None,
1204 };
1205 let compute_allocation = match self.compute_allocation {
1206 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1207 Some(Ok(x)) => Some(Nat::from(u8::from(x))),
1208 None => None,
1209 };
1210 let memory_allocation = match self.memory_allocation {
1211 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1212 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1213 None => None,
1214 };
1215 let freezing_threshold = match self.freezing_threshold {
1216 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1217 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1218 None => None,
1219 };
1220 let reserved_cycles_limit = match self.reserved_cycles_limit {
1221 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1222 Some(Ok(x)) => Some(Nat::from(u128::from(x))),
1223 None => None,
1224 };
1225 let wasm_memory_limit = match self.wasm_memory_limit {
1226 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1227 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1228 None => None,
1229 };
1230 let wasm_memory_threshold = match self.wasm_memory_threshold {
1231 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1232 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1233 None => None,
1234 };
1235 let log_visibility = match self.log_visibility {
1236 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1237 Some(Ok(x)) => Some(x),
1238 None => None,
1239 };
1240 let environment_variables = match self.environment_variables {
1241 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1242 Some(Ok(x)) => Some(x),
1243 None => None,
1244 };
1245
1246 Ok(self
1247 .canister
1248 .update(MgmtMethod::UpdateSettings.as_ref())
1249 .with_arg(In {
1250 canister_id: self.canister_id,
1251 settings: CanisterSettings {
1252 controllers,
1253 compute_allocation,
1254 memory_allocation,
1255 freezing_threshold,
1256 reserved_cycles_limit,
1257 wasm_memory_limit,
1258 wasm_memory_threshold,
1259 log_visibility,
1260 environment_variables,
1261 },
1262 })
1263 .with_effective_canister_id(self.canister_id)
1264 .build())
1265 }
1266
1267 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
1269 self.build()?.call().await
1270 }
1271
1272 pub async fn call_and_wait(self) -> Result<(), AgentError> {
1274 self.build()?.call_and_wait().await
1275 }
1276}
1277
1278#[cfg_attr(target_family = "wasm", async_trait(?Send))]
1279#[cfg_attr(not(target_family = "wasm"), async_trait)]
1280impl<'agent, 'canister: 'agent> AsyncCall for UpdateCanisterBuilder<'agent, 'canister> {
1281 type Value = ();
1282 async fn call(self) -> Result<CallResponse<()>, AgentError> {
1283 self.build()?.call().await
1284 }
1285
1286 async fn call_and_wait(self) -> Result<(), AgentError> {
1287 self.build()?.call_and_wait().await
1288 }
1289}
1290
1291impl<'agent, 'canister: 'agent> IntoFuture for UpdateCanisterBuilder<'agent, 'canister> {
1292 type IntoFuture = CallFuture<'agent, ()>;
1293 type Output = Result<(), AgentError>;
1294 fn into_future(self) -> Self::IntoFuture {
1295 AsyncCall::call_and_wait(self)
1296 }
1297}
1298
1299#[cfg(not(target_family = "wasm"))]
1300type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + Send + 'a>>;
1301#[cfg(target_family = "wasm")]
1302type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + 'a>>;