Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxmoxve: Support static network configuration from cloud-init #1165

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ nav_order: 8

Major changes:

- ProxmoxVE: Add support for static IP configuration from cloud-init

Minor changes:

- ProxmoxVE: Fixed instance boot without config drive
Expand Down Expand Up @@ -160,8 +162,8 @@ Changes:
- providers/gcp: access GCP metadata service by IP address
- providers/packet: access metadata service over HTTPS
- cli: don't report an error when --help or --version is specified
- cli: correctly print version when --version specified
- providers: Add PowerVS
- cli: correctly print version when --version specified
- providers: Add PowerVS
- workflows: bump toolchains; restrict repository access


Expand All @@ -173,7 +175,7 @@ Changes:
- cargo: update all dependencies
- *: remove cl-legacy feature
- ibmcloud: don't ignore I/O error when parsing metadata
- providers: fix clippy::unnecessary_wraps lint on 1.50
- providers: fix clippy::unnecessary_wraps lint on 1.50
- workflows: update pinned lint toolchain to 1.50.0
- *: switch from `error-chain` to `anyhow`
- cli: stop wrapping command-line parse errors
Expand Down Expand Up @@ -266,7 +268,7 @@ Changes:

- sshkeys: send structured info to journald
- ci: test a secondary arch on Travis
- ci: rust version from 1.39.0 to 1.40.0
- ci: rust version from 1.39.0 to 1.40.0
- makefile: tweak install step
- providers: add vmware
- util/cmdline: add helpers for detecting network kargs
Expand All @@ -278,7 +280,7 @@ Changes:

Changes:

- cargo: relax dependencies micro versions
- cargo: relax dependencies micro versions
- cargo: switch from deprecated tempdir crate to tempfile
- providers: add exoscale
- providers: add ibmcloud-classic as a separate platform
Expand Down Expand Up @@ -361,7 +363,7 @@ Changes:

Changes:

- providers/azure: fetch hostname from metadata
- providers/azure: fetch hostname from metadata
- add checkin service files for Azure and Packet
- metadata: accept "ec2" provider name only in legacy mode
- bump minimum toolchain to 1.31
Expand All @@ -384,7 +386,7 @@ Bugfixes:

- providers/gce: fix panic fetching metadata

Misc:
Misc:
- providers/gce: add basic hostname mock-test
- rustfmt whole project

Expand Down Expand Up @@ -491,4 +493,4 @@ and behavior for all providers should be identical to the previous golang
version. If it's not, please file a bug in our bug tracker,
https://github.com/coreos/bugs (or submit a pr!).

Additionally, `coreos-metadata` now supports ssh keys for azure.
Additionally, `coreos-metadata` now supports ssh keys for azure.
2 changes: 2 additions & 0 deletions src/initrd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! services are configured, so it may not be able to use all usual metadata
//! fetcher.

