Skip to content

Commit

Permalink
providers: support for proxmoxve
Browse files Browse the repository at this point in the history
  • Loading branch information
arcln committed Jan 10, 2024
1 parent b9d9448 commit 7449d1c
Show file tree
Hide file tree
Showing 19 changed files with 565 additions and 0 deletions.
5 changes: 5 additions & 0 deletions docs/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ The following platforms are supported, with a different set of features availabl
* powervs
- Attributes
- SSH keys
* proxmoxve
- Attributes
- Hostname
- SSH keys
- Network configuration
* scaleway
- Attributes
- Boot check-in
Expand Down
1 change: 1 addition & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ nav_order: 8

Major changes:

- Add support for Proxmox VE

Minor changes:

Expand Down
5 changes: 5 additions & 0 deletions docs/usage/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ Cloud providers with supported metadata endpoints and their respective attribute
* powervs
- AFTERBURN_POWERVS_INSTANCE_ID
- AFTERBURN_POWERVS_LOCAL_HOSTNAME
* proxmoxve
- AFTERBURN_PROXMOXVE_HOSTNAME
- AFTERBURN_PROXMOXVE_INSTANCE_ID
- AFTERBURN_PROXMOXVE_IPV4
- AFTERBURN_PROXMOXVE_IPV6
* scaleway
- AFTERBURN_SCALEWAY_HOSTNAME
- AFTERBURN_SCALEWAY_INSTANCE_ID
Expand Down
1 change: 1 addition & 0 deletions dracut/30afterburn/afterburn-hostname.service
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ ConditionKernelCommandLine=|ignition.platform.id=exoscale
ConditionKernelCommandLine=|ignition.platform.id=hetzner
ConditionKernelCommandLine=|ignition.platform.id=ibmcloud
ConditionKernelCommandLine=|ignition.platform.id=kubevirt
ConditionKernelCommandLine=|ignition.platform.id=proxmoxve
ConditionKernelCommandLine=|ignition.platform.id=scaleway
ConditionKernelCommandLine=|ignition.platform.id=vultr

Expand Down
2 changes: 2 additions & 0 deletions src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use crate::providers::openstack;
use crate::providers::openstack::network::OpenstackProviderNetwork;
use crate::providers::packet::PacketProvider;
use crate::providers::powervs::PowerVSProvider;
use crate::providers::proxmoxve::ProxmoxVEConfigDrive;
use crate::providers::scaleway::ScalewayProvider;
use crate::providers::vmware::VmwareProvider;
use crate::providers::vultr::VultrProvider;
Expand Down Expand Up @@ -68,6 +69,7 @@ pub fn fetch_metadata(provider: &str) -> Result<Box<dyn providers::MetadataProvi
"openstack-metadata" => box_result!(OpenstackProviderNetwork::try_new()?),
"packet" => box_result!(PacketProvider::try_new()?),
"powervs" => box_result!(PowerVSProvider::try_new()?),
"proxmoxve" => box_result!(ProxmoxVEConfigDrive::try_new()?),
"scaleway" => box_result!(ScalewayProvider::try_new()?),
"vmware" => box_result!(VmwareProvider::try_new()?),
"vultr" => box_result!(VultrProvider::try_new()?),
Expand Down
1 change: 1 addition & 0 deletions src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub mod microsoft;
pub mod openstack;
pub mod packet;
pub mod powervs;
pub mod proxmoxve;
pub mod scaleway;
pub mod vmware;
pub mod vultr;
Expand Down
247 changes: 247 additions & 0 deletions src/providers/proxmoxve/cloudconfig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
use crate::{
network::{self, NetworkRoute},
providers::MetadataProvider,
};
use anyhow::Result;
use ipnetwork::IpNetwork;
use openssh_keys::PublicKey;
use pnet_base::MacAddr;
use serde::Deserialize;
use slog_scope::warn;
use std::{
collections::HashMap,
fs::File,
net::{AddrParseError, IpAddr},
path::Path,
str::FromStr,
};

