1use crate::runbook::embedded_runbook::ExecutableEmbeddedRunbookInstance;
2use crate::runbook::{
3 get_source_context_for_diagnostic, RunbookExecutionMode, RunbookWorkspaceContext,
4 RuntimeContext,
5};
6use crate::types::{ConstructType, RunbookExecutionContext, RunbookSources};
7use kit::constants::{RE_EXECUTE_COMMAND, THIRD_PARTY_SIGNATURE_STATUS};
8use kit::types::commands::{
9 ConstructInstance, PostConditionEvaluationResult, PreConditionEvaluationResult,
10};
11use kit::types::types::ObjectDefinition;
12use std::collections::{BTreeMap, HashMap, HashSet};
13use std::fmt::Display;
14use txtx_addon_kit::constants::{
15 SIGNATURE_APPROVED, SIGNATURE_SKIPPABLE, SIGNED_MESSAGE_BYTES, SIGNED_TRANSACTION_BYTES,
16 TX_HASH,
17};
18use txtx_addon_kit::hcl::structure::Block as HclBlock;
19use txtx_addon_kit::helpers::hcl::visit_optional_untyped_attribute;
20use txtx_addon_kit::indexmap::IndexMap;
21use txtx_addon_kit::types::commands::{
22 add_ctx_to_diag, add_ctx_to_embedded_runbook_diag, CommandExecutionFuture,
23 DependencyExecutionResultCache, UnevaluatedInputsMap,
24};
25use txtx_addon_kit::types::embedded_runbooks::EmbeddedRunbookStatefulExecutionContext;
26use txtx_addon_kit::types::frontend::{
27 ActionItemRequestUpdate, ActionItemResponse, ActionItemResponseType, Actions, Block,
28 BlockEvent, ErrorPanelData, Panel,
29};
30use txtx_addon_kit::types::signers::SignersState;
31use txtx_addon_kit::types::stores::AddonDefaults;
32use txtx_addon_kit::types::types::{ObjectProperty, RunbookSupervisionContext, Type};
33use txtx_addon_kit::types::{ConstructId, PackageId};
34use txtx_addon_kit::types::{EvaluatableInput, WithEvaluatableInputs};
35use txtx_addon_kit::{
36 hcl::{
37 expr::{BinaryOperator, Expression, UnaryOperator},
38 template::Element,
39 },
40 types::{
41 commands::{CommandExecutionResult, CommandInputsEvaluationResult},
42 diagnostics::Diagnostic,
43 frontend::{ActionItemRequest, ActionItemStatus},
44 signers::SignerInstance,
45 types::Value,
46 ConstructDid,
47 },
48 uuid::Uuid,
49};
50
51pub async fn run_signers_evaluation(
55 runbook_workspace_context: &RunbookWorkspaceContext,
56 runbook_execution_context: &mut RunbookExecutionContext,
57 runtime_context: &RuntimeContext,
58 supervision_context: &RunbookSupervisionContext,
59 action_item_requests: &mut BTreeMap<ConstructDid, Vec<&mut ActionItemRequest>>,
60 action_item_responses: &BTreeMap<ConstructDid, Vec<ActionItemResponse>>,
61 progress_tx: &txtx_addon_kit::channel::Sender<BlockEvent>,
62) -> EvaluationPassResult {
63 let mut pass_result = EvaluationPassResult::new(&Uuid::new_v4());
64
65 let signers_instances = &runbook_execution_context.signers_instances;
66 let instantiated_signers = runbook_execution_context.order_for_signers_initialization.clone();
67
68 for construct_did in instantiated_signers.into_iter() {
69 let Some(signer_instance) = runbook_execution_context.signers_instances.get(&construct_did)
70 else {
71 continue;
72 };
73 let package_id = signer_instance.package_id.clone();
74
75 let construct_id = &runbook_workspace_context.expect_construct_id(&construct_did);
76
77 let instantiated = runbook_execution_context.is_signer_instantiated(&construct_did);
78
79 let add_ctx_to_diag = add_ctx_to_diag(
80 ConstructType::Signer.to_string(),
81 signer_instance.specification.matcher.clone(),
82 signer_instance.name.clone(),
83 signer_instance.namespace.clone(),
84 );
85
86 let mut cached_dependency_execution_results = DependencyExecutionResultCache::new();
87
88 let references_expressions =
89 signer_instance.get_expressions_referencing_commands_from_inputs();
90
91 for (_input, expr) in references_expressions.into_iter() {
92 if let Some((dependency, _, _)) = runbook_workspace_context
93 .try_resolve_construct_reference_in_expression(&package_id, &expr)
94 .unwrap()
95 {
96 if let Some(evaluation_result) =
97 runbook_execution_context.commands_execution_results.get(&dependency)
98 {
99 match cached_dependency_execution_results.merge(&dependency, &evaluation_result)
100 {
101 Ok(_) => {}
102 Err(diag) => {
103 pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
104 continue;
105 }
106 }
107 }
108 }
109 }
110
111 let input_evaluation_results = runbook_execution_context
112 .commands_inputs_evaluation_results
113 .get(&construct_did.clone());
114
115 let addon_context_key = (package_id.did(), signer_instance.namespace.clone());
116 let addon_defaults = runbook_workspace_context.get_addon_defaults(&addon_context_key);
117
118 let evaluated_inputs_res = perform_signer_inputs_evaluation(
119 &signer_instance,
120 &cached_dependency_execution_results,
121 &input_evaluation_results,
122 addon_defaults,
123 &package_id,
124 &runbook_workspace_context,
125 &runbook_execution_context,
126 runtime_context,
127 );
128
129 let evaluated_inputs = match evaluated_inputs_res {
130 Ok(result) => match result {
131 CommandInputEvaluationStatus::Complete(result) => result,
132 CommandInputEvaluationStatus::NeedsUserInteraction(_) => {
133 continue;
134 }
135 CommandInputEvaluationStatus::Aborted(_, diags) => {
136 pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
137 continue;
138 }
139 },
140 Err(diags) => {
141 pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
142 return pass_result;
143 }
144 };
145
146 let signer = runbook_execution_context.signers_instances.get(&construct_did).unwrap();
147
148 let mut signers_state = runbook_execution_context.signers_state.take().unwrap();
149 signers_state.create_new_signer(&construct_did, &signer.name);
150
151 let res = signer
152 .check_activability(
153 &construct_did,
154 &evaluated_inputs,
155 signers_state,
156 signers_instances,
157 &action_item_requests.get(&construct_did),
158 &action_item_responses.get(&construct_did),
159 supervision_context,
160 &runtime_context.authorization_context,
161 instantiated,
162 instantiated,
163 )
164 .await;
165 let signers_state = match res {
166 Ok((signers_state, mut new_actions)) => {
167 if new_actions.has_pending_actions() {
168 runbook_execution_context.signers_state = Some(signers_state);
169 runbook_execution_context
174 .commands_execution_results
175 .insert(construct_did, CommandExecutionResult::new());
176 pass_result.actions.append(&mut new_actions);
177 continue;
178 }
179 pass_result.actions.append(&mut new_actions);
180 signers_state
181 }
182 Err((signers_state, diag)) => {
183 runbook_execution_context.signers_state = Some(signers_state);
184 if let Some(requests) = action_item_requests.get_mut(&construct_did) {
185 for item in requests.iter_mut() {
186 let update = ActionItemRequestUpdate::from_id(&item.id)
188 .set_status(ActionItemStatus::Error(diag.clone()));
189 pass_result.actions.push_action_item_update(update);
190 }
191 }
192
193 pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
194
195 return pass_result;
196 }
197 };
198
199 runbook_execution_context
200 .commands_inputs_evaluation_results
201 .insert(construct_did.clone(), evaluated_inputs.clone());
202
203 let res = signer
204 .perform_activation(
205 &construct_did,
206 &evaluated_inputs,
207 signers_state,
208 signers_instances,
209 progress_tx,
210 )
211 .await;
212
213 let (mut result, signers_state) = match res {
214 Ok((signers_state, result)) => (Some(result), Some(signers_state)),
215 Err((signers_state, diag)) => {
216 runbook_execution_context.signers_state = Some(signers_state);
217 pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
218 return pass_result;
219 }
220 };
221 runbook_execution_context.signers_state = signers_state;
222 let Some(result) = result.take() else {
223 continue;
224 };
225
226 runbook_execution_context.commands_execution_results.insert(construct_did.clone(), result);
227 }
228
229 pass_result
230}
231
232pub struct EvaluationPassResult {
233 pub actions: Actions,
234 diagnostics: Vec<Diagnostic>,
235 pub pending_background_tasks_futures: Vec<CommandExecutionFuture>,
236 pub pending_background_tasks_constructs_uuids: Vec<(ConstructDid, ConstructDid)>,
237 pub background_tasks_uuid: Uuid,
238 pub nodes_to_re_execute: Vec<ConstructDid>,
239}
240
241impl EvaluationPassResult {
242 pub fn new(background_tasks_uuid: &Uuid) -> Self {
243 Self {
244 actions: Actions::none(),
245 diagnostics: vec![],
246 pending_background_tasks_futures: vec![],
247 pending_background_tasks_constructs_uuids: vec![],
248 background_tasks_uuid: background_tasks_uuid.clone(),
249 nodes_to_re_execute: vec![],
250 }
251 }
252
253 pub fn merge(&mut self, mut other: EvaluationPassResult) {
254 self.actions.append(&mut other.actions);
255 self.diagnostics.append(&mut other.diagnostics);
256 self.pending_background_tasks_futures.append(&mut other.pending_background_tasks_futures);
257 self.pending_background_tasks_constructs_uuids
258 .append(&mut other.pending_background_tasks_constructs_uuids);
259 }
260
261 pub fn compile_diagnostics_to_block(&self) -> Option<Block> {
262 if self.diagnostics.is_empty() {
263 return None;
264 };
265 Some(Block {
266 uuid: Uuid::new_v4(),
267 visible: true,
268 panel: Panel::ErrorPanel(ErrorPanelData::from_diagnostics(&self.diagnostics)),
269 })
270 }
271
272 pub fn diagnostics(&self) -> Vec<Diagnostic> {
273 self.diagnostics.clone()
274 }
275 pub fn has_diagnostics(&self) -> bool {
276 !self.diagnostics.is_empty()
277 }
278
279 pub fn push_diagnostic(
280 &mut self,
281 diag: &Diagnostic,
282 construct_id: &ConstructId,
283 ctx_adder: &impl Fn(&Diagnostic) -> Diagnostic,
284 ) {
285 self.diagnostics.push(ctx_adder(diag).location(&construct_id.construct_location))
286 }
287
288 pub fn append_diagnostics(
289 &mut self,
290 diags: Vec<Diagnostic>,
291 construct_id: &ConstructId,
292 ctx_adder: &impl Fn(&Diagnostic) -> Diagnostic,
293 ) {
294 diags.iter().for_each(|diag| self.push_diagnostic(diag, construct_id, &ctx_adder))
295 }
296
297 pub fn fill_diagnostic_span(&mut self, runbook_sources: &RunbookSources) {
298 for diag in self.diagnostics.iter_mut() {
299 diag.span = get_source_context_for_diagnostic(diag, runbook_sources);
300 }
301 }
302
303 pub fn with_spans_filled(mut self, runbook_sources: &RunbookSources) -> Vec<Diagnostic> {
304 self.fill_diagnostic_span(runbook_sources);
305 self.diagnostics()
306 }
307}
308
309impl Display for EvaluationPassResult {
310 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311 writeln!(f, "EvaluationPassResult {} {{", self.background_tasks_uuid)?;
312 writeln!(f, " actions: {:?}", self.actions)?;
313 writeln!(f, " diagnostics: {:?}", self.diagnostics)?;
314 writeln!(
315 f,
316 " pending_background_tasks: {:?}",
317 self.pending_background_tasks_constructs_uuids
318 )?;
319 writeln!(f, "}}")
320 }
321}
322
323fn should_skip_construct_evaluation(execution_result: &CommandExecutionResult) -> bool {
324 let has_re_execute_command =
326 execution_result.outputs.get(RE_EXECUTE_COMMAND).and_then(|v| v.as_bool()).unwrap_or(false);
327
328 let is_third_party_signed_construct_not_yet_signed_by_third_party = {
329 let third_party_val = execution_result
330 .outputs
331 .get(THIRD_PARTY_SIGNATURE_STATUS)
332 .map(|v| v.expect_third_party_signature());
333 let is_action_signed_by_third_party = third_party_val.is_some();
334 let is_third_party_sign_complete =
335 third_party_val.map(|v| v.is_approved()).unwrap_or(false);
336
337 is_action_signed_by_third_party && !is_third_party_sign_complete
338 };
339
340 !has_re_execute_command && !is_third_party_signed_construct_not_yet_signed_by_third_party
344}
345
346fn should_retry_construct_evaluation(execution_result: &CommandExecutionResult) -> bool {
347 let is_third_party_signed_construct_not_yet_signed_by_third_party = {
348 let third_party_val = execution_result
349 .outputs
350 .get(THIRD_PARTY_SIGNATURE_STATUS)
351 .map(|v| v.expect_third_party_signature());
352 let is_action_signed_by_third_party = third_party_val.is_some();
353 let is_third_party_sign_check_requested =
354 third_party_val.as_ref().map(|v| v.is_check_requested()).unwrap_or(false);
355
356 is_action_signed_by_third_party && is_third_party_sign_check_requested
357 };
358
359 is_third_party_signed_construct_not_yet_signed_by_third_party
360}
361
362pub async fn run_constructs_evaluation(
367 background_tasks_uuid: &Uuid,
368 runbook_workspace_context: &RunbookWorkspaceContext,
369 runbook_execution_context: &mut RunbookExecutionContext,
370 runtime_context: &RuntimeContext,
371 supervision_context: &RunbookSupervisionContext,
372 action_item_requests: &mut BTreeMap<ConstructDid, Vec<&mut ActionItemRequest>>,
373 action_item_responses: &BTreeMap<ConstructDid, Vec<ActionItemResponse>>,
374 progress_tx: &txtx_addon_kit::channel::Sender<BlockEvent>,
375) -> EvaluationPassResult {
376 let mut pass_result = EvaluationPassResult::new(background_tasks_uuid);
377
378 let mut unexecutable_nodes: HashSet<ConstructDid> = HashSet::new();
379
380 let top_level_inputs = runbook_workspace_context.top_level_inputs_values.clone();
381 for (input_uuid, value) in top_level_inputs.into_iter() {
382 let mut res = CommandExecutionResult::new();
383 res.outputs.insert("value".into(), value);
384 runbook_execution_context.commands_execution_results.insert(input_uuid, res);
385 }
386
387 for signer_states in runbook_execution_context.signers_state.iter_mut() {
388 for (_, signer) in signer_states.store.iter_mut() {
389 signer.clear_autoincrementable_nonce();
390 }
391 }
392
393 let mut genesis_dependency_execution_results = DependencyExecutionResultCache::new();
394
395 let mut signers_results = HashMap::new();
396 for (signer_construct_did, _) in runbook_execution_context.signers_instances.iter() {
397 let mut result = CommandExecutionResult::new();
398 result
399 .outputs
400 .insert("value".into(), Value::string(signer_construct_did.value().to_string()));
401 signers_results.insert(signer_construct_did.clone(), result);
402 }
403
404 for (signer_construct_did, _) in runbook_execution_context.signers_instances.iter() {
405 let results = signers_results.get(signer_construct_did).unwrap();
406 genesis_dependency_execution_results
407 .insert(signer_construct_did.clone(), Ok(results.clone()));
408 }
409
410 let ordered_constructs = runbook_execution_context.order_for_commands_execution.clone();
411
412 for construct_did in ordered_constructs.into_iter() {
413 if let Some(execution_results) =
414 runbook_execution_context.commands_execution_results.get(&construct_did)
415 {
416 if should_skip_construct_evaluation(execution_results) {
417 continue;
418 }
419 }
420
421 if let Some(_) = unexecutable_nodes.get(&construct_did) {
422 if let Some(deps) = runbook_execution_context.commands_dependencies.get(&construct_did)
423 {
424 for dep in deps.iter() {
425 unexecutable_nodes.insert(dep.clone());
426 }
427 }
428 continue;
429 }
430
431 if let Some(_) = runbook_execution_context.commands_instances.get(&construct_did) {
432 match evaluate_command_instance(
433 &construct_did,
434 &mut pass_result,
435 &mut unexecutable_nodes,
436 &mut genesis_dependency_execution_results,
437 runbook_workspace_context,
438 runbook_execution_context,
439 runtime_context,
440 supervision_context,
441 action_item_requests,
442 action_item_responses,
443 progress_tx,
444 )
445 .await
446 {
447 LoopEvaluationResult::Continue => continue,
448 LoopEvaluationResult::Bail => {
449 return pass_result;
450 }
451 }
452 }
453
454 if let Some(_) = runbook_execution_context.embedded_runbooks.get(&construct_did) {
455 match evaluate_embedded_runbook_instance(
456 background_tasks_uuid,
457 &construct_did,
458 &mut pass_result,
459 &mut unexecutable_nodes,
460 &mut genesis_dependency_execution_results,
461 runbook_workspace_context,
462 runbook_execution_context,
463 runtime_context,
464 supervision_context,
465 action_item_requests,
466 action_item_responses,
467 progress_tx,
468 )
469 .await
470 {
471 LoopEvaluationResult::Continue => continue,
472 LoopEvaluationResult::Bail => {
473 return pass_result;
474 }
475 }
476 }
477 }
478 pass_result
479}
480
481pub enum LoopEvaluationResult {
482 Continue,
483 Bail,
484}
485
486pub async fn evaluate_command_instance(
487 construct_did: &ConstructDid,
488 pass_result: &mut EvaluationPassResult,
489 unexecutable_nodes: &mut HashSet<ConstructDid>,
490 genesis_dependency_execution_results: &mut DependencyExecutionResultCache,
491 runbook_workspace_context: &RunbookWorkspaceContext,
492 runbook_execution_context: &mut RunbookExecutionContext,
493 runtime_context: &RuntimeContext,
494 supervision_context: &RunbookSupervisionContext,
495 action_item_requests: &mut BTreeMap<ConstructDid, Vec<&mut ActionItemRequest>>,
496 action_item_responses: &BTreeMap<ConstructDid, Vec<ActionItemResponse>>,
497 progress_tx: &txtx_addon_kit::channel::Sender<BlockEvent>,
498) -> LoopEvaluationResult {
499 let Some(command_instance) = runbook_execution_context.commands_instances.get(&construct_did)
500 else {
501 return LoopEvaluationResult::Continue;
503 };
504
505 let add_ctx_to_diag = add_ctx_to_diag(
506 "command".to_string(),
507 command_instance.specification.matcher.clone(),
508 command_instance.name.clone(),
509 command_instance.namespace.clone(),
510 );
511
512 let package_id = command_instance.package_id.clone();
513 let construct_id = &runbook_workspace_context.expect_construct_id(&construct_did);
514
515 let addon_context_key = (package_id.did(), command_instance.namespace.clone());
516 let addon_defaults = runbook_workspace_context.get_addon_defaults(&addon_context_key);
517
518 let input_evaluation_results = runbook_execution_context
519 .commands_inputs_evaluation_results
520 .get(&construct_did.clone())
521 .cloned();
522
523 let mut cached_dependency_execution_results = genesis_dependency_execution_results.clone();
524
525 let references_expressions =
528 command_instance.get_expressions_referencing_commands_from_inputs();
529
530 for (_input, expr) in references_expressions.into_iter() {
531 if let Some((dependency, _, _)) = runbook_workspace_context
532 .try_resolve_construct_reference_in_expression(&package_id, &expr)
533 .unwrap()
534 {
535 if let Some(evaluation_result) =
536 runbook_execution_context.commands_execution_results.get(&dependency)
537 {
538 match cached_dependency_execution_results.merge(&dependency, evaluation_result) {
539 Ok(_) => {}
540 Err(_) => continue,
541 }
542 }
543 }
544 }
545
546 let evaluated_inputs_res = perform_inputs_evaluation(
547 command_instance,
548 &cached_dependency_execution_results,
549 &input_evaluation_results.as_ref(),
550 addon_defaults,
551 &action_item_responses.get(&construct_did),
552 &package_id,
553 runbook_workspace_context,
554 runbook_execution_context,
555 runtime_context,
556 false,
557 false,
558 );
559
560 let mut evaluated_inputs = match evaluated_inputs_res {
561 Ok(result) => match result {
562 CommandInputEvaluationStatus::Complete(result) => result,
563 CommandInputEvaluationStatus::NeedsUserInteraction(_) => {
564 return LoopEvaluationResult::Continue;
565 }
566 CommandInputEvaluationStatus::Aborted(_, diags) => {
567 pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
568 return LoopEvaluationResult::Bail;
569 }
570 },
571 Err(diags) => {
572 pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
573 return LoopEvaluationResult::Bail;
574 }
575 };
576
577 let command_execution_result = {
578 let Some(command_instance) =
579 runbook_execution_context.commands_instances.get_mut(&construct_did)
580 else {
581 return LoopEvaluationResult::Continue;
583 };
584
585 match command_instance.evaluate_pre_conditions(
586 construct_did,
587 &evaluated_inputs,
588 progress_tx,
589 &pass_result.background_tasks_uuid,
590 ) {
591 Ok(result) => match result {
592 PreConditionEvaluationResult::Noop => {}
593 PreConditionEvaluationResult::Halt(diags) => {
594 pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
595 return LoopEvaluationResult::Bail;
596 }
597 PreConditionEvaluationResult::SkipDownstream => {
598 if let Some(deps) =
599 runbook_execution_context.commands_dependencies.get(&construct_did)
600 {
601 for dep in deps.iter() {
602 unexecutable_nodes.insert(dep.clone());
603 }
604 }
605 return LoopEvaluationResult::Continue;
606 }
607 },
608 Err(diag) => {
609 pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
610 return LoopEvaluationResult::Bail;
611 }
612 };
613
614 let executions_for_action = if command_instance.specification.implements_signing_capability
615 {
616 let signers = runbook_execution_context.signers_state.take().unwrap();
617 let signers = update_signer_instances_from_action_response(
618 signers,
619 &construct_did,
620 &action_item_responses.get(&construct_did),
621 );
622 match command_instance
623 .prepare_signed_nested_execution(
624 &construct_did,
625 &evaluated_inputs,
626 signers,
627 &runbook_execution_context.signers_instances,
628 )
629 .await
630 {
631 Ok((updated_signers, executions)) => {
632 runbook_execution_context.signers_state = Some(updated_signers);
633 executions
634 }
635 Err((updated_signers, diag)) => {
636 pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
637 runbook_execution_context.signers_state = Some(updated_signers);
638 return LoopEvaluationResult::Bail;
639 }
640 }
641 } else {
642 match command_instance.prepare_nested_execution(&construct_did, &evaluated_inputs) {
643 Ok(executions) => executions,
644 Err(diag) => {
645 pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
646 return LoopEvaluationResult::Bail;
647 }
648 }
649 };
650 let signed_by = runbook_execution_context
651 .signers_downstream_dependencies
652 .iter()
653 .filter_map(
654 |(signer, deps)| if deps.contains(construct_did) { Some(signer) } else { None },
655 )
656 .filter_map(|signer_did| {
657 runbook_execution_context
658 .signers_instances
659 .get(signer_did)
660 .map(|si| (signer_did.clone(), si.clone()))
661 })
662 .collect::<Vec<_>>();
663
664 let force_sequential_signing = signed_by
665 .iter()
666 .any(|(_, signer_instance)| signer_instance.specification.force_sequential_signing);
667
668 for (nested_construct_did, nested_evaluation_values) in executions_for_action.iter() {
669 if let Some(execution_results) =
670 runbook_execution_context.commands_execution_results.get(&nested_construct_did)
671 {
672 if should_skip_construct_evaluation(execution_results) {
673 continue;
674 }
675 };
676
677 let execution_result = if command_instance.specification.implements_signing_capability {
678 let signers = runbook_execution_context.signers_state.take().unwrap();
679 let signers = update_signer_instances_from_action_response(
680 signers,
681 &nested_construct_did,
682 &action_item_responses.get(&nested_construct_did),
683 );
684
685 let res = command_instance
686 .check_signed_executability(
687 &construct_did,
688 nested_evaluation_values,
689 &evaluated_inputs,
690 signers,
691 &mut runbook_execution_context.signers_instances,
692 &action_item_responses.get(&nested_construct_did),
693 &action_item_requests.get(&construct_did),
694 supervision_context,
695 &runtime_context.authorization_context,
696 )
697 .await;
698
699 let signers = match res {
700 Ok((updated_signers, mut new_actions)) => {
701 if new_actions.has_pending_actions() {
702 pass_result.actions.append(&mut new_actions);
703 runbook_execution_context.signers_state = Some(updated_signers);
704 if let Some(deps) =
705 runbook_execution_context.commands_dependencies.get(&construct_did)
706 {
707 for dep in deps.iter() {
708 unexecutable_nodes.insert(dep.clone());
709 }
710 }
711 if force_sequential_signing {
712 return LoopEvaluationResult::Bail;
713 } else {
714 return LoopEvaluationResult::Continue;
715 }
716 }
717 pass_result.actions.append(&mut new_actions);
718 updated_signers
719 }
720 Err((updated_signers, diag)) => {
721 pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
722 runbook_execution_context.signers_state = Some(updated_signers);
723 return LoopEvaluationResult::Bail;
724 }
725 };
726
727 runbook_execution_context
728 .commands_inputs_evaluation_results
729 .insert(construct_did.clone(), evaluated_inputs.clone());
730
731 let mut empty_vec = vec![];
732 let action_items_requests =
733 action_item_requests.get_mut(&construct_did).unwrap_or(&mut empty_vec);
734 let action_items_response = action_item_responses.get(&nested_construct_did);
735
736 let execution_result = command_instance
737 .perform_signed_execution(
738 &construct_did,
739 nested_evaluation_values,
740 &evaluated_inputs,
741 signers,
742 &runbook_execution_context.signers_instances,
743 action_items_requests,
744 &action_items_response,
745 progress_tx,
746 &runtime_context.authorization_context,
747 )
748 .await;
749 let execution_result = match execution_result {
750 Ok((updated_signers, result)) => {
751 runbook_execution_context.signers_state = Some(updated_signers);
752 if should_retry_construct_evaluation(&result) {
753 return LoopEvaluationResult::Continue;
754 }
755 Ok(result)
756 }
757 Err((updated_signers, diag)) => {
758 runbook_execution_context.signers_state = Some(updated_signers);
759 if let Some(deps) =
760 runbook_execution_context.commands_dependencies.get(&construct_did)
761 {
762 for dep in deps.iter() {
763 unexecutable_nodes.insert(dep.clone());
764 }
765 }
766 Err(diag)
767 }
768 };
769
770 execution_result
771 } else {
772 match command_instance.check_executability(
773 &construct_did,
774 nested_evaluation_values,
775 &mut evaluated_inputs,
776 &mut runbook_execution_context.signers_instances,
777 &action_item_responses.get(&nested_construct_did),
778 supervision_context,
779 &runtime_context.authorization_context,
780 ) {
781 Ok(mut new_actions) => {
782 if new_actions.has_pending_actions() {
783 pass_result.actions.append(&mut new_actions);
784 if let Some(deps) =
785 runbook_execution_context.commands_dependencies.get(&construct_did)
786 {
787 for dep in deps.iter() {
788 unexecutable_nodes.insert(dep.clone());
789 }
790 }
791 return LoopEvaluationResult::Continue;
792 }
793 pass_result.actions.append(&mut new_actions);
794 }
795 Err(diag) => {
796 pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
797 return LoopEvaluationResult::Bail;
798 }
799 }
800
801 runbook_execution_context
802 .commands_inputs_evaluation_results
803 .insert(construct_did.clone(), evaluated_inputs.clone());
804
805 let mut empty_vec = vec![];
806 let action_items_requests =
807 action_item_requests.get_mut(&construct_did).unwrap_or(&mut empty_vec);
808 let action_items_response = action_item_responses.get(&nested_construct_did);
809
810 let execution_result = {
811 command_instance
812 .perform_execution(
813 &construct_did,
814 nested_evaluation_values,
815 &evaluated_inputs,
816 action_items_requests,
817 &action_items_response,
818 progress_tx,
819 &runtime_context.authorization_context,
820 )
821 .await
822 };
823
824 let execution_result = match execution_result {
825 Ok(result) => Ok(result),
826 Err(e) => {
827 if let Some(deps) =
828 runbook_execution_context.commands_dependencies.get(&construct_did)
829 {
830 for dep in deps.iter() {
831 unexecutable_nodes.insert(dep.clone());
832 }
833 }
834 Err(e)
835 }
836 };
837 execution_result
838 };
839
840 let mut execution_result = match execution_result {
841 Ok(res) => res,
842 Err(diag) => {
843 pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
844 return LoopEvaluationResult::Continue;
845 }
846 };
847
848 if let RunbookExecutionMode::Partial(ref mut executed_constructs) =
849 runbook_execution_context.execution_mode
850 {
851 executed_constructs.push(nested_construct_did.clone());
852 }
853
854 if command_instance.specification.implements_background_task_capability {
855 let future_res = command_instance.build_background_task(
856 &construct_did,
857 nested_evaluation_values,
858 &evaluated_inputs,
859 &execution_result,
860 progress_tx,
861 &pass_result.background_tasks_uuid,
862 supervision_context,
863 &runtime_context.cloud_service_context,
864 );
865 let future = match future_res {
866 Ok(future) => future,
867 Err(diag) => {
868 pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
869 return LoopEvaluationResult::Bail;
870 }
871 };
872 if let Some(deps) =
873 runbook_execution_context.commands_dependencies.get(&construct_did)
874 {
875 for dep in deps.iter() {
876 unexecutable_nodes.insert(dep.clone());
877 }
878 }
879 runbook_execution_context.append_commands_execution_result(
884 &nested_construct_did,
885 &CommandExecutionResult::from([(
886 "background_task_uuid",
887 Value::string(pass_result.background_tasks_uuid.to_string()),
888 )]),
889 );
890 pass_result.pending_background_tasks_futures.push(future);
891 pass_result
892 .pending_background_tasks_constructs_uuids
893 .push((nested_construct_did.clone(), construct_did.clone()));
894
895 if force_sequential_signing {
899 return LoopEvaluationResult::Bail;
900 } else {
901 return LoopEvaluationResult::Continue;
902 }
903 } else {
904 runbook_execution_context
905 .commands_execution_results
906 .entry(nested_construct_did.clone())
907 .or_insert_with(CommandExecutionResult::new)
908 .append(&mut execution_result);
909 }
910 }
911
912 let res = command_instance.aggregate_nested_execution_results(
913 &construct_did,
914 &executions_for_action,
915 &runbook_execution_context.commands_execution_results,
916 );
917 match res {
918 Ok(result) => result,
919 Err(diag) => {
920 pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
921 return LoopEvaluationResult::Continue;
922 }
923 }
924 };
925
926 let command_instance =
927 runbook_execution_context.commands_instances.get(&construct_did).unwrap();
928
929 cached_dependency_execution_results.merge(construct_did, &command_execution_result).unwrap();
930
931 let self_referencing_inputs = perform_inputs_evaluation(
932 command_instance,
933 &cached_dependency_execution_results,
934 &input_evaluation_results.as_ref(),
935 addon_defaults,
936 &action_item_responses.get(&construct_did),
937 &package_id,
938 runbook_workspace_context,
939 runbook_execution_context,
940 runtime_context,
941 false,
942 true,
943 );
944
945 let mut self_referencing_inputs = match self_referencing_inputs {
946 Ok(result) => match result {
947 CommandInputEvaluationStatus::Complete(result) => result,
948 CommandInputEvaluationStatus::NeedsUserInteraction(_) => {
949 return LoopEvaluationResult::Continue;
950 }
951 CommandInputEvaluationStatus::Aborted(_, diags) => {
952 pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
953 return LoopEvaluationResult::Bail;
954 }
955 },
956 Err(diags) => {
957 pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
958 return LoopEvaluationResult::Bail;
959 }
960 };
961
962 self_referencing_inputs.inputs =
963 self_referencing_inputs.inputs.append_inputs(&evaluated_inputs.inputs.inputs);
964
965 match command_instance.evaluate_post_conditions(
966 construct_did,
967 &self_referencing_inputs,
968 runbook_execution_context
969 .commands_execution_results
970 .entry(construct_did.clone())
971 .or_insert(CommandExecutionResult::new()),
972 progress_tx,
973 &pass_result.background_tasks_uuid,
974 ) {
975 Ok(result) => {
976 match result {
977 PostConditionEvaluationResult::Noop => {}
978 PostConditionEvaluationResult::Halt(diags) => {
979 pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
980 return LoopEvaluationResult::Bail;
981 }
982 PostConditionEvaluationResult::SkipDownstream => {
983 if let Some(deps) =
984 runbook_execution_context.commands_dependencies.get(&construct_did)
985 {
986 for dep in deps.iter() {
987 unexecutable_nodes.insert(dep.clone());
988 }
989 }
990 return LoopEvaluationResult::Continue;
991 }
992 PostConditionEvaluationResult::Retry(_) => {
993 if let Some(deps) =
997 runbook_execution_context.commands_dependencies.get(&construct_did)
998 {
999 for dep in deps.iter() {
1000 unexecutable_nodes.insert(dep.clone());
1001 }
1002 }
1003 pass_result.nodes_to_re_execute.push(construct_did.clone());
1004 return LoopEvaluationResult::Continue;
1005 }
1006 }
1007 }
1008 Err(diag) => {
1009 pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
1010 return LoopEvaluationResult::Bail;
1011 }
1012 };
1013
1014 runbook_execution_context
1015 .commands_execution_results
1016 .insert(construct_did.clone(), command_execution_result);
1017
1018 if let RunbookExecutionMode::Partial(ref mut executed_constructs) =
1019 runbook_execution_context.execution_mode
1020 {
1021 executed_constructs.push(construct_did.clone());
1022 }
1023
1024 LoopEvaluationResult::Continue
1025}
1026
1027pub async fn evaluate_embedded_runbook_instance(
1028 background_tasks_uuid: &Uuid,
1029 construct_did: &ConstructDid,
1030 pass_result: &mut EvaluationPassResult,
1031 unexecutable_nodes: &mut HashSet<ConstructDid>,
1032 genesis_dependency_execution_results: &mut DependencyExecutionResultCache,
1033 runbook_workspace_context: &RunbookWorkspaceContext,
1034 runbook_execution_context: &mut RunbookExecutionContext,
1035 runtime_context: &RuntimeContext,
1036 supervision_context: &RunbookSupervisionContext,
1037 action_item_requests: &mut BTreeMap<ConstructDid, Vec<&mut ActionItemRequest>>,
1038 action_item_responses: &BTreeMap<ConstructDid, Vec<ActionItemResponse>>,
1039 progress_tx: &txtx_addon_kit::channel::Sender<BlockEvent>,
1040) -> LoopEvaluationResult {
1041 let Some(embedded_runbook) = runbook_execution_context.embedded_runbooks.get(&construct_did)
1042 else {
1043 return LoopEvaluationResult::Continue;
1044 };
1045
1046 let add_ctx_to_diag = add_ctx_to_embedded_runbook_diag(embedded_runbook.name.clone());
1047
1048 let package_id = embedded_runbook.package_id.clone();
1049 let construct_id = &runbook_workspace_context.expect_construct_id(&construct_did);
1050
1051 let input_evaluation_results =
1052 runbook_execution_context.commands_inputs_evaluation_results.get(&construct_did.clone());
1053
1054 let mut cached_dependency_execution_results = genesis_dependency_execution_results.clone();
1055
1056 let references_expressions =
1059 embedded_runbook.get_expressions_referencing_commands_from_inputs();
1060
1061 for (_input, expr) in references_expressions.into_iter() {
1062 if let Some((dependency, _, _)) = runbook_workspace_context
1063 .try_resolve_construct_reference_in_expression(&package_id, &expr)
1064 .unwrap()
1065 {
1066 if let Some(evaluation_result) =
1067 runbook_execution_context.commands_execution_results.get(&dependency)
1068 {
1069 match cached_dependency_execution_results.merge(&dependency, evaluation_result) {
1070 Ok(_) => {}
1071 Err(_) => continue,
1072 }
1073 }
1074 }
1075 }
1076
1077 let evaluated_inputs_res = perform_inputs_evaluation(
1078 embedded_runbook,
1079 &cached_dependency_execution_results,
1080 &input_evaluation_results,
1081 &AddonDefaults::new("empty"),
1082 &action_item_responses.get(&construct_did),
1083 &package_id,
1084 runbook_workspace_context,
1085 runbook_execution_context,
1086 runtime_context,
1087 false,
1088 false,
1089 );
1090 let evaluated_inputs = match evaluated_inputs_res {
1091 Ok(result) => match result {
1092 CommandInputEvaluationStatus::Complete(result) => result,
1093 CommandInputEvaluationStatus::NeedsUserInteraction(_) => {
1094 return LoopEvaluationResult::Continue
1095 }
1096 CommandInputEvaluationStatus::Aborted(_, diags) => {
1097 pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
1098 return LoopEvaluationResult::Bail;
1099 }
1100 },
1101 Err(diags) => {
1102 pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
1103 return LoopEvaluationResult::Bail;
1104 }
1105 };
1106
1107 let mut executable_embedded_runbook = match ExecutableEmbeddedRunbookInstance::new(
1110 embedded_runbook.clone(),
1111 EmbeddedRunbookStatefulExecutionContext::new(
1112 &runbook_execution_context.signers_instances,
1113 &runbook_execution_context.signers_state,
1114 &runbook_workspace_context
1115 .constructs
1116 .iter()
1117 .filter_map(|(did, id)| {
1118 if runbook_execution_context.signers_instances.contains_key(did) {
1119 Some((did.clone(), id.clone()))
1120 } else {
1121 None
1122 }
1123 })
1124 .collect(),
1125 ),
1126 &evaluated_inputs.inputs,
1127 runtime_context,
1128 ) {
1129 Ok(res) => res,
1130 Err(diag) => {
1131 pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
1132 return LoopEvaluationResult::Bail;
1133 }
1134 };
1135
1136 let result = Box::pin(run_constructs_evaluation(
1137 background_tasks_uuid,
1138 &executable_embedded_runbook.context.workspace_context,
1139 &mut executable_embedded_runbook.context.execution_context,
1140 runtime_context,
1141 supervision_context,
1142 action_item_requests,
1143 action_item_responses,
1144 progress_tx,
1145 ))
1146 .await;
1147
1148 runbook_execution_context
1149 .commands_inputs_evaluation_results
1150 .insert(construct_did.clone(), evaluated_inputs.clone());
1151
1152 runbook_execution_context.append_command_inputs_evaluation_results_no_override(
1154 &executable_embedded_runbook.context.execution_context.commands_inputs_evaluation_results,
1155 );
1156
1157 runbook_execution_context.signers_state =
1158 executable_embedded_runbook.context.execution_context.signers_state;
1159
1160 pass_result.merge(result);
1161
1162 let has_diags = !pass_result.diagnostics.is_empty();
1163 let has_pending_actions = pass_result.actions.has_pending_actions();
1164 let has_pending_background_tasks = !pass_result.pending_background_tasks_futures.is_empty();
1165
1166 if has_diags || has_pending_actions || has_pending_background_tasks {
1167 if let Some(deps) = runbook_execution_context.commands_dependencies.get(&construct_did) {
1168 for dep in deps.iter() {
1169 unexecutable_nodes.insert(dep.clone());
1170 }
1171 }
1172 return LoopEvaluationResult::Continue;
1173 } else {
1174 runbook_execution_context.append_commands_execution_results(
1176 &executable_embedded_runbook.context.execution_context.commands_execution_results,
1177 );
1178 }
1179
1180 LoopEvaluationResult::Continue
1181}
1182
1183#[derive(Debug)]
1189pub enum ExpressionEvaluationStatus {
1190 CompleteOk(Value),
1191 CompleteErr(Diagnostic),
1192 DependencyNotComputed,
1193}
1194
1195pub fn eval_expression(
1196 expr: &Expression,
1197 dependencies_execution_results: &DependencyExecutionResultCache,
1198 package_id: &PackageId,
1199 runbook_workspace_context: &RunbookWorkspaceContext,
1200 runbook_execution_context: &RunbookExecutionContext,
1201 runtime_context: &RuntimeContext,
1202) -> Result<ExpressionEvaluationStatus, Diagnostic> {
1203 let value = match expr {
1204 Expression::Null(_decorated_null) => Value::null(),
1206 Expression::Bool(decorated_bool) => Value::bool(*decorated_bool.value()),
1208 Expression::Number(formatted_number) => {
1210 match (
1211 formatted_number.value().as_u64(),
1212 formatted_number.value().as_i64(),
1213 formatted_number.value().as_f64(),
1214 ) {
1215 (Some(value), _, _) => Value::integer(value.into()),
1216 (_, Some(value), _) => Value::integer(value.into()),
1217 (_, _, Some(value)) => Value::float(value),
1218 (None, None, None) => unreachable!(), }
1220 }
1221 Expression::String(decorated_string) => Value::string(decorated_string.to_string()),
1223 Expression::Array(entries) => {
1225 let mut res = vec![];
1226 for entry_expr in entries {
1227 let value = match eval_expression(
1228 entry_expr,
1229 dependencies_execution_results,
1230 package_id,
1231 runbook_workspace_context,
1232 runbook_execution_context,
1233 runtime_context,
1234 )? {
1235 ExpressionEvaluationStatus::CompleteOk(result) => result,
1236 ExpressionEvaluationStatus::CompleteErr(e) => {
1237 return Ok(ExpressionEvaluationStatus::CompleteErr(e))
1238 }
1239 ExpressionEvaluationStatus::DependencyNotComputed => {
1240 return Ok(ExpressionEvaluationStatus::DependencyNotComputed)
1241 }
1242 };
1243 res.push(value);
1244 }
1245 Value::array(res)
1246 }
1247 Expression::Object(object) => {
1249 let mut map = IndexMap::new();
1250 for (k, v) in object.into_iter() {
1251 let key = match k {
1252 txtx_addon_kit::hcl::expr::ObjectKey::Expression(k_expr) => {
1253 match eval_expression(
1254 k_expr,
1255 dependencies_execution_results,
1256 package_id,
1257 runbook_workspace_context,
1258 runbook_execution_context,
1259 runtime_context,
1260 )? {
1261 ExpressionEvaluationStatus::CompleteOk(result) => match result {
1262 Value::String(result) => result,
1263 _ => {
1264 return Ok(ExpressionEvaluationStatus::CompleteErr(
1265 Diagnostic::error_from_string(
1266 "object key must evaluate to a string".to_string(),
1267 ),
1268 ))
1269 }
1270 },
1271 ExpressionEvaluationStatus::CompleteErr(e) => {
1272 return Ok(ExpressionEvaluationStatus::CompleteErr(e))
1273 }
1274 ExpressionEvaluationStatus::DependencyNotComputed => {
1275 return Ok(ExpressionEvaluationStatus::DependencyNotComputed)
1276 }
1277 }
1278 }
1279 txtx_addon_kit::hcl::expr::ObjectKey::Ident(k_ident) => k_ident.to_string(),
1280 };
1281 let value = match eval_expression(
1282 v.expr(),
1283 dependencies_execution_results,
1284 package_id,
1285 runbook_workspace_context,
1286 runbook_execution_context,
1287 runtime_context,
1288 )? {
1289 ExpressionEvaluationStatus::CompleteOk(result) => result,
1290 ExpressionEvaluationStatus::CompleteErr(e) => {
1291 return Ok(ExpressionEvaluationStatus::CompleteErr(e))
1292 }
1293 ExpressionEvaluationStatus::DependencyNotComputed => {
1294 return Ok(ExpressionEvaluationStatus::DependencyNotComputed)
1295 }
1296 };
1297 map.insert(key, value);
1298 }
1299 Value::Object(map)
1300 }
1301 Expression::StringTemplate(string_template) => {
1303 let mut res = String::new();
1304 for element in string_template.into_iter() {
1305 match element {
1306 Element::Literal(literal) => {
1307 res.push_str(literal.value());
1308 }
1309 Element::Interpolation(interpolation) => {
1310 let value = match eval_expression(
1311 &interpolation.expr,
1312 dependencies_execution_results,
1313 package_id,
1314 runbook_workspace_context,
1315 runbook_execution_context,
1316 runtime_context,
1317 )? {
1318 ExpressionEvaluationStatus::CompleteOk(result) => result.to_string(),
1319 ExpressionEvaluationStatus::CompleteErr(e) => {
1320 return Ok(ExpressionEvaluationStatus::CompleteErr(e))
1321 }
1322 ExpressionEvaluationStatus::DependencyNotComputed => {
1323 return Ok(ExpressionEvaluationStatus::DependencyNotComputed)
1324 }
1325 };
1326 res.push_str(&value);
1327 }
1328 Element::Directive(_) => {
1329 unimplemented!("string templates with directives not yet supported")
1330 }
1331 };
1332 }
1333 Value::string(res)
1334 }
1335 Expression::HeredocTemplate(_heredoc_template) => {
1337 unimplemented!()
1338 }
1339 Expression::Parenthesis(_sub_expr) => {
1341 unimplemented!()
1342 }
1343 Expression::Variable(_decorated_var) => {
1345 return Err(diagnosed_error!(
1346 "Directly referencing a variable is not supported. Did you mean `variable.{}`?",
1347 _decorated_var.as_str()
1348 )
1349 .into());
1350 }
1351 Expression::Conditional(_conditional) => {
1353 unimplemented!()
1354 }
1355 Expression::FuncCall(function_call) => {
1357 let func_namespace = function_call.name.namespace.first().map(|n| n.to_string());
1358 let func_name = function_call.name.name.to_string();
1359 let mut args = vec![];
1360 for expr in function_call.args.iter() {
1361 let value = match eval_expression(
1362 expr,
1363 dependencies_execution_results,
1364 package_id,
1365 runbook_workspace_context,
1366 runbook_execution_context,
1367 runtime_context,
1368 )? {
1369 ExpressionEvaluationStatus::CompleteOk(result) => result,
1370 ExpressionEvaluationStatus::CompleteErr(e) => {
1371 return Ok(ExpressionEvaluationStatus::CompleteErr(e))
1372 }
1373 ExpressionEvaluationStatus::DependencyNotComputed => {
1374 return Ok(ExpressionEvaluationStatus::DependencyNotComputed)
1375 }
1376 };
1377 args.push(value);
1378 }
1379 runtime_context
1380 .execute_function(
1381 package_id.did(),
1382 func_namespace,
1383 &func_name,
1384 &args,
1385 &runtime_context.authorization_context,
1386 )
1387 .map_err(|e| e)?
1388 }
1389 Expression::Traversal(_) => {
1391 let (dependency, mut components, _subpath) = match runbook_workspace_context
1392 .try_resolve_construct_reference_in_expression(package_id, expr)
1393 {
1394 Ok(Some(res)) => res,
1395 Ok(None) => {
1396 return Err(diagnosed_error!(
1397 "unable to resolve expression '{}'",
1398 expr.to_string().trim()
1399 ));
1400 }
1401 Err(e) => {
1402 return Err(diagnosed_error!(
1403 "unable to resolve expression '{}': {}",
1404 expr.to_string().trim(),
1405 e
1406 ))
1407 }
1408 };
1409
1410 let res = match dependencies_execution_results.get(&dependency) {
1411 Some(res) => match res.clone() {
1412 Ok(res) => res,
1413 Err(e) => return Ok(ExpressionEvaluationStatus::CompleteErr(e.clone())),
1414 },
1415 None => match runbook_execution_context.commands_execution_results.get(&dependency)
1416 {
1417 Some(res) => res.clone(),
1418 None => return Ok(ExpressionEvaluationStatus::DependencyNotComputed),
1419 },
1420 };
1421
1422 let some_attribute = components.pop_front();
1423 let no_attribute = some_attribute.is_none();
1424 let attribute = some_attribute.unwrap_or("value".into());
1425
1426 match res.outputs.get(&attribute) {
1427 Some(output) => {
1428 if let Some(_) = output.as_object() {
1429 output.get_keys_from_object(components)?
1430 } else {
1431 output.clone()
1432 }
1433 }
1434 None => match res.outputs.get("value") {
1439 Some(output) => {
1440 if let Some(_) = output.as_object() {
1441 components.push_front(attribute);
1442 output.get_keys_from_object(components)?
1443 } else {
1444 output.clone()
1445 }
1446 }
1447 None => {
1448 if no_attribute {
1451 return Ok(ExpressionEvaluationStatus::CompleteOk(Value::string(
1452 dependency.to_string(),
1453 )));
1454 } else {
1455 return Ok(ExpressionEvaluationStatus::DependencyNotComputed);
1456 }
1457 }
1458 },
1459 }
1460 }
1461 Expression::UnaryOp(unary_op) => {
1463 let _expr = eval_expression(
1464 &unary_op.expr,
1465 dependencies_execution_results,
1466 package_id,
1467 runbook_workspace_context,
1468 runbook_execution_context,
1469 runtime_context,
1470 )?;
1471 match &unary_op.operator.value() {
1472 UnaryOperator::Neg => {}
1473 UnaryOperator::Not => {}
1474 }
1475 unimplemented!()
1476 }
1477 Expression::BinaryOp(binary_op) => {
1479 let lhs = match eval_expression(
1480 &binary_op.lhs_expr,
1481 dependencies_execution_results,
1482 package_id,
1483 runbook_workspace_context,
1484 runbook_execution_context,
1485 runtime_context,
1486 )? {
1487 ExpressionEvaluationStatus::CompleteOk(result) => result,
1488 ExpressionEvaluationStatus::CompleteErr(e) => {
1489 return Ok(ExpressionEvaluationStatus::CompleteErr(e))
1490 }
1491 ExpressionEvaluationStatus::DependencyNotComputed => {
1492 return Ok(ExpressionEvaluationStatus::DependencyNotComputed)
1493 }
1494 };
1495 let rhs = match eval_expression(
1496 &binary_op.rhs_expr,
1497 dependencies_execution_results,
1498 package_id,
1499 runbook_workspace_context,
1500 runbook_execution_context,
1501 runtime_context,
1502 )? {
1503 ExpressionEvaluationStatus::CompleteOk(result) => result,
1504 ExpressionEvaluationStatus::CompleteErr(e) => {
1505 return Ok(ExpressionEvaluationStatus::CompleteErr(e))
1506 }
1507 ExpressionEvaluationStatus::DependencyNotComputed => {
1508 return Ok(ExpressionEvaluationStatus::DependencyNotComputed)
1509 }
1510 };
1511
1512 let func = match &binary_op.operator.value() {
1513 BinaryOperator::And => "and_bool",
1514 BinaryOperator::Div => "div",
1515 BinaryOperator::Eq => "eq",
1516 BinaryOperator::Greater => "gt",
1517 BinaryOperator::GreaterEq => "gte",
1518 BinaryOperator::Less => "lt",
1519 BinaryOperator::LessEq => "lte",
1520 BinaryOperator::Minus => "minus",
1521 BinaryOperator::Mod => "modulo",
1522 BinaryOperator::Mul => "multiply",
1523 BinaryOperator::Plus => "add",
1524 BinaryOperator::NotEq => "neq",
1525 BinaryOperator::Or => "or_bool",
1526 };
1527 runtime_context.execute_function(
1528 package_id.did(),
1529 None,
1530 func,
1531 &vec![lhs, rhs],
1532 &runtime_context.authorization_context,
1533 )?
1534 }
1535 Expression::ForExpr(_for_expr) => {
1537 unimplemented!()
1538 }
1539 };
1540
1541 Ok(ExpressionEvaluationStatus::CompleteOk(value))
1542}
1543
1544pub fn update_signer_instances_from_action_response(
1549 mut signers: SignersState,
1550 construct_did: &ConstructDid,
1551 action_item_response: &Option<&Vec<ActionItemResponse>>,
1552) -> SignersState {
1553 match action_item_response {
1554 Some(responses) => {
1555 responses.into_iter().for_each(|ActionItemResponse { action_item_id: _, payload }| {
1556 match payload {
1557 ActionItemResponseType::ProvideSignedTransaction(response) => {
1558 if let Some(mut signer_state) =
1559 signers.pop_signer_state(&response.signer_uuid)
1560 {
1561 let did = &construct_did.to_string();
1562 match &response.signed_transaction_bytes {
1563 Some(bytes) => {
1564 signer_state.insert_scoped_value(
1565 &did,
1566 SIGNED_TRANSACTION_BYTES,
1567 Value::string(bytes.clone()),
1568 );
1569 }
1570 None => match response.signature_approved {
1571 Some(true) => {
1572 signer_state.insert_scoped_value(
1573 &did,
1574 SIGNATURE_APPROVED,
1575 Value::bool(true),
1576 );
1577 }
1578 Some(false) => {}
1579 None => {
1580 let skippable = signer_state
1581 .get_scoped_value(&did, SIGNATURE_SKIPPABLE)
1582 .and_then(|v| v.as_bool())
1583 .unwrap_or(false);
1584 if skippable {
1585 signer_state.insert_scoped_value(
1586 &did,
1587 SIGNED_TRANSACTION_BYTES,
1588 Value::null(),
1589 );
1590 }
1591 }
1592 },
1593 }
1594 signers.push_signer_state(signer_state.clone());
1595 }
1596 }
1597 ActionItemResponseType::SendTransaction(response) => {
1598 if let Some(mut signer_state) =
1599 signers.pop_signer_state(&response.signer_uuid)
1600 {
1601 let did = &construct_did.to_string();
1602 signer_state.insert_scoped_value(
1603 &did,
1604 TX_HASH,
1605 Value::string(response.transaction_hash.clone()),
1606 );
1607
1608 signers.push_signer_state(signer_state.clone());
1609 }
1610 }
1611 ActionItemResponseType::ProvideSignedMessage(response) => {
1612 if let Some(mut signer_state) =
1613 signers.pop_signer_state(&response.signer_uuid)
1614 {
1615 signer_state.insert_scoped_value(
1616 &construct_did.value().to_string(),
1617 SIGNED_MESSAGE_BYTES,
1618 Value::string(response.signed_message_bytes.clone()),
1619 );
1620 signers.push_signer_state(signer_state.clone());
1621 }
1622 }
1623 ActionItemResponseType::VerifyThirdPartySignature(response) => {
1624 if let Some(mut signer_state) =
1625 signers.pop_signer_state(&response.signer_uuid)
1626 {
1627 signer_state.insert_scoped_value(
1631 &construct_did.value().to_string(),
1632 THIRD_PARTY_SIGNATURE_STATUS,
1633 Value::third_party_signature_check_requested(),
1634 );
1635 signers.push_signer_state(signer_state.clone());
1636 }
1637 }
1638 _ => {}
1639 }
1640 });
1641 }
1642 None => {}
1643 }
1644
1645 signers
1646}
1647
1648#[derive(Debug)]
1649pub enum CommandInputEvaluationStatus {
1650 Complete(CommandInputsEvaluationResult),
1651 NeedsUserInteraction(CommandInputsEvaluationResult),
1652 Aborted(CommandInputsEvaluationResult, Vec<Diagnostic>),
1653}
1654
1655pub fn perform_inputs_evaluation(
1656 with_evaluatable_inputs: &impl WithEvaluatableInputs,
1657 dependencies_execution_results: &DependencyExecutionResultCache,
1658 input_evaluation_results: &Option<&CommandInputsEvaluationResult>,
1659 addon_defaults: &AddonDefaults,
1660 action_item_response: &Option<&Vec<ActionItemResponse>>,
1661 package_id: &PackageId,
1662 runbook_workspace_context: &RunbookWorkspaceContext,
1663 runbook_execution_context: &RunbookExecutionContext,
1664 runtime_context: &RuntimeContext,
1665 simulation: bool,
1666 self_referencing_inputs: bool,
1667) -> Result<CommandInputEvaluationStatus, Vec<Diagnostic>> {
1668 let mut has_existing_evaluation_results = true;
1669 let mut results = match *input_evaluation_results {
1670 Some(evaluated_inputs) => {
1671 let mut inputs = evaluated_inputs.clone();
1672 inputs.inputs = inputs.inputs.with_defaults(&addon_defaults.store);
1673 inputs
1674 }
1675 None => {
1676 has_existing_evaluation_results = false;
1677 CommandInputsEvaluationResult::new(
1678 &with_evaluatable_inputs.name(),
1679 &addon_defaults.store,
1680 )
1681 }
1682 };
1683 let mut require_user_interaction = false;
1684 let mut diags = vec![];
1685 let inputs = if self_referencing_inputs {
1686 has_existing_evaluation_results = false;
1687 with_evaluatable_inputs.self_referencing_inputs()
1688 } else {
1689 with_evaluatable_inputs.spec_inputs()
1690 };
1691
1692 let mut fatal_error = false;
1693
1694 match action_item_response {
1695 Some(responses) => {
1696 responses.into_iter().for_each(|ActionItemResponse { action_item_id: _, payload }| {
1697 match payload {
1698 ActionItemResponseType::ReviewInput(_update) => {}
1699 ActionItemResponseType::ProvideInput(update) => {
1700 results.inputs.insert(&update.input_name, update.updated_value.clone());
1701 }
1702 _ => {}
1703 }
1704 })
1705 }
1706 None => {}
1707 }
1708
1709 for input in inputs.into_iter() {
1710 let input_name = input.name();
1711 let input_typing = input.typing();
1712
1713 if simulation {
1714 if input_name.eq("signer") {
1716 results.unevaluated_inputs.insert("signer".into(), None);
1717 continue;
1718 }
1719 if input_name.eq("signers") {
1720 results.unevaluated_inputs.insert("signers".into(), None);
1721 continue;
1722 }
1723 } else if has_existing_evaluation_results {
1724 if !results.unevaluated_inputs.contains_key(&input_name) {
1725 continue;
1726 }
1727 }
1728 if let Some(object_def) = input.as_object() {
1729 let Some(expr) =
1733 with_evaluatable_inputs.get_expression_from_object(&input_name, &input_typing)?
1734 else {
1735 continue;
1736 };
1737 if let Expression::Traversal(traversal) = &expr {
1738 let value = match eval_expression(
1739 &Expression::Traversal(traversal.clone()),
1740 dependencies_execution_results,
1741 package_id,
1742 runbook_workspace_context,
1743 runbook_execution_context,
1744 runtime_context,
1745 ) {
1746 Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
1747 Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
1748 if e.is_error() {
1749 fatal_error = true;
1750 }
1751 results.unevaluated_inputs.insert(input_name.clone(), Some(e.clone()));
1752 diags.push(e);
1753 continue;
1754 }
1755 Err(e) => {
1756 if e.is_error() {
1757 fatal_error = true;
1758 }
1759 results.unevaluated_inputs.insert(input_name.clone(), Some(e.clone()));
1760 diags.push(e);
1761 continue;
1762 }
1763 Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
1764 require_user_interaction = true;
1765 results.unevaluated_inputs.insert(input_name.clone(), None);
1766 continue;
1767 }
1768 };
1769 results.insert(&input_name, value);
1770 continue;
1771 }
1772
1773 if let Expression::FuncCall(ref function_call) = expr {
1774 let value = match eval_expression(
1775 &Expression::FuncCall(function_call.clone()),
1776 dependencies_execution_results,
1777 package_id,
1778 runbook_workspace_context,
1779 runbook_execution_context,
1780 runtime_context,
1781 ) {
1782 Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
1783 Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
1784 if e.is_error() {
1785 fatal_error = true;
1786 }
1787 results.unevaluated_inputs.insert(input_name.to_string(), Some(e.clone()));
1788 diags.push(e);
1789 continue;
1790 }
1791 Err(e) => {
1792 if e.is_error() {
1793 fatal_error = true;
1794 }
1795 results.unevaluated_inputs.insert(input_name.to_string(), Some(e.clone()));
1796 diags.push(e);
1797 continue;
1798 }
1799 Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
1800 require_user_interaction = true;
1801 results.unevaluated_inputs.insert(input_name.to_string(), None);
1802 continue;
1803 }
1804 };
1805 results.insert(&input_name, value);
1806 continue;
1807 }
1808 let mut object_values = IndexMap::new();
1809 match object_def {
1810 ObjectDefinition::Strict(props) => {
1811 for prop in props.iter() {
1812 let Some(expr) = with_evaluatable_inputs
1813 .get_expression_from_object_property(&input_name, &prop)
1814 else {
1815 continue;
1816 };
1817
1818 let value = match eval_expression(
1819 &expr,
1820 dependencies_execution_results,
1821 package_id,
1822 runbook_workspace_context,
1823 runbook_execution_context,
1824 runtime_context,
1825 ) {
1826 Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
1827 Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
1828 if e.is_error() {
1829 fatal_error = true;
1830 }
1831 results
1832 .unevaluated_inputs
1833 .insert(input_name.clone(), Some(e.clone()));
1834 diags.push(e);
1835 continue;
1836 }
1837 Err(e) => {
1838 if e.is_error() {
1839 fatal_error = true;
1840 }
1841 results
1842 .unevaluated_inputs
1843 .insert(input_name.clone(), Some(e.clone()));
1844 diags.push(e);
1845 continue;
1846 }
1847 Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
1848 require_user_interaction = true;
1849 results.unevaluated_inputs.insert(input_name.clone(), None);
1850 continue;
1851 }
1852 };
1853
1854 match value.clone() {
1857 Value::Object(obj) => {
1858 for (k, v) in obj.into_iter() {
1859 object_values.insert(k, v);
1860 }
1861 }
1862 v => {
1863 object_values.insert(prop.name.to_string(), v);
1864 }
1865 };
1866 }
1867 }
1868 ObjectDefinition::Arbitrary(_) => {
1869 let Some(expr) = with_evaluatable_inputs.get_expression_from_input(&input_name)
1870 else {
1871 continue;
1872 };
1873 let Some(object_block) = expr.as_object() else {
1874 continue;
1875 };
1876 for (object_key, object_value) in object_block.iter() {
1877 let object_key = match object_key {
1878 kit::hcl::expr::ObjectKey::Ident(ident) => ident.to_string(),
1879 kit::hcl::expr::ObjectKey::Expression(expr) => match expr.as_str() {
1880 Some(s) => s.to_string(),
1881 None => {
1882 let diag = diagnosed_error!("object key must be a string");
1883 results
1884 .unevaluated_inputs
1885 .insert(input_name.clone(), Some(diag.clone()));
1886 diags.push(diag);
1887 fatal_error = true;
1888 continue;
1889 }
1890 },
1891 };
1892 let expr = object_value.expr();
1893
1894 let value = match eval_expression(
1895 &expr,
1896 dependencies_execution_results,
1897 package_id,
1898 runbook_workspace_context,
1899 runbook_execution_context,
1900 runtime_context,
1901 ) {
1902 Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
1903 Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
1904 if e.is_error() {
1905 fatal_error = true;
1906 }
1907 results
1908 .unevaluated_inputs
1909 .insert(input_name.clone(), Some(e.clone()));
1910 diags.push(e);
1911 continue;
1912 }
1913 Err(e) => {
1914 if e.is_error() {
1915 fatal_error = true;
1916 }
1917 results
1918 .unevaluated_inputs
1919 .insert(input_name.clone(), Some(e.clone()));
1920 diags.push(e);
1921 continue;
1922 }
1923 Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
1924 require_user_interaction = true;
1925 results.unevaluated_inputs.insert(input_name.clone(), None);
1926 continue;
1927 }
1928 };
1929 object_values.insert(object_key, value);
1930 }
1931 }
1932 ObjectDefinition::Tuple(_) | ObjectDefinition::Enum(_) => {
1933 unimplemented!("ObjectDefinition::Tuple and ObjectDefinition::Enum are not supported for runbook types");
1934 }
1935 }
1936
1937 if !object_values.is_empty() {
1938 results.insert(&input_name, Value::object(object_values));
1939 }
1940 } else if let Some(_) = input.as_array() {
1941 let mut array_values = vec![];
1942 let Some(expr) = with_evaluatable_inputs.get_expression_from_input(&input_name) else {
1943 continue;
1944 };
1945 let value = match eval_expression(
1946 &expr,
1947 dependencies_execution_results,
1948 package_id,
1949 runbook_workspace_context,
1950 runbook_execution_context,
1951 runtime_context,
1952 ) {
1953 Ok(ExpressionEvaluationStatus::CompleteOk(result)) => match result {
1954 Value::Addon(_) => unreachable!(),
1955 Value::Object(_) => unreachable!(),
1956 Value::Array(entries) => {
1957 for (i, entry) in entries.into_iter().enumerate() {
1958 array_values.insert(i, entry); }
1960 Value::array(array_values)
1961 }
1962 _ => result,
1963 },
1964 Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
1965 if e.is_error() {
1966 fatal_error = true;
1967 }
1968 results.unevaluated_inputs.insert(input_name.clone(), Some(e.clone()));
1969 diags.push(e);
1970 continue;
1971 }
1972 Err(e) => {
1973 if e.is_error() {
1974 fatal_error = true;
1975 }
1976 results.unevaluated_inputs.insert(input_name.clone(), Some(e.clone()));
1977 diags.push(e);
1978 continue;
1979 }
1980 Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
1981 require_user_interaction = true;
1982 results.unevaluated_inputs.insert(input_name.clone(), None);
1983 continue;
1984 }
1985 };
1986
1987 results.insert(&input_name, value);
1988 } else if let Some(_) = input.as_map() {
1989 match evaluate_map_input(
1990 results.clone(),
1991 &input,
1992 with_evaluatable_inputs,
1993 dependencies_execution_results,
1994 package_id,
1995 runbook_workspace_context,
1996 runbook_execution_context,
1997 runtime_context,
1998 ) {
1999 Ok(Some(res)) => {
2000 if res.fatal_error {
2001 fatal_error = true;
2002 }
2003 if res.require_user_interaction {
2004 require_user_interaction = true;
2005 }
2006 results = res.result;
2007 diags.extend(res.diags);
2008 }
2009 Ok(None) => continue,
2010 Err(e) => return Err(e),
2011 };
2012 } else {
2013 let Some(expr) = with_evaluatable_inputs.get_expression_from_input(&input_name) else {
2014 continue;
2015 };
2016
2017 let value = match eval_expression(
2018 &expr,
2019 dependencies_execution_results,
2020 package_id,
2021 runbook_workspace_context,
2022 runbook_execution_context,
2023 runtime_context,
2024 ) {
2025 Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
2026 Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
2027 if e.is_error() {
2028 fatal_error = true;
2029 }
2030 results.unevaluated_inputs.insert(input_name.clone(), Some(e.clone()));
2031 diags.push(e);
2032 continue;
2033 }
2034 Err(e) => {
2035 if e.is_error() {
2036 fatal_error = true;
2037 }
2038 results.unevaluated_inputs.insert(input_name.clone(), Some(e.clone()));
2039 diags.push(e);
2040 continue;
2041 }
2042 Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
2043 require_user_interaction = true;
2044 results.unevaluated_inputs.insert(input_name.clone(), None);
2045 continue;
2046 }
2047 };
2048 results.insert(&input_name, value);
2049 }
2050 }
2051
2052 if fatal_error {
2053 return Ok(CommandInputEvaluationStatus::Aborted(results, diags));
2054 }
2055
2056 let status = match (fatal_error, require_user_interaction) {
2057 (false, false) => CommandInputEvaluationStatus::Complete(results),
2058 (_, _) => CommandInputEvaluationStatus::NeedsUserInteraction(results),
2059 };
2060 Ok(status)
2061}
2062
2063pub fn perform_signer_inputs_evaluation(
2064 signer_instance: &SignerInstance,
2065 dependencies_execution_results: &DependencyExecutionResultCache,
2066 input_evaluation_results: &Option<&CommandInputsEvaluationResult>,
2067 addon_defaults: &AddonDefaults,
2068 package_id: &PackageId,
2069 runbook_workspace_context: &RunbookWorkspaceContext,
2070 runbook_execution_context: &RunbookExecutionContext,
2071 runtime_context: &RuntimeContext,
2072) -> Result<CommandInputEvaluationStatus, Vec<Diagnostic>> {
2073 let mut results =
2074 CommandInputsEvaluationResult::new(&signer_instance.name, &addon_defaults.store);
2075 let mut require_user_interaction = false;
2076 let mut diags = vec![];
2077 let inputs = signer_instance.inputs();
2078 let mut fatal_error = false;
2079
2080 for input in inputs.into_iter() {
2081 let input_name = input.name();
2082
2083 let previously_evaluated_input = match input_evaluation_results {
2085 Some(input_evaluation_results) => {
2086 input_evaluation_results.inputs.get_value(&input_name)
2087 }
2088 None => None,
2089 };
2090 if let Some(object_def) = input.as_object() {
2091 let mut object_values = IndexMap::new();
2093 match object_def {
2094 ObjectDefinition::Strict(props) => {
2095 for prop in props.iter() {
2096 if let Some(value) = previously_evaluated_input {
2097 match value.clone() {
2098 Value::Object(obj) => {
2099 for (k, v) in obj.into_iter() {
2100 object_values.insert(k, v);
2101 }
2102 }
2103 v => {
2104 object_values.insert(prop.name.to_string(), v);
2105 }
2106 };
2107 }
2108
2109 let Some(expr) =
2110 signer_instance.get_expression_from_object_property(&input_name, &prop)
2111 else {
2112 continue;
2113 };
2114 let value = match eval_expression(
2115 &expr,
2116 dependencies_execution_results,
2117 package_id,
2118 runbook_workspace_context,
2119 runbook_execution_context,
2120 runtime_context,
2121 ) {
2122 Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
2123 Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
2124 if e.is_error() {
2125 fatal_error = true;
2126 }
2127 diags.push(e);
2128 continue;
2129 }
2130 Err(e) => {
2131 if e.is_error() {
2132 fatal_error = true;
2133 }
2134 diags.push(e);
2135 continue;
2136 }
2137 Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
2138 require_user_interaction = true;
2139 continue;
2140 }
2141 };
2142
2143 match value.clone() {
2144 Value::Object(obj) => {
2145 for (k, v) in obj.into_iter() {
2146 object_values.insert(k, v);
2147 }
2148 }
2149 v => {
2150 object_values.insert(prop.name.to_string(), v);
2151 }
2152 };
2153 }
2154 }
2155 ObjectDefinition::Arbitrary(_) => {
2156 println!(
2157 "Warning: arbitrary object definition is not supported for signer inputs"
2158 );
2159 }
2160 ObjectDefinition::Tuple(_) | ObjectDefinition::Enum(_) => {
2161 unimplemented!("ObjectDefinition::Tuple and ObjectDefinition::Enum are not supported for runbook types");
2162 }
2163 }
2164
2165 if !object_values.is_empty() {
2166 results.insert(&input_name, Value::Object(object_values));
2167 }
2168 } else if let Some(_) = input.as_array() {
2169 let mut array_values = vec![];
2170 if let Some(value) = previously_evaluated_input {
2171 match value.clone() {
2172 Value::Array(entries) => {
2173 array_values.extend::<Vec<Value>>(entries.into_iter().collect());
2174 }
2175 _ => {
2176 unreachable!()
2177 }
2178 }
2179 }
2180
2181 let Some(expr) = signer_instance.get_expression_from_input(&input_name) else {
2182 continue;
2183 };
2184 let value = match eval_expression(
2185 &expr,
2186 dependencies_execution_results,
2187 package_id,
2188 runbook_workspace_context,
2189 runbook_execution_context,
2190 runtime_context,
2191 ) {
2192 Ok(ExpressionEvaluationStatus::CompleteOk(result)) => match result {
2193 Value::Array(entries) => {
2194 for (i, entry) in entries.into_iter().enumerate() {
2195 array_values.insert(i, entry); }
2197 Value::array(array_values)
2198 }
2199 _ => unreachable!(),
2200 },
2201 Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
2202 if e.is_error() {
2203 fatal_error = true;
2204 }
2205 diags.push(e);
2206 continue;
2207 }
2208 Err(e) => {
2209 if e.is_error() {
2210 fatal_error = true;
2211 }
2212 diags.push(e);
2213 continue;
2214 }
2215 Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
2216 let Expression::Array(exprs) = expr else { panic!() };
2218 let mut references = vec![];
2219 for expr in exprs.iter() {
2220 let result = runbook_workspace_context
2221 .try_resolve_construct_reference_in_expression(package_id, &expr);
2222 if let Ok(Some((construct_did, _, _))) = result {
2223 references.push(Value::string(construct_did.value().to_string()));
2224 }
2225 }
2226 results.inputs.insert(&input_name, Value::array(references));
2227 continue;
2228 }
2229 };
2230 results.insert(&input_name, value);
2231 } else {
2232 let value = if let Some(value) = previously_evaluated_input {
2233 value.clone()
2234 } else {
2235 let Some(expr) = signer_instance.get_expression_from_input(&input_name) else {
2236 continue;
2237 };
2238 match eval_expression(
2239 &expr,
2240 dependencies_execution_results,
2241 package_id,
2242 runbook_workspace_context,
2243 runbook_execution_context,
2244 runtime_context,
2245 ) {
2246 Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
2247 Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
2248 if e.is_error() {
2249 fatal_error = true;
2250 }
2251 diags.push(e);
2252 continue;
2253 }
2254 Err(e) => {
2255 if e.is_error() {
2256 fatal_error = true;
2257 }
2258 diags.push(e);
2259 continue;
2260 }
2261 Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
2262 require_user_interaction = true;
2263 continue;
2264 }
2265 }
2266 };
2267
2268 results.insert(&input_name, value);
2269 }
2270 }
2271
2272 let status = match (fatal_error, require_user_interaction) {
2273 (false, false) => CommandInputEvaluationStatus::Complete(results),
2274 (true, _) => CommandInputEvaluationStatus::Aborted(results, diags),
2275 (false, _) => CommandInputEvaluationStatus::NeedsUserInteraction(results),
2276 };
2277 Ok(status)
2278}
2279
2280#[derive(Clone, Debug)]
2281struct EvaluateMapInputResult {
2282 result: CommandInputsEvaluationResult,
2283 require_user_interaction: bool,
2284 diags: Vec<Diagnostic>,
2285 fatal_error: bool,
2286}
2287fn evaluate_map_input(
2291 mut result: CommandInputsEvaluationResult,
2292 input_spec: &Box<dyn EvaluatableInput>,
2293 with_evaluatable_inputs: &impl WithEvaluatableInputs,
2294 dependencies_execution_results: &DependencyExecutionResultCache,
2295 package_id: &PackageId,
2296 runbook_workspace_context: &RunbookWorkspaceContext,
2297 runbook_execution_context: &RunbookExecutionContext,
2298 runtime_context: &RuntimeContext,
2299) -> Result<Option<EvaluateMapInputResult>, Vec<Diagnostic>> {
2300 let input_name = input_spec.name();
2301 let input_typing = input_spec.typing();
2302 let input_optional = input_spec.optional();
2303
2304 let spec_object_def = input_spec.as_map().expect("expected input to be a map");
2305
2306 let Some(blocks) =
2307 with_evaluatable_inputs.get_blocks_for_map(&input_name, &input_typing, input_optional)?
2308 else {
2309 return Ok(None);
2310 };
2311
2312 let res = match spec_object_def {
2313 ObjectDefinition::Strict(props) => evaluate_map_object_prop(
2314 &input_name,
2315 EvaluateMapObjectPropResult::new(),
2316 blocks,
2317 &props,
2318 dependencies_execution_results,
2319 package_id,
2320 runbook_workspace_context,
2321 runbook_execution_context,
2322 runtime_context,
2323 )
2324 .map_err(|diag| vec![diag])?,
2325 ObjectDefinition::Arbitrary(_) => evaluate_arbitrary_inputs_map(
2326 &input_name,
2327 EvaluateMapObjectPropResult::new(),
2328 blocks,
2329 dependencies_execution_results,
2330 package_id,
2331 runbook_workspace_context,
2332 runbook_execution_context,
2333 runtime_context,
2334 )
2335 .map_err(|diag| vec![diag])?,
2336 ObjectDefinition::Tuple(_) | ObjectDefinition::Enum(_) => {
2337 unimplemented!("ObjectDefinition::Tuple and ObjectDefinition::Enum are not supported for runbook types");
2338 }
2339 };
2340
2341 result.insert(&input_name, Value::array(res.entries));
2342 result.unevaluated_inputs.merge(&res.unevaluated_inputs);
2343 Ok(Some(EvaluateMapInputResult {
2344 result,
2345 require_user_interaction: res.require_user_interaction,
2346 diags: res.diags,
2347 fatal_error: res.fatal_error,
2348 }))
2349}
2350
2351fn evaluate_arbitrary_inputs_map(
2352 spec_input_name: &str,
2353 mut parent_result: EvaluateMapObjectPropResult,
2354 blocks: Vec<HclBlock>,
2355 dependencies_execution_results: &DependencyExecutionResultCache,
2356 package_id: &PackageId,
2357 runbook_workspace_context: &RunbookWorkspaceContext,
2358 runbook_execution_context: &RunbookExecutionContext,
2359 runtime_context: &RuntimeContext,
2360) -> Result<EvaluateMapObjectPropResult, Diagnostic> {
2361 for block in blocks {
2362 let mut object_values = IndexMap::new();
2363 for attr in block.body.attributes() {
2364 let expr = attr.value.clone();
2365 let ident = attr.key.to_string();
2366
2367 let value = match eval_expression(
2368 &expr,
2369 dependencies_execution_results,
2370 package_id,
2371 runbook_workspace_context,
2372 runbook_execution_context,
2373 runtime_context,
2374 ) {
2375 Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
2376 Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
2377 if e.is_error() {
2378 parent_result.fatal_error = true;
2379 }
2380 parent_result
2381 .unevaluated_inputs
2382 .insert(spec_input_name.to_string(), Some(e.clone()));
2383 parent_result.diags.push(e);
2384 continue;
2385 }
2386 Err(e) => {
2387 if e.is_error() {
2388 parent_result.fatal_error = true;
2389 }
2390 parent_result
2391 .unevaluated_inputs
2392 .insert(spec_input_name.to_string(), Some(e.clone()));
2393 parent_result.diags.push(e);
2394 continue;
2395 }
2396 Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
2397 parent_result.require_user_interaction = true;
2398 parent_result.unevaluated_inputs.insert(spec_input_name.to_string(), None);
2399 continue;
2400 }
2401 };
2402 match value.clone() {
2403 Value::Object(obj) => {
2404 for (k, v) in obj.into_iter() {
2405 object_values.insert(k, v);
2406 }
2407 }
2408 v => {
2409 object_values.insert(ident, v);
2410 }
2411 };
2412 }
2413
2414 let child_blocks = block.body.blocks().cloned().collect::<Vec<_>>();
2415 if !child_blocks.is_empty() {
2416 let mut ident_grouped_child_blocks = IndexMap::new();
2417 child_blocks.iter().for_each(|child_block| {
2418 ident_grouped_child_blocks
2419 .entry(child_block.ident.to_string())
2420 .or_insert_with(Vec::new)
2421 .push(child_block.clone())
2422 });
2423 for (ident, child_blocks) in ident_grouped_child_blocks {
2424 let child_block_result = evaluate_arbitrary_inputs_map(
2425 spec_input_name,
2426 EvaluateMapObjectPropResult::new(),
2427 child_blocks,
2428 dependencies_execution_results,
2429 package_id,
2430 runbook_workspace_context,
2431 runbook_execution_context,
2432 runtime_context,
2433 )?;
2434 parent_result.unevaluated_inputs = child_block_result.unevaluated_inputs;
2435 let mut diags = parent_result.diags.clone();
2436 diags.extend(child_block_result.diags);
2437 parent_result.diags = diags;
2438 if child_block_result.fatal_error {
2439 parent_result.fatal_error = true;
2440 continue;
2441 }
2442 if child_block_result.require_user_interaction {
2443 parent_result.require_user_interaction = true;
2444 continue;
2445 }
2446
2447 object_values.insert(ident, Value::array(child_block_result.entries));
2448 }
2449 }
2450 parent_result.entries.push(Value::object(object_values));
2451 }
2452 Ok(parent_result)
2453}
2454
2455#[derive(Clone, Debug)]
2456struct EvaluateMapObjectPropResult {
2457 entries: Vec<Value>,
2458 unevaluated_inputs: UnevaluatedInputsMap,
2459 require_user_interaction: bool,
2460 diags: Vec<Diagnostic>,
2461 fatal_error: bool,
2462}
2463impl EvaluateMapObjectPropResult {
2464 fn new() -> Self {
2465 Self {
2466 entries: vec![],
2467 unevaluated_inputs: UnevaluatedInputsMap::new(),
2468 require_user_interaction: false,
2469 diags: vec![],
2470 fatal_error: false,
2471 }
2472 }
2473}
2474
2475fn evaluate_map_object_prop(
2476 spec_input_name: &str,
2477 mut parent_result: EvaluateMapObjectPropResult,
2478 blocks: Vec<HclBlock>,
2479 spec_object_props: &Vec<ObjectProperty>,
2480 dependencies_execution_results: &DependencyExecutionResultCache,
2481 package_id: &PackageId,
2482 runbook_workspace_context: &RunbookWorkspaceContext,
2483 runbook_execution_context: &RunbookExecutionContext,
2484 runtime_context: &RuntimeContext,
2485) -> Result<EvaluateMapObjectPropResult, Diagnostic> {
2486 for block in blocks.iter() {
2487 let mut object_values = IndexMap::new();
2488 for spec_object_prop in spec_object_props.iter() {
2489 let value = if let Some(expr) =
2490 visit_optional_untyped_attribute(&spec_object_prop.name, &block)
2491 {
2492 let value = match eval_expression(
2493 &expr,
2494 dependencies_execution_results,
2495 package_id,
2496 runbook_workspace_context,
2497 runbook_execution_context,
2498 runtime_context,
2499 ) {
2500 Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
2501 Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
2502 if e.is_error() {
2503 parent_result.fatal_error = true;
2504 }
2505 parent_result
2506 .unevaluated_inputs
2507 .insert(spec_input_name.to_string(), Some(e.clone()));
2508 parent_result.diags.push(e);
2509 continue;
2510 }
2511 Err(e) => {
2512 if e.is_error() {
2513 parent_result.fatal_error = true;
2514 }
2515 parent_result
2516 .unevaluated_inputs
2517 .insert(spec_input_name.to_string(), Some(e.clone()));
2518 parent_result.diags.push(e);
2519 continue;
2520 }
2521 Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
2522 parent_result.require_user_interaction = true;
2523 parent_result.unevaluated_inputs.insert(spec_input_name.to_string(), None);
2524 continue;
2525 }
2526 };
2527 value
2528 } else {
2529 let child_map_blocks = block
2530 .body
2531 .get_blocks(&spec_object_prop.name)
2532 .into_iter()
2533 .map(|b| b.clone())
2534 .collect::<Vec<_>>();
2535 if child_map_blocks.is_empty() {
2536 continue;
2537 }
2538 let Type::Map(ref child_map_spec_object_def) = spec_object_prop.typing else {
2539 return Err(diagnosed_error!(
2540 "expected type {} for property {}, found map",
2541 spec_object_prop.typing.to_string(),
2542 spec_object_prop.name
2543 ));
2544 };
2545
2546 let res = match child_map_spec_object_def {
2547 ObjectDefinition::Strict(props) => evaluate_map_object_prop(
2548 spec_input_name,
2549 EvaluateMapObjectPropResult::new(),
2550 child_map_blocks,
2551 &props,
2552 dependencies_execution_results,
2553 package_id,
2554 runbook_workspace_context,
2555 runbook_execution_context,
2556 runtime_context,
2557 )?,
2558 ObjectDefinition::Arbitrary(_) => evaluate_arbitrary_inputs_map(
2559 spec_input_name,
2560 EvaluateMapObjectPropResult::new(),
2561 child_map_blocks,
2562 dependencies_execution_results,
2563 package_id,
2564 runbook_workspace_context,
2565 runbook_execution_context,
2566 runtime_context,
2567 )?,
2568 ObjectDefinition::Tuple(_) | ObjectDefinition::Enum(_) => {
2569 unimplemented!("ObjectDefinition::Tuple and ObjectDefinition::Enum are not supported for runbook types");
2570 }
2571 };
2572
2573 parent_result.unevaluated_inputs = res.unevaluated_inputs;
2574 let mut diags = parent_result.diags.clone();
2575 diags.extend(res.diags);
2576 parent_result.diags = diags;
2577 if res.fatal_error {
2578 parent_result.fatal_error = true;
2579 continue;
2580 }
2581 if res.require_user_interaction {
2582 parent_result.require_user_interaction = true;
2583 continue;
2584 }
2585 Value::array(res.entries)
2586 };
2587
2588 match value.clone() {
2589 Value::Object(obj) => {
2590 for (k, v) in obj.into_iter() {
2591 object_values.insert(k, v);
2592 }
2593 }
2594 v => {
2595 object_values.insert(spec_object_prop.name.to_string(), v);
2596 }
2597 };
2598 }
2599 parent_result.entries.push(Value::object(object_values));
2600 }
2601
2602 Ok(parent_result)
2603}
2604
2605#[cfg(test)]
2606mod map_eval_tests;