summaryrefslogtreecommitdiff
path: root/src/spec.rs
blob: 0a3d097dac255f5dfbed7355ad5964552af5a238 (plain)
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
use crate::config::Configuration;

use log::debug;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};

#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct InputSpecification {
    pub name: String,

    #[serde(default)]
    pub ssh_key_files: Vec<PathBuf>,

    pub rsa_host_key: Option<String>,
    pub rsa_host_cert: Option<String>,
    pub dsa_host_key: Option<String>,
    pub dsa_host_cert: Option<String>,
    pub ecdsa_host_key: Option<String>,
    pub ecdsa_host_cert: Option<String>,
    pub ed25519_host_key: Option<String>,
    pub ed25519_host_cert: Option<String>,

    pub base: Option<PathBuf>,
    pub image: PathBuf,
    pub image_size_gib: u64,
    pub memory_mib: u64,
    pub cpus: u64,
}

#[derive(Debug)]
pub struct Specification {
    pub name: String,
    pub ssh_keys: Vec<String>,
    pub rsa_host_key: Option<String>,
    pub rsa_host_cert: Option<String>,
    pub dsa_host_key: Option<String>,
    pub dsa_host_cert: Option<String>,
    pub ecdsa_host_key: Option<String>,
    pub ecdsa_host_cert: Option<String>,
    pub ed25519_host_key: Option<String>,
    pub ed25519_host_cert: Option<String>,

    pub base: PathBuf,
    pub image: PathBuf,
    pub image_size_gib: u64,
    pub memory_mib: u64,
    pub cpus: u64,
}

#[derive(Debug, thiserror::Error)]
pub enum SpecificationError {
    #[error("No base image or default base image specified: {0}")]
    NoBaseImage(PathBuf),

    #[error("Failed to read SSH public key file {0}")]
    SshKeyRead(PathBuf, #[source] std::io::Error),

    #[error(transparent)]
    IoError(#[from] std::io::Error),

    #[error(transparent)]
    FromUtf8Error(#[from] std::string::FromUtf8Error),

    #[error(transparent)]
    YamlError(#[from] serde_yaml::Error),
}

impl Specification {
    pub fn from_file(
        config: &Configuration,
        filename: &Path,
    ) -> Result<Specification, SpecificationError> {
        debug!("reading specification from {}", filename.display());
        let spec = fs::read(filename)?;
        let input: InputSpecification = serde_yaml::from_slice(&spec)?;
        debug!("specification as read from file: {:#?}", input);

        let base = if let Some(base) = input.base {
            base.to_path_buf()
        } else if let Some(ref base) = config.default_base_image {
            base.to_path_buf()
        } else {
            return Err(SpecificationError::NoBaseImage(filename.to_path_buf()));
        };

        let spec = Specification {
            name: input.name.clone(),
            ssh_keys: ssh_keys(&input.ssh_key_files)?,
            rsa_host_key: input.rsa_host_key,
            rsa_host_cert: input.rsa_host_cert,
            dsa_host_key: input.dsa_host_key,
            dsa_host_cert: input.dsa_host_cert,
            ecdsa_host_key: input.ecdsa_host_key,
            ecdsa_host_cert: input.ecdsa_host_cert,
            ed25519_host_key: input.ed25519_host_key,
            ed25519_host_cert: input.ed25519_host_cert,
            base,
            image: input.image.clone(),
            image_size_gib: input.image_size_gib,
            memory_mib: input.memory_mib,
            cpus: input.cpus,
        };

        debug!("specification as with defaults applied: {:#?}", spec);
        Ok(spec)
    }
}

fn ssh_keys(filenames: &[PathBuf]) -> Result<Vec<String>, SpecificationError> {
    let mut keys = vec![];
    for filename in filenames {
        let key = std::fs::read(filename)
            .map_err(|e| SpecificationError::SshKeyRead(filename.to_path_buf(), e))?;
        let key = String::from_utf8(key)?;
        let key = key.strip_suffix("\n").or(Some(&key)).unwrap();
        keys.push(key.to_string());
    }
    Ok(keys)
}