spin_doctor/manifest/
upgrade.rs1use anyhow::{anyhow, Context, Result};
2use async_trait::async_trait;
3use spin_common::ui::quoted_path;
4use spin_manifest::{compat::v1_to_v2_app, schema::v1::AppManifestV1, ManifestVersion};
5use toml_edit::{de::from_document, ser::to_document, Item, Table};
6
7use crate::{Diagnosis, Diagnostic, PatientApp, Treatment};
8
9#[derive(Default)]
11pub struct UpgradeDiagnostic;
12
13#[async_trait]
14impl Diagnostic for UpgradeDiagnostic {
15 type Diagnosis = UpgradeDiagnosis;
16
17 async fn diagnose(&self, patient: &PatientApp) -> Result<Vec<Self::Diagnosis>> {
18 Ok(
19 match ManifestVersion::detect(&patient.manifest_doc.to_string())? {
20 ManifestVersion::V1 => vec![UpgradeDiagnosis],
21 _ => vec![],
22 },
23 )
24 }
25}
26
27#[derive(Debug)]
29pub struct UpgradeDiagnosis;
30
31impl Diagnosis for UpgradeDiagnosis {
32 fn description(&self) -> String {
33 "Version 1 manifest can be upgraded to version 2".into()
34 }
35
36 fn is_critical(&self) -> bool {
37 false
38 }
39
40 fn treatment(&self) -> Option<&dyn crate::Treatment> {
41 Some(self)
42 }
43}
44
45#[async_trait]
46impl Treatment for UpgradeDiagnosis {
47 fn summary(&self) -> String {
48 "Upgrade manifest to version 2".into()
49 }
50
51 async fn treat(&self, patient: &mut PatientApp) -> Result<()> {
52 let v1: AppManifestV1 = from_document(patient.manifest_doc.clone())
53 .context("failed to decode AppManifestV1")?;
54 let v2 = v1_to_v2_app(v1).context("failed to upgrade version 1 manifest to version 2")?;
55 let mut v2_doc = to_document(&v2)?;
56
57 let application = uninline_table(&mut v2_doc["application"])?;
59 if let Some(application_trigger) = application.get_mut("trigger") {
60 let application_trigger = uninline_table(application_trigger)?;
61 application_trigger.set_dotted(true);
62 for (_, trigger_config) in application_trigger.iter_mut() {
63 uninline_table(trigger_config)?.set_implicit(true);
64 }
65 }
66
67 if let Some(variables) = v2_doc.get_mut("variables") {
69 uninline_table(variables)?;
70 }
71
72 if let Some(triggers) = v2_doc.get_mut("trigger") {
74 let triggers = uninline_table(triggers)?;
75 triggers.set_dotted(true);
76 for (_, typed_triggers) in triggers.iter_mut() {
77 *typed_triggers = Item::ArrayOfTables(
78 std::mem::take(typed_triggers)
79 .into_array_of_tables()
80 .map_err(expected_table)?,
81 );
82 }
83 }
84
85 if let Some(components) = v2_doc.get_mut("component") {
87 let components = uninline_table(components)?;
88 components.set_dotted(true);
89 for (_, component) in components.iter_mut() {
90 let component = uninline_table(component)?;
91 if let Some(build) = component.get_mut("build") {
92 uninline_table(build)?;
93 }
94 }
95 }
96
97 let v1_backup_path = patient.manifest_path.with_extension("toml.v1_backup");
99 std::fs::rename(&patient.manifest_path, &v1_backup_path)
100 .context("failed to back up existing manifest")?;
101 println!(
102 "Version 1 manifest backed up to {}.",
103 quoted_path(&v1_backup_path)
104 );
105
106 std::fs::write(&patient.manifest_path, v2_doc.to_string())
108 .context("failed to write version 2 manifest")?;
109 patient.manifest_doc = v2_doc;
110
111 Ok(())
112 }
113}
114
115fn uninline_table(item: &mut Item) -> Result<&mut Table> {
116 *item = Item::Table(std::mem::take(item).into_table().map_err(expected_table)?);
117 Ok(item.as_table_mut().unwrap())
118}
119
120fn expected_table(got: Item) -> anyhow::Error {
121 anyhow!("expected table, got {}", got.type_name())
122}