#[derive(Debug)]
pub struct ProxmoxVECloudConfig {
pub meta_data: ProxmoxVECloudMetaData,
pub user_data: ProxmoxVECloudUserData,
pub vendor_data: ProxmoxVECloudVendorData,
pub network_config: ProxmoxVECloudNetworkConfig,
}

#[derive(Debug, Deserialize)]
pub struct ProxmoxVECloudMetaData {
#[serde(rename = "instance-id")]
pub instance_id: String,
}

#[derive(Debug, Deserialize)]
pub struct ProxmoxVECloudUserData {
pub hostname: String,
pub manage_etc_hosts: bool,
pub fqdn: String,
pub chpasswd: ProxmoxVECloudChpasswdConfig,
pub users: Vec<String>,
pub package_upgrade: bool,
#[serde(default)]
pub ssh_authorized_keys: Vec<String>,
}

#[derive(Debug, Deserialize)]
pub struct ProxmoxVECloudChpasswdConfig {
pub expire: bool,
}

#[derive(Debug, Deserialize)]
pub struct ProxmoxVECloudVendorData {}

#[derive(Debug, Deserialize)]
pub struct ProxmoxVECloudNetworkConfig {
pub version: u32,
pub config: Vec<ProxmoxVECloudNetworkConfigEntry>,
}

#[derive(Debug, Deserialize)]
pub struct ProxmoxVECloudNetworkConfigEntry {
#[serde(rename = "type")]
pub network_type: String,
pub name: Option<String>,
pub mac_address: Option<String>,
#[serde(default)]
pub address: Vec<String>,
#[serde(default)]
pub search: Vec<String>,
#[serde(default)]
pub subnets: Vec<ProxmoxVECloudNetworkConfigSubnet>,
}

#[derive(Debug, Deserialize)]
pub struct ProxmoxVECloudNetworkConfigSubnet {
#[serde(rename = "type")]
pub subnet_type: String,
pub address: Option<String>,
pub netmask: Option<String>,
pub gateway: Option<String>,
}

impl ProxmoxVECloudConfig {
pub fn try_new(path: &Path) -> Result<Self> {
Ok(Self {
meta_data: serde_yaml::from_reader(File::open(path.join("meta-data"))?)?,
user_data: serde_yaml::from_reader(File::open(path.join("user-data"))?)?,
vendor_data: serde_yaml::from_reader(File::open(path.join("vendor-data"))?)?,
network_config: serde_yaml::from_reader(File::open(path.join("network-config"))?)?,
})
}
}

impl MetadataProvider for ProxmoxVECloudConfig {
fn attributes(&self) -> Result<HashMap<String, String>> {
let mut out = HashMap::new();

out.insert(
"PROXMOXVE_HOSTNAME".to_owned(),
self.hostname()?.unwrap_or_default(),
);

out.insert(
"PROXMOXVE_INSTANCE_ID".to_owned(),
self.meta_data.instance_id.clone(),
);

if let Some(first_interface) = self.networks()?.first() {
first_interface.ip_addresses.iter().for_each(|ip| match ip {
IpNetwork::V4(network) => {
out.insert("PROXMOXVE_IPV4".to_owned(), network.ip().to_string());
}
IpNetwork::V6(network) => {
out.insert("PROXMOXVE_IPV6".to_owned(), network.ip().to_string());
}
});
}

Ok(out)
}

fn hostname(&self) -> Result<Option<String>> {
Ok(Some(self.user_data.hostname.clone()))
}

fn ssh_keys(&self) -> Result<Vec<PublicKey>> {
Ok(self
.user_data
.ssh_authorized_keys
.iter()
.map(|key| PublicKey::from_str(key))
.collect::<Result<Vec<_>, _>>()?)
}

fn networks(&self) -> Result<Vec<network::Interface>> {
let nameservers = self
.network_config
.config
.iter()
.filter(|config| config.network_type == "nameserver")
.collect::<Vec<_>>();

if nameservers.len() > 1 {
return Err(anyhow::anyhow!("too many nameservers, only one supported"));
}

let mut interfaces = self
.network_config
.config
.iter()
.filter(|config| config.network_type == "physical")
.map(|entry| entry.to_interface())
.collect::<Result<Vec<_>, _>>()?;

if let Some(iface) = interfaces.first_mut() {
if let Some(nameserver) = nameservers.first() {
iface.nameservers = nameserver
.address
.iter()
.map(|ip| IpAddr::from_str(ip))
.collect::<Result<Vec<IpAddr>, AddrParseError>>()?;
}
}

Ok(interfaces)
}
}

