spin_doctor/manifest/
upgrade.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use spin_common::ui::quoted_path;
use spin_manifest::{compat::v1_to_v2_app, schema::v1::AppManifestV1, ManifestVersion};
use toml_edit::{de::from_document, ser::to_document, Item, Table};

use crate::{Diagnosis, Diagnostic, PatientApp, Treatment};

/// UpgradeDiagnostic detects old manifest versions and upgrades them.
#[derive(Default)]
pub struct UpgradeDiagnostic;

#[async_trait]
impl Diagnostic for UpgradeDiagnostic {
    type Diagnosis = UpgradeDiagnosis;

    async fn diagnose(&self, patient: &PatientApp) -> Result<Vec<Self::Diagnosis>> {
        Ok(
            match ManifestVersion::detect(&patient.manifest_doc.to_string())? {
                ManifestVersion::V1 => vec![UpgradeDiagnosis],
                _ => vec![],
            },
        )
    }
}

/// UpgradeDiagnosis represents an upgradable manifest.
#[derive(Debug)]
pub struct UpgradeDiagnosis;

impl Diagnosis for UpgradeDiagnosis {
    fn description(&self) -> String {
        "Version 1 manifest can be upgraded to version 2".into()
    }

    fn is_critical(&self) -> bool {
        false
    }

    fn treatment(&self) -> Option<&dyn crate::Treatment> {
        Some(self)
    }
}

#[async_trait]
impl Treatment for UpgradeDiagnosis {
    fn summary(&self) -> String {
        "Upgrade manifest to version 2".into()
    }

    async fn treat(&self, patient: &mut PatientApp) -> Result<()> {
        let v1: AppManifestV1 = from_document(patient.manifest_doc.clone())
            .context("failed to decode AppManifestV1")?;
        let v2 = v1_to_v2_app(v1).context("failed to upgrade version 1 manifest to version 2")?;
        let mut v2_doc = to_document(&v2)?;

        // Format [application] table
        let application = uninline_table(&mut v2_doc["application"])?;
        if let Some(application_trigger) = application.get_mut("trigger") {
            let application_trigger = uninline_table(application_trigger)?;
            application_trigger.set_dotted(true);
            for (_, trigger_config) in application_trigger.iter_mut() {
                uninline_table(trigger_config)?.set_implicit(true);
            }
        }

        // Format [variables]
        if let Some(variables) = v2_doc.get_mut("variables") {
            uninline_table(variables)?;
        }

        // Format [[trigger.*]] tables
        if let Some(triggers) = v2_doc.get_mut("trigger") {
            let triggers = uninline_table(triggers)?;
            triggers.set_dotted(true);
            for (_, typed_triggers) in triggers.iter_mut() {
                *typed_triggers = Item::ArrayOfTables(
                    std::mem::take(typed_triggers)
                        .into_array_of_tables()
                        .map_err(expected_table)?,
                );
            }
        }

        // Format [component.*] tables
        if let Some(components) = v2_doc.get_mut("component") {
            let components = uninline_table(components)?;
            components.set_dotted(true);
            for (_, component) in components.iter_mut() {
                let component = uninline_table(component)?;
                if let Some(build) = component.get_mut("build") {
                    uninline_table(build)?;
                }
            }
        }

        // Back-up original V1 manifest
        let v1_backup_path = patient.manifest_path.with_extension("toml.v1_backup");
        std::fs::rename(&patient.manifest_path, &v1_backup_path)
            .context("failed to back up existing manifest")?;
        println!(
            "Version 1 manifest backed up to {}.",
            quoted_path(&v1_backup_path)
        );

        // Write new V2 manifest
        std::fs::write(&patient.manifest_path, v2_doc.to_string())
            .context("failed to write version 2 manifest")?;
        patient.manifest_doc = v2_doc;

        Ok(())
    }
}

fn uninline_table(item: &mut Item) -> Result<&mut Table> {
    *item = Item::Table(std::mem::take(item).into_table().map_err(expected_table)?);
    Ok(item.as_table_mut().unwrap())
}

fn expected_table(got: Item) -> anyhow::Error {
    anyhow!("expected table, got {}", got.type_name())
}