Skip to main content

alembic_engine/
lib.rs

1//! engine orchestration: load, validate, plan, apply.
2
3mod adapter_ops;
4mod apply_retry;
5mod django;
6mod errors;
7pub mod external;
8mod extract;
9mod loader;
10mod pipeline;
11mod planner;
12mod retort;
13mod state;
14mod types;
15
16use alembic_core::{key_string, validate_inventory, Inventory, Object, ValidationReport};
17use anyhow::{anyhow, Result};
18
19#[cfg(test)]
20mod tests;
21
22pub use adapter_ops::{
23    build_key_from_schema, build_request_body, query_filters_from_key, resolve_value_for_type,
24};
25pub use apply_retry::{apply_non_delete_with_retries, RetryApplyDriver, RetryApplyResult};
26pub use django::{emit_django_app, DjangoEmitOptions};
27pub use errors::AdapterApplyError;
28pub use external::{
29    run_external_adapter, ExternalAdapter, ExternalEnvelope, ExternalObject, ExternalRequest,
30    ExternalResponse, EXTERNAL_PROTOCOL_VERSION,
31};
32pub use extract::{import_inventory, ImportReport};
33pub use loader::load_brew;
34pub use planner::{plan, sort_ops_for_apply};
35pub use retort::{compile_retort, is_brew_format, load_raw_yaml, load_retort, Retort};
36pub use state::{PostgresTlsMode, StateData, StateStore};
37pub use types::{
38    Adapter, AppliedOp, ApplyReport, BackendId, FieldChange, ObservedObject, ObservedState, Op,
39    Plan, PlanSummary, ProvisionReport,
40};
41
42/// validate an inventory and return the report.
43pub fn validate(inventory: &Inventory) -> ValidationReport {
44    validate_inventory(inventory)
45}
46
47/// helper to format a validation report into a Result.
48pub fn report_to_result(report: ValidationReport) -> Result<()> {
49    report_to_result_with_sources(report, &[])
50}
51
52/// helper to format a validation report with source locations into a Result.
53pub fn report_to_result_with_sources(report: ValidationReport, objects: &[Object]) -> Result<()> {
54    if report.is_ok() {
55        return Ok(());
56    }
57
58    let located_errors = report.with_sources(objects);
59    let mut message = String::from("validation failed:\n");
60    for error in located_errors {
61        message.push_str(&format!("- {error}\n"));
62    }
63    Err(anyhow!(message))
64}
65
66/// observe backend state and produce a deterministic plan.
67pub async fn build_plan(
68    adapter: &(dyn Adapter + '_),
69    inventory: &Inventory,
70    state: &mut StateStore,
71    allow_delete: bool,
72) -> Result<Plan> {
73    let observed = pipeline::observe(adapter, inventory, state).await?;
74    Ok(plan(
75        &inventory.objects,
76        &observed,
77        state,
78        &inventory.schema,
79        allow_delete,
80    ))
81}
82
83pub(crate) fn bootstrap_state_from_observed(
84    state: &mut StateStore,
85    desired: &[Object],
86    observed: &ObservedState,
87) -> bool {
88    let mut updated = false;
89    for object in desired {
90        if state
91            .backend_id(object.type_name.clone(), object.uid)
92            .is_some()
93        {
94            continue;
95        }
96        if let Some(obs) = observed
97            .by_key
98            .get(&(object.type_name.clone(), key_string(&object.key)))
99        {
100            if obs.type_name != object.type_name {
101                continue;
102            }
103            if let Some(backend_id) = &obs.backend_id {
104                state.set_backend_id(object.type_name.clone(), object.uid, backend_id.clone());
105                updated = true;
106            }
107        }
108    }
109    updated
110}
111
112/// apply a plan and update the state store.
113pub async fn apply_plan(
114    adapter: &(dyn Adapter + '_),
115    plan: &Plan,
116    state: &mut StateStore,
117    allow_delete: bool,
118) -> Result<ApplyReport> {
119    pipeline::apply(adapter, plan, state, allow_delete).await
120}