impl ProxmoxVECloudNetworkConfigEntry {
pub fn to_interface(&self) -> Result<network::Interface> {
if self.network_type != "physical" {
return Err(anyhow::anyhow!(
"cannot convert config to interface: unsupported config type \"{}\"",
self.network_type
));
}

let mut iface = network::Interface {
name: self.name.clone(),

// filled later
nameservers: vec![],
// filled below
ip_addresses: vec![],
// filled below
routes: vec![],
// filled below because Option::try_map doesn't exist yet
mac_address: None,

// unsupported by proxmox ve
bond: None,

// default values
path: None,
priority: 20,
unmanaged: false,
required_for_online: None,
};

for subnet in &self.subnets {
if subnet.subnet_type.contains("static") {
if subnet.address.is_none() {
return Err(anyhow::anyhow!(
"cannot convert static subnet to interface: missing address"
));
}

if let Some(netmask) = &subnet.netmask {
iface.ip_addresses.push(IpNetwork::with_netmask(
IpAddr::from_str(subnet.address.as_ref().unwrap())?,
IpAddr::from_str(netmask)?,
)?);
} else {
iface
.ip_addresses
.push(IpNetwork::from_str(subnet.address.as_ref().unwrap())?);
}

if let Some(gateway) = &subnet.gateway {
let gateway = IpAddr::from_str(gateway)?;

let destination = if gateway.is_ipv6() {
IpNetwork::from_str("::/0")?
} else {
IpNetwork::from_str("0.0.0.0/0")?
};

iface.routes.push(NetworkRoute {
destination,
gateway,
});
} else {
warn!("found subnet type \"static\" without gateway");
}
}

if subnet.subnet_type == "ipv6_slaac" {
warn!("subnet type \"ipv6_slaac\" not supported, ignoring");
}
}

if let Some(mac) = &self.mac_address {
iface.mac_address = Some(MacAddr::from_str(mac)?);
}

Ok(iface)
}
}
66 changes: 66 additions & 0 deletions src/providers/proxmoxve/configdrive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use super::ProxmoxVECloudConfig;
use crate::{network, providers::MetadataProvider};
use anyhow::{Context, Result};
use openssh_keys::PublicKey;
use slog_scope::error;
use std::{
collections::HashMap,
path::{Path, PathBuf},
};

#[derive(Debug)]
pub struct ProxmoxVEConfigDrive {
mount_path: PathBuf,
config: ProxmoxVECloudConfig,
}

impl ProxmoxVEConfigDrive {
pub fn try_new() -> Result<Self> {
const CONFIG_DRIVE_LABEL: &str = "cidata";
const TARGET_FS: &str = "iso9660";

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),
target.path(),
TARGET_FS,
3,
)?;

let mount_path = target.path().to_owned();
Ok(Self {
config: ProxmoxVECloudConfig::try_new(&mount_path)?,
mount_path,
})
}
}

impl MetadataProvider for ProxmoxVEConfigDrive {
fn attributes(&self) -> Result<HashMap<String, String>> {
self.config.attributes()
}

fn hostname(&self) -> Result<Option<String>> {
self.config.hostname()
}

fn ssh_keys(&self) -> Result<Vec<PublicKey>> {
self.config.ssh_keys()
}

fn networks(&self) -> Result<Vec<network::Interface>> {
self.config.networks()
}
}

impl Drop for ProxmoxVEConfigDrive {
fn drop(&mut self) {
if let Err(e) = crate::util::unmount(&self.mount_path, 3) {
error!("failed to cleanup Proxmox VE config-drive: {:?}", e);
};
}
}
Loading

0 comments on commit 7449d1c

Please sign in to comment.