-
Notifications
You must be signed in to change notification settings - Fork 110
/
Copy pathconfigdrive.rs
139 lines (116 loc) · 4.21 KB
/
configdrive.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! configdrive metadata fetcher for cloudstack
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use openssh_keys::PublicKey;
use slog_scope::error;
use tempfile::TempDir;
use crate::providers::MetadataProvider;
const CONFIG_DRIVE_LABEL_1: &str = "config-2";
const CONFIG_DRIVE_LABEL_2: &str = "CONFIG-2";
/// CloudStack config-drive.
#[derive(Debug)]
pub struct ConfigDrive {
/// Path to the top directory of the mounted config-drive.
drive_path: PathBuf,
/// Temporary directory for own mountpoint (if any).
temp_dir: Option<TempDir>,
}
impl ConfigDrive {
/// Try to build a new provider client.
///
/// This internally tries to mount (and own) the config-drive.
pub fn try_new() -> Result<Self> {
// Short-circuit if the config-drive is already mounted.
let path = Path::new("/media/ConfigDrive/cloudstack/metadata/");
if path.exists() {
return Ok(ConfigDrive {
temp_dir: None,
drive_path: PathBuf::from("/media/ConfigDrive/"),
});
}
// Otherwise, try and mount with each of the labels.
let target = tempfile::Builder::new()
.prefix("afterburn-")
.tempdir()
.context("failed to create temporary directory")?;
crate::util::mount_ro(
&Path::new("/dev/disk/by-label/").join(CONFIG_DRIVE_LABEL_1),
target.path(),
"iso9660",
3,
)
.or_else(|_| {
crate::util::mount_ro(
&Path::new("/dev/disk/by-label/").join(CONFIG_DRIVE_LABEL_2),
target.path(),
"iso9660",
3,
)
})?;
let cd = ConfigDrive {
drive_path: target.path().to_owned(),
temp_dir: Some(target),
};
Ok(cd)
}
/// Return the path to the metadata directory.
fn metadata_dir(&self) -> PathBuf {
self.drive_path.clone().join("cloudstack").join("metadata")
}
fn fetch_value(&self, key: &str) -> Result<Option<String>> {
let filename = self.metadata_dir().join(format!("{key}.txt"));
if !filename.exists() {
return Ok(None);
}
let mut file =
File::open(&filename).with_context(|| format!("failed to open file '{filename:?}'"))?;
let mut contents = String::new();
file.read_to_string(&mut contents)
.with_context(|| format!("failed to read from file '{filename:?}'"))?;
Ok(Some(contents))
}
fn fetch_publickeys(&self) -> Result<Vec<PublicKey>> {
let filename = self.metadata_dir().join("public_keys.txt");
let file =
File::open(&filename).with_context(|| format!("failed to open file '{filename:?}'"))?;
PublicKey::read_keys(file).context("failed to read public keys from config drive file")
}
}
impl MetadataProvider for ConfigDrive {
fn attributes(&self) -> Result<HashMap<String, String>> {
let mut out = HashMap::with_capacity(6);
let add_value = |map: &mut HashMap<_, _>, key: &str, name| -> Result<()> {
let value = self.fetch_value(name)?;
if let Some(value) = value {
map.insert(key.to_string(), value);
}
Ok(())
};
add_value(
&mut out,
"CLOUDSTACK_AVAILABILITY_ZONE",
"availability_zone",
)?;
add_value(&mut out, "CLOUDSTACK_CLOUD_IDENTIFIER", "cloud_identifier")?;
add_value(&mut out, "CLOUDSTACK_INSTANCE_ID", "instance_id")?;
add_value(&mut out, "CLOUDSTACK_LOCAL_HOSTNAME", "local_hostname")?;
add_value(&mut out, "CLOUDSTACK_SERVICE_OFFERING", "service_offering")?;
add_value(&mut out, "CLOUDSTACK_VM_ID", "vm_id")?;
Ok(out)
}
fn ssh_keys(&self) -> Result<Vec<PublicKey>> {
self.fetch_publickeys()
}
}
impl Drop for ConfigDrive {
fn drop(&mut self) {
if self.temp_dir.is_some() {
if let Err(e) = crate::util::unmount(&self.metadata_dir(), 3) {
error!("failed to cleanup CloudStack config-drive: {}", e);
};
}
}
}