summaryrefslogtreecommitdiff
path: root/src/install.rs
blob: 473885567eb0f0a84eb12f3991713b42a49fd9a1 (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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//! Create a new VM.
//!
//! This module runs the `virt-install` tool to create a new VM, using
//! an existing image file. It attaches the cloud-init ISO
//! configuration image to the VM.

use crate::cloudinit::{CloudInitConfig, CloudInitError};
use crate::image::VirtualMachineImage;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::result::Result;

/// Errors from this module
#[derive(Debug, thiserror::Error)]
pub enum VirtInstallError {
    /// Failed to create VM.
    #[error("virt-install failed: {0}")]
    VirtInstallFailed(String),

    /// Failed to run virt-install.
    #[error("couldn't run virt-install")]
    Run(#[source] std::io::Error),

    /// No networks defined.
    #[error("no networks defined for {0}")]
    NoNetworks(String),

    /// Error parsing a string as UTF8.
    #[error(transparent)]
    StringError(#[from] std::string::FromUtf8Error),

    /// Error from cloud-init configuration.
    #[error(transparent)]
    CloudInitError(#[from] CloudInitError),
}

/// Arguments to virt-install.
///
/// These are the arguments we can adjust, for running virt-install.
#[derive(Debug)]
pub struct VirtInstallArgs {
    name: String,
    memory: u64,
    vcpus: u64,
    image: VirtualMachineImage,
    init: CloudInitConfig,
    networks: Vec<String>,
}

impl VirtInstallArgs {
    /// Create new set of arguments for virt-install.
    pub fn new(name: &str, image: &VirtualMachineImage, init: &CloudInitConfig) -> Self {
        Self {
            name: name.to_string(),
            memory: 1024,
            vcpus: 1,
            image: image.clone(),
            init: init.clone(),
            networks: vec![],
        }
    }

    /// Name for new VM.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Memory for new VM, in MiB.
    pub fn memory(&self) -> u64 {
        self.memory
    }

    /// Change memory to give to new VM, in MiB.
    pub fn set_memory(&mut self, memory: u64) {
        self.memory = memory
    }

    /// Virtual CPUs for new VM.
    pub fn vcpus(&self) -> u64 {
        self.vcpus
    }

    /// Change virtual CPUs for new VM.
    pub fn set_vcpus(&mut self, vcpus: u64) {
        self.vcpus = vcpus
    }

    /// Image for new VM.
    pub fn image(&self) -> &VirtualMachineImage {
        &self.image
    }

    /// cloud-init configuration for new VM.
    pub fn init(&self) -> &CloudInitConfig {
        &self.init
    }

    /// Add another network to add to the VM.
    pub fn add_network(&mut self, network: &str) {
        self.networks.push(network.to_string());
    }

    /// Return list of networks to add to the VM.
    pub fn networks(&self) -> Vec<String> {
        self.networks.clone()
    }
}

/// Create new VM with virt-install.
pub fn virt_install(args: &VirtInstallArgs, iso: &Path) -> Result<PathBuf, VirtInstallError> {
    let networks: Vec<String> = args
        .networks
        .iter()
        .map(|s| format!("--network={}", s))
        .collect();

    args.init().create_iso(iso)?;

    let r = Command::new("virt-install")
        .arg("--name")
        .arg(args.name())
        .arg("--memory")
        .arg(format!("{}", args.memory()))
        .arg("--vcpus")
        .arg(format!("{}", args.vcpus()))
        .arg(format!(
            "--disk=path={},cache=none",
            args.image().filename().display()
        ))
        .arg(format!("--disk=path={},readonly=on", iso.display()))
        .arg("--connect=qemu::///system")
        .arg("--connect")
        .arg("qemu:///system")
        .arg("--cpu=host-passthrough")
        .arg("--os-variant=debian9")
        .arg("--import")
        .arg("--graphics=spice")
        .arg("--noautoconsole")
        .arg("--quiet")
        .args(&networks)
        .output()
        .map_err(VirtInstallError::Run)?;
    if !r.status.success() {
        let stderr = String::from_utf8(r.stderr)?;
        return Err(VirtInstallError::VirtInstallFailed(stderr));
    }
    Ok(iso.to_path_buf())
}