1use diffing_context::ConsolidatedPlanChanges;
2use flow_context::FlowContext;
3use kit::indexmap::IndexMap;
4use kit::types::cloud_interface::CloudServiceContext;
5use kit::types::frontend::ActionItemRequestType;
6use kit::types::types::AddonJsonConverter;
7use kit::types::{ConstructDid, RunbookInstanceContext};
8use serde_json::{json, Value as JsonValue};
9use std::collections::{HashMap, HashSet, VecDeque};
10use txtx_addon_kit::hcl::structure::BlockLabel;
11use txtx_addon_kit::hcl::Span;
12use txtx_addon_kit::helpers::fs::FileLocation;
13use txtx_addon_kit::helpers::hcl::RawHclContent;
14use txtx_addon_kit::types::commands::{CommandExecutionResult, DependencyExecutionResultCache};
15use txtx_addon_kit::types::diagnostics::DiagnosticSpan;
16use txtx_addon_kit::types::stores::ValueStore;
17use txtx_addon_kit::types::types::RunbookSupervisionContext;
18use txtx_addon_kit::types::{diagnostics::Diagnostic, types::Value};
19use txtx_addon_kit::types::{AuthorizationContext, Did, PackageId, RunbookId};
20use txtx_addon_kit::Addon;
21
22pub mod collector;
23mod diffing_context;
24pub mod embedded_runbook;
25mod execution_context;
26pub mod flow_context;
27mod graph_context;
28pub mod location;
29mod runtime_context;
30pub mod variables;
31mod workspace_context;
32
33pub use diffing_context::ConsolidatedChanges;
34pub use diffing_context::{RunbookExecutionSnapshot, RunbookSnapshotContext, SynthesizedChange};
35pub use execution_context::{RunbookExecutionContext, RunbookExecutionMode};
36pub use graph_context::RunbookGraphContext;
37pub use runtime_context::{AddonConstructFactory, RuntimeContext};
38pub use workspace_context::RunbookWorkspaceContext;
39
40use crate::manifest::{RunbookStateLocation, RunbookTransientStateLocation};
41
42#[derive(Debug)]
43pub struct Runbook {
44 pub runbook_id: RunbookId,
46 pub description: Option<String>,
48 pub runtime_context: RuntimeContext,
50 pub flow_contexts: Vec<FlowContext>,
52 pub supervision_context: RunbookSupervisionContext,
54 pub sources: RunbookSources,
56 pub top_level_inputs_map: RunbookTopLevelInputsMap,
58}
59
60impl Runbook {
61 fn get_no_addons_by_namespace(_namepace: &str) -> Option<Box<dyn Addon>> {
62 None
63 }
64 pub fn new(runbook_id: RunbookId, description: Option<String>) -> Self {
65 Self {
66 runbook_id,
67 description,
68 flow_contexts: vec![],
69 runtime_context: RuntimeContext::new(
70 AuthorizationContext::empty(),
71 Runbook::get_no_addons_by_namespace,
72 CloudServiceContext::empty(),
73 ),
74 sources: RunbookSources::new(),
75 supervision_context: RunbookSupervisionContext::new(),
76 top_level_inputs_map: RunbookTopLevelInputsMap::new(),
77 }
78 }
79
80 pub fn runbook_id(&self) -> RunbookId {
81 self.runbook_id.clone()
82 }
83
84 pub fn to_instance_context(&self) -> RunbookInstanceContext {
85 RunbookInstanceContext {
86 runbook_id: self.runbook_id.clone(),
87 workspace_location: self
88 .runtime_context
89 .authorization_context
90 .workspace_location
91 .clone(),
92 environment_selector: self.top_level_inputs_map.current_environment.clone(),
93 }
94 }
95
96 pub fn enable_full_execution_mode(&mut self) {
97 for r in self.flow_contexts.iter_mut() {
98 r.execution_context.execution_mode = RunbookExecutionMode::Full
99 }
100 }
101
102 pub fn initialize_flow_contexts(
107 &self,
108 runtime_context: &RuntimeContext,
109 runbook_sources: &RunbookSources,
110 top_level_inputs_map: &RunbookTopLevelInputsMap,
111 ) -> Result<Vec<FlowContext>, Diagnostic> {
112 let mut dummy_workspace_context = RunbookWorkspaceContext::new(self.runbook_id.clone());
113 let mut dummy_execution_context = RunbookExecutionContext::new();
114
115 let current_top_level_value_store = top_level_inputs_map.current_top_level_inputs();
116
117 for (key, value) in current_top_level_value_store.iter() {
118 let construct_did = dummy_workspace_context.index_top_level_input(key, value);
119
120 let mut result = CommandExecutionResult::new();
121 result.outputs.insert("value".into(), value.clone());
122 dummy_execution_context.commands_execution_results.insert(construct_did, result);
123 }
124
125 let mut sources = runbook_sources.to_vec_dequeue();
126 let dependencies_execution_results = DependencyExecutionResultCache::new();
127
128 let mut flow_contexts = vec![];
129
130 let mut package_ids = vec![];
131
132 let mut flow_map = vec![];
137 while let Some((location, package_name, raw_content)) = sources.pop_front() {
138 let package_id =
139 PackageId::from_file(&location, &self.runbook_id, &package_name).map_err(|e| e)?;
140 package_ids.push(package_id.clone());
141
142 let mut blocks = raw_content.into_typed_blocks().map_err(|diag| diag.location(&location))?;
143
144 while let Some(typed_block) = blocks.pop_front() {
145 use crate::types::ConstructType;
146 match &typed_block.construct_type {
147 Ok(ConstructType::Flow) => {
148 let Some(BlockLabel::String(name)) = typed_block.labels.first() else {
149 continue;
150 };
151 let flow_name = name.to_string();
152 let flow_context = FlowContext::new(
153 &flow_name,
154 &self.runbook_id,
155 ¤t_top_level_value_store,
156 );
157 flow_map.push((flow_context, typed_block.body.attributes().cloned().collect()));
158 }
159 _ => {}
160 }
161 }
162 }
163
164 if flow_map.is_empty() {
166 let flow_name = top_level_inputs_map.current_top_level_input_name();
167 let flow_context =
168 FlowContext::new(&flow_name, &self.runbook_id, ¤t_top_level_value_store);
169 flow_map.push((flow_context, vec![]));
170 }
171
172 for (flow_context, attributes) in flow_map.iter_mut() {
174 for package_id in package_ids.iter() {
175 flow_context.workspace_context.index_package(package_id);
176 flow_context.graph_context.index_package(package_id);
177 flow_context.index_flow_inputs_from_attributes(
178 attributes,
179 &dependencies_execution_results,
180 package_id,
181 &dummy_workspace_context,
182 &dummy_execution_context,
183 runtime_context,
184 )?;
185 }
186 flow_contexts.push(flow_context.to_owned());
187 }
188
189 Ok(flow_contexts)
190 }
191
192 pub async fn build_contexts_from_sources(
194 &mut self,
195 sources: RunbookSources,
196 top_level_inputs_map: RunbookTopLevelInputsMap,
197 authorization_context: AuthorizationContext,
198 get_addon_by_namespace: fn(&str) -> Option<Box<dyn Addon>>,
199 cloud_service_context: CloudServiceContext,
200 ) -> Result<bool, Vec<Diagnostic>> {
201 self.flow_contexts.clear();
203 let mut runtime_context = RuntimeContext::new(
204 authorization_context,
205 get_addon_by_namespace,
206 cloud_service_context,
207 );
208
209 let mut flow_contexts = self
211 .initialize_flow_contexts(&runtime_context, &sources, &top_level_inputs_map)
212 .map_err(|e| vec![e])?;
213
214 for flow_context in flow_contexts.iter_mut() {
216 runtime_context.register_addons_from_sources(
218 &mut flow_context.workspace_context,
219 &self.runbook_id,
220 &sources,
221 &flow_context.execution_context,
222 &top_level_inputs_map.current_environment,
223 )?;
224 flow_context
226 .workspace_context
227 .build_from_sources(
228 &sources,
229 &mut runtime_context,
230 &mut flow_context.graph_context,
231 &mut flow_context.execution_context,
232 &top_level_inputs_map.current_environment,
233 )
234 .await?;
235
236 flow_context
238 .execution_context
239 .simulate_inputs_execution(&runtime_context, &flow_context.workspace_context)
240 .await
241 .map_err(|diag| {
242 vec![diag
243 .clone()
244 .set_diagnostic_span(get_source_context_for_diagnostic(&diag, &sources))]
245 })?;
246 let domain_specific_dependencies = runtime_context
248 .perform_addon_processing(&mut flow_context.execution_context)
249 .map_err(|(diag, construct_did)| {
250 let construct_id =
251 &flow_context.workspace_context.expect_construct_id(&construct_did);
252 let command_instance = flow_context
253 .execution_context
254 .commands_instances
255 .get(&construct_did)
256 .unwrap();
257 let diag = diag
258 .location(&construct_id.construct_location)
259 .set_span_range(command_instance.block.span());
260 vec![diag
261 .clone()
262 .set_diagnostic_span(get_source_context_for_diagnostic(&diag, &sources))]
263 })?;
264 flow_context
266 .graph_context
267 .build(
268 &mut flow_context.execution_context,
269 &flow_context.workspace_context,
270 domain_specific_dependencies,
271 )
272 .map_err(|diags| {
273 diags
274 .into_iter()
275 .map(|diag| {
276 diag.clone().set_diagnostic_span(get_source_context_for_diagnostic(
277 &diag, &sources,
278 ))
279 })
280 .collect::<Vec<_>>()
281 })?;
282 }
283
284 self.flow_contexts = flow_contexts;
286 self.runtime_context = runtime_context;
287 self.sources = sources;
288 self.top_level_inputs_map = top_level_inputs_map;
289 Ok(true)
290 }
291
292 pub fn find_expected_flow_context_mut(&mut self, key: &str) -> &mut FlowContext {
293 for flow_context in self.flow_contexts.iter_mut() {
294 if flow_context.name.eq(key) {
295 return flow_context;
296 }
297 }
298 unreachable!()
299 }
300
301 pub async fn update_inputs_selector(
302 &mut self,
303 selector: Option<String>,
304 force: bool,
305 ) -> Result<bool, Vec<Diagnostic>> {
306 if !force && selector.eq(&self.top_level_inputs_map.current_environment) {
308 return Ok(false);
309 }
310
311 if let Some(ref entry) = selector {
313 if !self.top_level_inputs_map.environments.contains(entry) {
314 return Err(vec![Diagnostic::error_from_string(format!(
315 "input '{}' unknown from inputs map",
316 entry
317 ))]);
318 }
319 }
320 let mut inputs_map = self.top_level_inputs_map.clone();
322 inputs_map.current_environment = selector;
323 let authorization_context: AuthorizationContext =
324 self.runtime_context.authorization_context.clone();
325 let cloud_service_context: CloudServiceContext =
326 self.runtime_context.cloud_service_context.clone();
327
328 self.build_contexts_from_sources(
329 self.sources.clone(),
330 inputs_map,
331 authorization_context,
332 self.runtime_context.addons_context.get_addon_by_namespace,
333 cloud_service_context,
334 )
335 .await
336 }
337
338 pub fn get_inputs_selectors(&self) -> Vec<String> {
339 self.top_level_inputs_map.environments.clone()
340 }
341
342 pub fn get_active_inputs_selector(&self) -> Option<String> {
343 self.top_level_inputs_map.current_environment.clone()
344 }
345
346 pub fn backup_execution_contexts(&self) -> HashMap<String, RunbookExecutionContext> {
347 let mut execution_context_backups = HashMap::new();
348 for flow_context in self.flow_contexts.iter() {
349 let execution_context_backup = flow_context.execution_context.clone();
350 execution_context_backups.insert(flow_context.name.clone(), execution_context_backup);
351 }
352 execution_context_backups
353 }
354
355 pub async fn simulate_and_snapshot_flows(
356 &mut self,
357 old_snapshot: &RunbookExecutionSnapshot,
358 ) -> Result<RunbookExecutionSnapshot, String> {
359 let ctx = RunbookSnapshotContext::new();
360
361 for flow_context in self.flow_contexts.iter_mut() {
362 let frontier = HashSet::new();
363 let _res = flow_context
364 .execution_context
365 .simulate_execution(
366 &self.runtime_context,
367 &flow_context.workspace_context,
368 &self.supervision_context,
369 &frontier,
370 )
371 .await;
372
373 let Some(flow_snapshot) = old_snapshot.flows.get(&flow_context.name) else {
374 continue;
375 };
376
377 flow_context
380 .execution_context
381 .apply_snapshot_to_execution_context(flow_snapshot, &flow_context.workspace_context)
382 .map_err(|e| e.message)?;
383 }
384
385 let new = ctx
386 .snapshot_runbook_execution(
387 &self.runbook_id,
388 &self.flow_contexts,
389 None,
390 &self.top_level_inputs_map,
391 )
392 .map_err(|e| e.message)?;
393 Ok(new)
394 }
395
396 pub fn prepare_flows_for_new_plans(
397 &mut self,
398 new_plans_to_add: &Vec<String>,
399 execution_context_backups: HashMap<String, RunbookExecutionContext>,
400 ) {
401 for flow_context_key in new_plans_to_add.iter() {
402 let flow_context = self.find_expected_flow_context_mut(&flow_context_key);
403 flow_context.execution_context.execution_mode = RunbookExecutionMode::Full;
404 let pristine_execution_context =
405 execution_context_backups.get(flow_context_key).unwrap();
406 flow_context.execution_context = pristine_execution_context.clone();
407 }
408 }
409
410 pub fn prepared_flows_for_updated_plans(
411 &mut self,
412 plans_to_update: &IndexMap<String, ConsolidatedPlanChanges>,
413 ) -> (
414 IndexMap<String, Vec<(String, Option<String>)>>,
415 IndexMap<String, Vec<(String, Option<String>)>>,
416 ) {
417 let mut actions_to_re_execute = IndexMap::new();
418 let mut actions_to_execute = IndexMap::new();
419
420 for (flow_context_key, changes) in plans_to_update.iter() {
421 let critical_edits = changes
422 .constructs_to_update
423 .iter()
424 .filter(|c| !c.description.is_empty() && c.critical)
425 .collect::<Vec<_>>();
426
427 let additions = changes.new_constructs_to_add.iter().collect::<Vec<_>>();
428 let mut unexecuted =
429 changes.constructs_to_run.iter().map(|(e, _)| e.clone()).collect::<Vec<_>>();
430
431 let flow_context = self.find_expected_flow_context_mut(&flow_context_key);
432
433 if critical_edits.is_empty() && additions.is_empty() && unexecuted.is_empty() {
434 flow_context.execution_context.execution_mode = RunbookExecutionMode::Ignored;
435 continue;
436 }
437
438 let mut added_construct_dids: Vec<ConstructDid> =
439 additions.into_iter().map(|(construct_did, _)| construct_did.clone()).collect();
440
441 let mut descendants_of_critically_changed_commands = critical_edits
442 .iter()
443 .filter_map(|c| {
444 if let Some(construct_did) = &c.construct_did {
445 let mut segment = vec![];
446 segment.push(construct_did.clone());
447 let mut deps = flow_context
448 .graph_context
449 .get_downstream_dependencies_for_construct_did(&construct_did, true);
450 segment.append(&mut deps);
451 Some(segment)
452 } else {
453 None
454 }
455 })
456 .flatten()
457 .filter(|d| !added_construct_dids.contains(d))
458 .collect::<Vec<_>>();
459 descendants_of_critically_changed_commands.sort();
460 descendants_of_critically_changed_commands.dedup();
461
462 let actions: Vec<(String, Option<String>)> = descendants_of_critically_changed_commands
463 .iter()
464 .map(|construct_did| {
465 let documentation = flow_context
466 .execution_context
467 .commands_inputs_evaluation_results
468 .get(construct_did)
469 .and_then(|r| r.inputs.get_string("description"))
470 .and_then(|d| Some(d.to_string()));
471 let command = flow_context
472 .execution_context
473 .commands_instances
474 .get(construct_did)
475 .unwrap();
476 (command.name.to_string(), documentation)
477 })
478 .collect();
479 actions_to_re_execute.insert(flow_context_key.clone(), actions);
480
481 let added_actions: Vec<(String, Option<String>)> = added_construct_dids
482 .iter()
483 .map(|construct_did| {
484 let documentation = flow_context
485 .execution_context
486 .commands_inputs_evaluation_results
487 .get(construct_did)
488 .and_then(|r| r.inputs.get_string("description"))
489 .and_then(|d| Some(d.to_string()));
490 let command = flow_context
491 .execution_context
492 .commands_instances
493 .get(construct_did)
494 .unwrap();
495 (command.name.to_string(), documentation)
496 })
497 .collect();
498 actions_to_execute.insert(flow_context_key.clone(), added_actions);
499
500 let mut great_filter = descendants_of_critically_changed_commands;
501 great_filter.append(&mut added_construct_dids);
502 great_filter.append(&mut unexecuted);
503
504 for construct_did in great_filter.iter() {
505 let _ =
506 flow_context.execution_context.commands_execution_results.remove(construct_did);
507 }
508
509 flow_context.execution_context.order_for_commands_execution = flow_context
510 .execution_context
511 .order_for_commands_execution
512 .clone()
513 .into_iter()
514 .filter(|c| great_filter.contains(&c))
515 .collect();
516
517 flow_context.execution_context.execution_mode =
518 RunbookExecutionMode::Partial(great_filter);
519 }
520
521 (actions_to_re_execute, actions_to_execute)
522 }
523
524 pub fn write_runbook_state(
525 &self,
526 runbook_state_location: Option<RunbookStateLocation>,
527 ) -> Result<Option<FileLocation>, String> {
528 if let Some(state_file_location) = runbook_state_location {
529 let previous_snapshot = match state_file_location.load_execution_snapshot(
530 true,
531 &self.runbook_id.name,
532 &self.top_level_inputs_map.current_top_level_input_name(),
533 ) {
534 Ok(snapshot) => Some(snapshot),
535 Err(_e) => None,
536 };
537
538 let state_file_location = state_file_location.get_location_for_ctx(
539 &self.runbook_id.name,
540 Some(&self.top_level_inputs_map.current_top_level_input_name()),
541 );
542 if let Some(RunbookTransientStateLocation(lock_file)) =
543 RunbookTransientStateLocation::from_state_file_location(&state_file_location)
544 {
545 let _ = std::fs::remove_file(&lock_file.to_string());
546 }
547
548 let diff = RunbookSnapshotContext::new();
549 let snapshot = diff
550 .snapshot_runbook_execution(
551 &self.runbook_id,
552 &self.flow_contexts,
553 previous_snapshot,
554 &self.top_level_inputs_map,
555 )
556 .map_err(|e| e.message)?;
557 state_file_location
558 .write_content(serde_json::to_string_pretty(&snapshot).unwrap().as_bytes())
559 .expect("unable to save state");
560 Ok(Some(state_file_location))
561 } else {
562 Ok(None)
563 }
564 }
565
566 pub fn mark_failed_and_write_transient_state(
567 &mut self,
568 runbook_state_location: Option<RunbookStateLocation>,
569 ) -> Result<Option<FileLocation>, String> {
570 for running_context in self.flow_contexts.iter_mut() {
571 running_context.execution_context.execution_mode = RunbookExecutionMode::FullFailed;
572 }
573
574 if let Some(runbook_state_location) = runbook_state_location {
575 let previous_snapshot = match runbook_state_location.load_execution_snapshot(
576 false,
577 &self.runbook_id.name,
578 &self.top_level_inputs_map.current_top_level_input_name(),
579 ) {
580 Ok(snapshot) => Some(snapshot),
581 Err(_e) => None,
582 };
583
584 let lock_file = RunbookTransientStateLocation::get_location_from_state_file_location(
585 &runbook_state_location.get_location_for_ctx(
586 &self.runbook_id.name,
587 Some(&self.top_level_inputs_map.current_top_level_input_name()),
588 ),
589 );
590 let diff = RunbookSnapshotContext::new();
591 let snapshot = diff
592 .snapshot_runbook_execution(
593 &self.runbook_id,
594 &self.flow_contexts,
595 previous_snapshot,
596 &self.top_level_inputs_map,
597 )
598 .map_err(|e| e.message)?;
599 lock_file
600 .write_content(serde_json::to_string_pretty(&snapshot).unwrap().as_bytes())
601 .map_err(|e| format!("unable to save state ({})", e.to_string()))?;
602 Ok(Some(lock_file))
603 } else {
604 Ok(None)
605 }
606 }
607
608 pub fn collect_formatted_outputs(&self) -> RunbookOutputs {
609 let mut runbook_outputs = RunbookOutputs::new();
610 for flow_context in self.flow_contexts.iter() {
611 let grouped_actions_items = flow_context
612 .execution_context
613 .collect_outputs_constructs_results(&self.runtime_context.authorization_context);
614 for (_, items) in grouped_actions_items.iter() {
615 for item in items.iter() {
616 if let ActionItemRequestType::DisplayOutput(ref output) = item.action_type {
617 runbook_outputs.add_output(
618 &flow_context.name,
619 &output.name,
620 &output.value,
621 &output.description,
622 );
623 }
624 }
625 }
626 }
627 runbook_outputs
628 }
629}
630
631#[derive(Clone, Debug)]
632pub struct RunbookOutputs {
633 outputs: IndexMap<String, IndexMap<String, (Value, Option<String>)>>,
634}
635impl RunbookOutputs {
636 pub fn new() -> Self {
637 Self { outputs: IndexMap::new() }
638 }
639
640 pub fn add_output(
641 &mut self,
642 flow_name: &str,
643 output_name: &str,
644 output_value: &Value,
645 output_description: &Option<String>,
646 ) {
647 let flow_outputs = self.outputs.entry(flow_name.to_string()).or_insert_with(IndexMap::new);
648 flow_outputs
649 .insert(output_name.to_string(), (output_value.clone(), output_description.clone()));
650 }
651
652 pub fn get_output_row_data(
654 &self,
655 filter: &Option<String>,
656 ) -> IndexMap<String, Vec<Vec<String>>> {
657 let mut output_row_data = IndexMap::new();
658 for (flow_name, flow_outputs) in self.outputs.iter() {
659 let mut flow_output_row =
660 vec![vec!["name".to_string(), "value".to_string(), "description".to_string()]];
661 for (output_name, (output_value, output_description)) in flow_outputs.iter() {
662 if let Some(ref filter) = filter {
663 if !output_name.contains(filter) {
664 continue;
665 }
666 }
667
668 let mut row = vec![];
669 row.push(output_name.to_string());
670 row.push(output_value.to_string());
671 row.push(output_description.clone().unwrap_or_else(|| "".to_string()));
672 flow_output_row.push(row);
673 }
674 output_row_data.insert(flow_name.to_string(), flow_output_row);
675 }
676 output_row_data
677 }
678
679 pub fn to_json(&self, addon_converters: &Vec<AddonJsonConverter>) -> JsonValue {
680 let mut json = json!({});
681 let only_one_flow = self.outputs.len() == 1;
682 for (flow_name, flow_outputs) in self.outputs.iter() {
683 let mut flow_json = json!({});
684 for (output_name, (output_value, output_description)) in flow_outputs.iter() {
685 let mut output_json = json!({});
686 output_json["value"] = output_value.to_json(Some(&addon_converters));
687 if let Some(ref output_description) = output_description {
688 output_json["description"] = output_description.clone().into();
689 }
690 flow_json[output_name] = output_json;
691 }
692 if only_one_flow {
693 return flow_json;
694 }
695 json[flow_name] = flow_json;
696 }
697 json
698 }
699
700 pub fn is_empty(&self) -> bool {
701 if self.outputs.is_empty() {
702 return true;
703 }
704 let mut empty = true;
705 for (_, outputs) in self.outputs.iter() {
706 if !outputs.is_empty() {
707 empty = false;
708 }
709 }
710 empty
711 }
712}
713
714#[derive(Clone, Debug)]
715pub struct RunbookTopLevelInputsMap {
716 current_environment: Option<String>,
717 environments: Vec<String>,
718 values: HashMap<Option<String>, Vec<(String, Value)>>,
719}
720
721pub const DEFAULT_TOP_LEVEL_INPUTS_NAME: &str = "default";
722pub const GLOBAL_TOP_LEVEL_INPUTS_NAME: &str = "global";
723
724impl RunbookTopLevelInputsMap {
725 pub fn new() -> Self {
726 Self { current_environment: None, environments: vec![], values: HashMap::new() }
727 }
728 pub fn from_environment_map(
729 selector: &Option<String>,
730 environments_map: &IndexMap<String, IndexMap<String, String>>,
731 ) -> Self {
732 let mut environments = vec![];
733 let mut values = HashMap::from_iter([(None, vec![])]);
734
735 let mut global_values = vec![];
736 if let Some(global_env_vars) = environments_map.get(GLOBAL_TOP_LEVEL_INPUTS_NAME) {
737 for (key, value) in global_env_vars.iter() {
738 global_values.push((key.to_string(), Value::parse_and_default_to_string(value)));
739 }
740 };
741
742 for (selector, inputs) in environments_map.iter() {
743 if selector.eq(GLOBAL_TOP_LEVEL_INPUTS_NAME) {
744 continue; }
746 let mut env_values = vec![];
747 for (key, value) in global_values.iter() {
749 env_values.push((key.to_string(), value.clone()));
750 }
751 for (key, value) in inputs.iter() {
753 env_values.push((key.to_string(), Value::parse_and_default_to_string(value)));
754 }
755 environments.push(selector.to_string());
756 values.insert(Some(selector.to_string()), env_values);
757 }
758
759 Self {
760 current_environment: selector.clone().or(environments.get(0).map(|v| v.to_string())),
761 environments,
762 values,
763 }
764 }
765
766 pub fn current_top_level_input_name(&self) -> String {
767 self.current_environment
768 .clone()
769 .unwrap_or_else(|| DEFAULT_TOP_LEVEL_INPUTS_NAME.to_string())
770 }
771
772 pub fn current_top_level_inputs(&self) -> ValueStore {
773 let empty_vec = vec![];
774 let name = self.current_top_level_input_name();
775 let raw_inputs = self.values.get(&self.current_environment).unwrap_or(&empty_vec);
776 let current_map = ValueStore::new(&name, &Did::zero()).with_inputs_from_vec(raw_inputs);
777 current_map
778 }
779
780 pub fn override_values_with_cli_inputs(
781 &mut self,
782 inputs: &Vec<String>,
783 buffer_stdin: Option<String>,
784 ) -> Result<(), String> {
785 for input in inputs.iter() {
786 let Some((input_name, input_value)) = input.split_once("=") else {
787 return Err(format!(
788 "expected --input argument to be formatted as '{}', got '{}'",
789 "key=value", input
790 ));
791 };
792 let input_value = match (input_value.eq("←"), &buffer_stdin) {
793 (true, Some(v)) => v.to_string(),
794 _ => input_value.to_string(),
795 };
796 let new_value = Value::parse_and_default_to_string(&input_value);
797 for (_, values) in self.values.iter_mut() {
798 let mut found = false;
799 for (k, old_value) in values.iter_mut() {
800 if k.eq(&input_name) {
801 *old_value = new_value.clone();
802 found = true;
803 }
804 }
805 if !found {
806 values.push((input_name.to_string(), new_value.clone()));
807 }
808 }
809 }
810 Ok(())
811 }
812}
813
814#[derive(Clone, Debug)]
815pub struct RunbookSources {
816 pub tree: HashMap<FileLocation, (String, RawHclContent)>,
818}
819
820impl RunbookSources {
821 pub fn new() -> Self {
822 Self { tree: HashMap::new() }
823 }
824
825 pub fn add_source(&mut self, name: String, location: FileLocation, content: String) {
826 self.tree.insert(location, (name, RawHclContent::from_string(content)));
827 }
828
829 pub fn to_vec_dequeue(&self) -> VecDeque<(FileLocation, String, RawHclContent)> {
830 self.tree
831 .iter()
832 .map(|(file_location, (package_name, raw_content))| {
833 (file_location.clone(), package_name.clone(), raw_content.clone())
834 })
835 .collect()
836 }
837}
838
839pub fn get_source_context_for_diagnostic(
840 diag: &Diagnostic,
841 runbook_sources: &RunbookSources,
842) -> Option<DiagnosticSpan> {
843 let Some(construct_location) = &diag.location else {
844 return None;
845 };
846 let Some(span_range) = &diag.span_range() else {
847 return None;
848 };
849
850 let Some((_, (_, raw_content))) =
851 runbook_sources.tree.iter().find(|(location, _)| location.eq(&construct_location))
852 else {
853 return None;
854 };
855 let raw_content_string = raw_content.to_string();
856 let mut lines = 1;
857 let mut cols = 1;
858 let mut span = DiagnosticSpan::new();
859
860 let mut chars = raw_content_string.chars().enumerate().peekable();
861 while let Some((i, ch)) = chars.next() {
862 if i == span_range.start {
863 span.line_start = lines;
864 span.column_start = cols;
865 }
866 if i == span_range.end {
867 span.line_end = lines;
868 span.column_end = cols;
869 }
870 match ch {
871 '\n' => {
872 lines += 1;
873 cols = 1;
874 }
875 '\r' => {
876 if let Some((_, '\n')) = chars.peek() {
878 chars.next();
880 lines += 1;
881 cols = 1;
882 } else {
883 cols += 1;
884 }
885 }
886 _ => {
887 cols += 1;
888 }
889 }
890 }
891 Some(span)
892}