use crate::providers::proxmoxve::ProxmoxVEConfigDrive;
use crate::providers::vmware::VmwareProvider;
use crate::providers::MetadataProvider;
use anyhow::{Context, Result};
Expand All @@ -17,6 +18,7 @@ static KARGS_PATH: &str = "/etc/cmdline.d/50-afterburn-network-kargs.conf";
pub(crate) fn fetch_network_kargs(provider: &str) -> Result<Option<String>> {
match provider {
"vmware" => VmwareProvider::try_new()?.rd_network_kargs(),
"proxmoxve" => ProxmoxVEConfigDrive::try_new()?.rd_network_kargs(),
_ => Ok(None),
}
}
Expand Down
173 changes: 154 additions & 19 deletions src/providers/proxmoxve/cloudconfig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
network::{self, DhcpSetting, NetworkRoute},
providers::MetadataProvider,
};
use anyhow::Result;
use anyhow::{Context, Result};
use ipnetwork::IpNetwork;
use openssh_keys::PublicKey;
use pnet_base::MacAddr;
Expand All @@ -20,6 +20,7 @@ use std::{
pub struct ProxmoxVECloudConfig {
pub meta_data: ProxmoxVECloudMetaData,
pub user_data: Option<ProxmoxVECloudUserData>,
#[allow(dead_code)]
pub vendor_data: ProxmoxVECloudVendorData,
pub network_config: ProxmoxVECloudNetworkConfig,
}
Expand All @@ -33,26 +34,15 @@ pub struct ProxmoxVECloudMetaData {
#[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>,
}

Expand All @@ -65,8 +55,6 @@ pub struct ProxmoxVECloudNetworkConfigEntry {
#[serde(default)]
pub address: Vec<String>,
#[serde(default)]
pub search: Vec<String>,
#[serde(default)]
pub subnets: Vec<ProxmoxVECloudNetworkConfigSubnet>,
}

Expand All @@ -82,11 +70,13 @@ pub struct ProxmoxVECloudNetworkConfigSubnet {
impl ProxmoxVECloudConfig {
pub fn try_new(path: &Path) -> Result<Self> {
let mut user_data = None;
let raw_user_data = std::fs::read_to_string(path.join("user-data"))?;
let raw_user_data = std::fs::read_to_string(path.join("user-data"))
.context("failed to read user-data file")?;

if let Some(first_line) = raw_user_data.split('\n').next() {
if first_line.starts_with("#cloud-config") {
user_data = serde_yaml::from_str(&raw_user_data)?;
user_data = serde_yaml::from_str(&raw_user_data)
.context("failed to parse user-data as YAML")?;
}
}

Expand All @@ -98,9 +88,19 @@ impl ProxmoxVECloudConfig {

Ok(Self {
user_data,
meta_data: serde_yaml::from_reader(File::open(path.join("meta-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"))?)?,
meta_data: serde_yaml::from_reader(
File::open(path.join("meta-data")).context("failed to open meta-data file")?,
)
.context("failed to parse meta-data as YAML")?,
vendor_data: serde_yaml::from_reader(
File::open(path.join("vendor-data")).context("failed to open vendor-data file")?,
)
.context("failed to parse vendor-data as YAML")?,
network_config: serde_yaml::from_reader(
File::open(path.join("network-config"))
.context("failed to open network-config file")?,
)
.context("failed to parse network-config as YAML")?,
})
}
}
Expand Down Expand Up @@ -183,6 +183,141 @@ impl MetadataProvider for ProxmoxVECloudConfig {

Ok(interfaces)
}

fn rd_network_kargs(&self) -> Result<Option<String>> {
let mut kargs = Vec::new();

if let Ok(networks) = self.networks() {
for iface in networks {
// Add IP configuration if static
for addr in iface.ip_addresses {
match addr {
IpNetwork::V4(network) => {
if let Some(gateway) = iface
.routes
.iter()
.find(|r| r.destination.is_ipv4() && r.destination.prefix() == 0)
{
kargs.push(format!(
"ip={}::{}:{}",
network.ip(),
gateway.gateway,
network.mask()
));
} else {
kargs.push(format!("ip={}:::{}", network.ip(), network.mask()));
}
}
IpNetwork::V6(network) => {
if let Some(gateway) = iface
.routes
.iter()
.find(|r| r.destination.is_ipv6() && r.destination.prefix() == 0)
{
kargs.push(format!(
"ip={}::{}:{}",
network.ip(),
gateway.gateway,
network.prefix()
));
} else {
kargs.push(format!("ip={}:::{}", network.ip(), network.prefix()));
}
}
}
}

// Add DHCP configuration
if let Some(dhcp) = iface.dhcp {
match dhcp {
DhcpSetting::V4 => kargs.push("ip=dhcp".to_string()),
DhcpSetting::V6 => kargs.push("ip=dhcp6".to_string()),
DhcpSetting::Both => kargs.push("ip=dhcp,dhcp6".to_string()),
}
}

// Add nameservers
if !iface.nameservers.is_empty() {
let nameservers = iface
.nameservers
.iter()
.map(|ns| ns.to_string())
.collect::<Vec<_>>()
.join(",");
kargs.push(format!("nameserver={}", nameservers));
}
}
}

if kargs.is_empty() {
Ok(None)
} else {
Ok(Some(kargs.join(" ")))
}
}

fn netplan_config(&self) -> Result<Option<String>> {
// Convert network config to netplan format
if let Ok(networks) = self.networks() {
let mut netplan = serde_yaml::Mapping::new();
let mut network = serde_yaml::Mapping::new();
let mut ethernets = serde_yaml::Mapping::new();

for iface in networks {
let mut eth_config = serde_yaml::Mapping::new();

// Add DHCP settings
if let Some(dhcp) = iface.dhcp {
match dhcp {
DhcpSetting::V4 => {
eth_config.insert("dhcp4".into(), true.into());
}
DhcpSetting::V6 => {
eth_config.insert("dhcp6".into(), true.into());
}
DhcpSetting::Both => {
eth_config.insert("dhcp4".into(), true.into());
eth_config.insert("dhcp6".into(), true.into());
}
}
}

// Add static addresses if any
if !iface.ip_addresses.is_empty() {
let addresses: Vec<String> = iface
.ip_addresses
.iter()
.map(|addr| addr.to_string())
.collect();
eth_config.insert("addresses".into(), addresses.into());
}

// Add nameservers if any
if !iface.nameservers.is_empty() {
let nameservers: Vec<String> =
iface.nameservers.iter().map(|ns| ns.to_string()).collect();
eth_config.insert(
"nameservers".into(),
serde_yaml::Value::Mapping(serde_yaml::Mapping::from_iter(vec![(
"addresses".into(),
nameservers.into(),
)])),
);
}

if let Some(name) = iface.name {
ethernets.insert(name.into(), eth_config.into());
}
}

network.insert("ethernets".into(), ethernets.into());
netplan.insert("network".into(), network.into());

Ok(Some(serde_yaml::to_string(&netplan)?))
} else {
Ok(None)
}
}
}

impl ProxmoxVECloudNetworkConfigEntry {
Expand Down
41 changes: 30 additions & 11 deletions src/providers/proxmoxve/configdrive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{network, providers::MetadataProvider};
use anyhow::{Context, Result};
use openssh_keys::PublicKey;
use slog_scope::error;
use std::{collections::HashMap, path::Path};
use std::{collections::HashMap, path::Path, process::Command};
use tempfile::TempDir;

const CONFIG_DRIVE_LABEL: &str = "cidata";
Expand All @@ -16,23 +16,34 @@ pub struct ProxmoxVEConfigDrive {
}

impl ProxmoxVEConfigDrive {
fn find_cidata_device() -> Option<String> {
let output = Command::new("blkid")
.args(["--cache-file", "/dev/null", "-L", CONFIG_DRIVE_LABEL])
.output()
.ok()?;

if !output.status.success() {
return None;
}

Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
}

pub fn try_new() -> Result<Self> {
let mount_dir = 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),
mount_dir.path(),
TARGET_FS,
3,
)?;
let device_path = Self::find_cidata_device()
.ok_or_else(|| anyhow::anyhow!("could not find cidata device"))?;

crate::util::mount_ro(Path::new(&device_path), mount_dir.path(), TARGET_FS, 3)?;

Ok(Self {
config: ProxmoxVECloudConfig::try_new(mount_dir.path())?,
mount_dir,
})
let config = ProxmoxVECloudConfig::try_new(mount_dir.path())
.context("failed to read ProxmoxVE cloud config")?;

Ok(Self { config, mount_dir })
}
}

Expand All @@ -52,6 +63,14 @@ impl MetadataProvider for ProxmoxVEConfigDrive {
fn networks(&self) -> Result<Vec<network::Interface>> {
self.config.networks()
}

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

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

impl Drop for ProxmoxVEConfigDrive {
Expand Down
Loading