diff --git a/build_library/vm_image_util.sh b/build_library/vm_image_util.sh index 1b61232e6ba..4870327af20 100644 --- a/build_library/vm_image_util.sh +++ b/build_library/vm_image_util.sh @@ -24,6 +24,7 @@ VALID_IMG_TYPES=( openstack_mini packet parallels + proxmoxve pxe qemu_uefi rackspace @@ -55,6 +56,7 @@ VALID_OEM_PACKAGES=( kubevirt openstack packet + proxmoxve qemu rackspace rackspace-onmetal @@ -309,6 +311,13 @@ IMG_packet_OEM_PACKAGE=common-oem-files IMG_packet_OEM_SYSEXT=oem-packet IMG_packet_OEM_USE=packet +## proxmoxve +IMG_proxmoxve_DISK_FORMAT=qcow2 +IMG_proxmoxve_DISK_LAYOUT=vm +IMG_proxmoxve_OEM_PACKAGE=common-oem-files +IMG_proxmoxve_OEM_USE=proxmoxve +IMG_proxmoxve_OEM_SYSEXT=oem-proxmoxve + ## scaleway IMG_scaleway_DISK_FORMAT=qcow2 IMG_scaleway_DISK_LAYOUT=vm diff --git a/changelog/changes/2024-03-22-proxmoxve.md b/changelog/changes/2024-03-22-proxmoxve.md new file mode 100644 index 00000000000..2470ec3616a --- /dev/null +++ b/changelog/changes/2024-03-22-proxmoxve.md @@ -0,0 +1 @@ +- Added Proxmox Virtual Environment images ([scripts#1783](https://github.com/flatcar/scripts/pull/1783)) diff --git a/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/afterburn-5.5.1-r1.ebuild b/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/afterburn-5.5.1-r2.ebuild similarity index 100% rename from sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/afterburn-5.5.1-r1.ebuild rename to sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/afterburn-5.5.1-r2.ebuild diff --git a/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/afterburn-9999.ebuild b/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/afterburn-9999.ebuild index 0c85763b595..491f81b2ef3 100644 --- a/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/afterburn-9999.ebuild +++ b/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/afterburn-9999.ebuild @@ -322,6 +322,9 @@ PATCHES=( "${FILESDIR}"/0001-Revert-remove-cl-legacy-feature.patch "${FILESDIR}"/0002-util-cmdline-Handle-the-cmdline-flags-as-list-of-sup.patch "${FILESDIR}"/0003-cargo-reduce-binary-size-for-release-profile.patch + "${FILESDIR}"/0004-providers-support-for-proxmoxve.patch + "${FILESDIR}"/0005-proxmoxve-ignore-user-data-file-if-header-is-not-pre.patch + "${FILESDIR}"/0006-proxmoxve-Generate-proper-network-unit-for-the-DHCP-.patch ) src_unpack() { diff --git a/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/files/0004-providers-support-for-proxmoxve.patch b/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/files/0004-providers-support-for-proxmoxve.patch new file mode 100644 index 00000000000..543618a8194 --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/files/0004-providers-support-for-proxmoxve.patch @@ -0,0 +1,758 @@ +From 3b5d32200d192cd980dd4ada3329b601d249f745 Mon Sep 17 00:00:00 2001 +From: Arthur Chaloin +Date: Wed, 10 Jan 2024 14:13:39 +0000 +Subject: [PATCH 1/2] providers: support for proxmoxve + +--- + docs/platforms.md | 5 + + docs/release-notes.md | 2 + + docs/usage/attributes.md | 5 + + dracut/30afterburn/afterburn-hostname.service | 1 + + src/metadata.rs | 2 + + src/providers/mod.rs | 1 + + src/providers/proxmoxve/cloudconfig.rs | 247 ++++++++++++++++++ + src/providers/proxmoxve/configdrive.rs | 66 +++++ + src/providers/proxmoxve/mod.rs | 22 ++ + src/providers/proxmoxve/tests.rs | 143 ++++++++++ + systemd/afterburn-sshkeys@.service.in | 1 + + tests/fixtures/proxmoxve/dhcp/meta-data | 1 + + tests/fixtures/proxmoxve/dhcp/network-config | 13 + + tests/fixtures/proxmoxve/dhcp/user-data | 13 + + tests/fixtures/proxmoxve/dhcp/vendor-data | 0 + tests/fixtures/proxmoxve/static/meta-data | 1 + + .../fixtures/proxmoxve/static/network-config | 30 +++ + tests/fixtures/proxmoxve/static/user-data | 13 + + tests/fixtures/proxmoxve/static/vendor-data | 0 + 19 files changed, 566 insertions(+) + create mode 100644 src/providers/proxmoxve/cloudconfig.rs + create mode 100644 src/providers/proxmoxve/configdrive.rs + create mode 100644 src/providers/proxmoxve/mod.rs + create mode 100644 src/providers/proxmoxve/tests.rs + create mode 100644 tests/fixtures/proxmoxve/dhcp/meta-data + create mode 100644 tests/fixtures/proxmoxve/dhcp/network-config + create mode 100644 tests/fixtures/proxmoxve/dhcp/user-data + create mode 100644 tests/fixtures/proxmoxve/dhcp/vendor-data + create mode 100644 tests/fixtures/proxmoxve/static/meta-data + create mode 100644 tests/fixtures/proxmoxve/static/network-config + create mode 100644 tests/fixtures/proxmoxve/static/user-data + create mode 100644 tests/fixtures/proxmoxve/static/vendor-data + +diff --git a/docs/platforms.md b/docs/platforms.md +index d279573..53bee97 100644 +--- a/docs/platforms.md ++++ b/docs/platforms.md +@@ -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 +diff --git a/docs/release-notes.md b/docs/release-notes.md +index b488bfc..acee039 100644 +--- a/docs/release-notes.md ++++ b/docs/release-notes.md +@@ -8,6 +8,8 @@ nav_order: 8 + + Major changes: + ++- Add support for Proxmox VE ++ + Minor changes: + + Packaging changes: +diff --git a/docs/usage/attributes.md b/docs/usage/attributes.md +index f3868f9..d6d030d 100644 +--- a/docs/usage/attributes.md ++++ b/docs/usage/attributes.md +@@ -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 +diff --git a/dracut/30afterburn/afterburn-hostname.service b/dracut/30afterburn/afterburn-hostname.service +index 485cd82..268522b 100644 +--- a/dracut/30afterburn/afterburn-hostname.service ++++ b/dracut/30afterburn/afterburn-hostname.service +@@ -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 + +diff --git a/src/metadata.rs b/src/metadata.rs +index 0d93c84..bcc2a28 100644 +--- a/src/metadata.rs ++++ b/src/metadata.rs +@@ -34,6 +34,7 @@ use crate::providers::packet::PacketProvider; + use crate::providers::powervs::PowerVSProvider; + #[cfg(feature = "cl-legacy")] + use crate::providers::vagrant_virtualbox::VagrantVirtualboxProvider; ++use crate::providers::proxmoxve::ProxmoxVEConfigDrive; + use crate::providers::scaleway::ScalewayProvider; + use crate::providers::vmware::VmwareProvider; + use crate::providers::vultr::VultrProvider; +@@ -76,6 +77,7 @@ pub fn fetch_metadata(provider: &str) -> Result box_result!(PowerVSProvider::try_new()?), + #[cfg(feature = "cl-legacy")] + "vagrant-virtualbox" => box_result!(VagrantVirtualboxProvider::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()?), +diff --git a/src/providers/mod.rs b/src/providers/mod.rs +index 589ae46..1cd3305 100644 +--- a/src/providers/mod.rs ++++ b/src/providers/mod.rs +@@ -39,6 +39,7 @@ pub mod packet; + pub mod powervs; + #[cfg(feature = "cl-legacy")] + pub mod vagrant_virtualbox; ++pub mod proxmoxve; + pub mod scaleway; + pub mod vmware; + pub mod vultr; +diff --git a/src/providers/proxmoxve/cloudconfig.rs b/src/providers/proxmoxve/cloudconfig.rs +new file mode 100644 +index 0000000..3535e26 +--- /dev/null ++++ b/src/providers/proxmoxve/cloudconfig.rs +@@ -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, ++ pub package_upgrade: bool, ++ #[serde(default)] ++ pub ssh_authorized_keys: Vec, ++} ++ ++#[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, ++} ++ ++#[derive(Debug, Deserialize)] ++pub struct ProxmoxVECloudNetworkConfigEntry { ++ #[serde(rename = "type")] ++ pub network_type: String, ++ pub name: Option, ++ pub mac_address: Option, ++ #[serde(default)] ++ pub address: Vec, ++ #[serde(default)] ++ pub search: Vec, ++ #[serde(default)] ++ pub subnets: Vec, ++} ++ ++#[derive(Debug, Deserialize)] ++pub struct ProxmoxVECloudNetworkConfigSubnet { ++ #[serde(rename = "type")] ++ pub subnet_type: String, ++ pub address: Option, ++ pub netmask: Option, ++ pub gateway: Option, ++} ++ ++impl ProxmoxVECloudConfig { ++ pub fn try_new(path: &Path) -> Result { ++ 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> { ++ 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> { ++ Ok(Some(self.user_data.hostname.clone())) ++ } ++ ++ fn ssh_keys(&self) -> Result> { ++ Ok(self ++ .user_data ++ .ssh_authorized_keys ++ .iter() ++ .map(|key| PublicKey::from_str(key)) ++ .collect::, _>>()?) ++ } ++ ++ fn networks(&self) -> Result> { ++ let nameservers = self ++ .network_config ++ .config ++ .iter() ++ .filter(|config| config.network_type == "nameserver") ++ .collect::>(); ++ ++ 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::, _>>()?; ++ ++ 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::, AddrParseError>>()?; ++ } ++ } ++ ++ Ok(interfaces) ++ } ++} ++ ++impl ProxmoxVECloudNetworkConfigEntry { ++ pub fn to_interface(&self) -> Result { ++ 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) ++ } ++} +diff --git a/src/providers/proxmoxve/configdrive.rs b/src/providers/proxmoxve/configdrive.rs +new file mode 100644 +index 0000000..37f8e4c +--- /dev/null ++++ b/src/providers/proxmoxve/configdrive.rs +@@ -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 { ++ 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> { ++ self.config.attributes() ++ } ++ ++ fn hostname(&self) -> Result> { ++ self.config.hostname() ++ } ++ ++ fn ssh_keys(&self) -> Result> { ++ self.config.ssh_keys() ++ } ++ ++ fn networks(&self) -> Result> { ++ 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); ++ }; ++ } ++} +diff --git a/src/providers/proxmoxve/mod.rs b/src/providers/proxmoxve/mod.rs +new file mode 100644 +index 0000000..14146b0 +--- /dev/null ++++ b/src/providers/proxmoxve/mod.rs +@@ -0,0 +1,22 @@ ++// Copyright 2017 CoreOS, Inc. ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++ ++mod configdrive; ++pub use configdrive::*; ++ ++mod cloudconfig; ++pub use cloudconfig::*; ++ ++#[cfg(test)] ++mod tests; +diff --git a/src/providers/proxmoxve/tests.rs b/src/providers/proxmoxve/tests.rs +new file mode 100644 +index 0000000..060ee8d +--- /dev/null ++++ b/src/providers/proxmoxve/tests.rs +@@ -0,0 +1,143 @@ ++use super::ProxmoxVECloudConfig; ++use crate::{ ++ network::{self, NetworkRoute}, ++ providers::MetadataProvider, ++}; ++use ipnetwork::IpNetwork; ++use openssh_keys::PublicKey; ++use pnet_base::MacAddr; ++use std::{net::IpAddr, path::Path, str::FromStr}; ++ ++#[test] ++fn test_attributes() { ++ let config = ProxmoxVECloudConfig::try_new(Path::new("tests/fixtures/proxmoxve/static")) ++ .expect("cannot parse config"); ++ let attributes = config.attributes().expect("cannot get hostname"); ++ ++ assert_eq!(attributes["PROXMOXVE_HOSTNAME"], "dummy".to_string()); ++ ++ assert_eq!( ++ attributes["PROXMOXVE_INSTANCE_ID"], ++ "15a9919cb91024fbd1d70fa07f0efa749cbba03b".to_string() ++ ); ++ ++ assert_eq!(attributes["PROXMOXVE_IPV4"], "192.168.1.1".to_string()); ++ ++ assert_eq!( ++ attributes["PROXMOXVE_IPV6"], ++ "2001:db8:85a3::8a2e:370:0".to_string() ++ ); ++} ++ ++#[test] ++fn test_hostname() { ++ let config = ProxmoxVECloudConfig::try_new(Path::new("tests/fixtures/proxmoxve/dhcp")) ++ .expect("cannot parse config"); ++ ++ assert_eq!( ++ config.hostname().expect("cannot get hostname"), ++ Some("dummy".to_string()) ++ ); ++} ++ ++#[test] ++fn test_ssh_keys() { ++ let test_ssh_key = PublicKey::from_str("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDd1hElre4j44sbmULXyO5j6dRnkRFCMjEGtRSy2SuvFD8WyB5uectcEMvz7ORhQIVbPlz94wFjpSX5wl/gmSKL/7GOyerJo0Y2cvyjJJahuDn+JnIL0tT0HS1pJ5iJqQpxXeOAzMK5Heum+uGw9BzbiUHnRzjJr8Ltx4CAGMfubevD4SX32Q8BTQiaU4ZnGtdHo16pWwRsq1f6/UtL4gDCni9vm8QmmGDRloi/pBn1csjKw+volFyu/kSEmGLWow6NuT6TrhGAbMKas5HfYq0Mn3LGPZL7XjqJQ6CO0TzkG/BNplZT2tiwHtsvXsbePTp4ZUi4dkCMz2xR4eikaI1V dummy@dummy.local").unwrap(); ++ let config = ProxmoxVECloudConfig::try_new(Path::new("tests/fixtures/proxmoxve/dhcp")) ++ .expect("cannot parse config"); ++ ++ assert_eq!( ++ config.ssh_keys().expect("cannot get ssh keys"), ++ vec![test_ssh_key] ++ ); ++} ++ ++#[test] ++fn test_network_dhcp() { ++ let config = ProxmoxVECloudConfig::try_new(Path::new("tests/fixtures/proxmoxve/dhcp")) ++ .expect("cannot parse config"); ++ ++ assert_eq!( ++ config.networks().expect("cannot get networks"), ++ vec![network::Interface { ++ name: Some("eth0".to_owned()), ++ mac_address: Some(MacAddr::from_str("01:23:45:67:89:00").unwrap()), ++ path: None, ++ priority: 20, ++ nameservers: vec![ ++ IpAddr::from_str("1.1.1.1").unwrap(), ++ IpAddr::from_str("8.8.8.8").unwrap() ++ ], ++ ip_addresses: vec![], ++ routes: vec![], ++ bond: None, ++ unmanaged: false, ++ required_for_online: None ++ }] ++ ); ++} ++ ++#[test] ++fn test_network_static() { ++ let config = ProxmoxVECloudConfig::try_new(Path::new("tests/fixtures/proxmoxve/static")) ++ .expect("cannot parse config"); ++ ++ assert_eq!( ++ config.networks().expect("cannot get networks"), ++ vec![ ++ network::Interface { ++ name: Some("eth0".to_owned()), ++ mac_address: Some(MacAddr::from_str("01:23:45:67:89:00").unwrap()), ++ path: None, ++ priority: 20, ++ nameservers: vec![ ++ IpAddr::from_str("1.1.1.1").unwrap(), ++ IpAddr::from_str("8.8.8.8").unwrap() ++ ], ++ ip_addresses: vec![ ++ IpNetwork::from_str("192.168.1.1/24").unwrap(), ++ IpNetwork::from_str("2001:0db8:85a3:0000:0000:8a2e:0370:0/24").unwrap(), ++ ], ++ routes: vec![ ++ NetworkRoute { ++ destination: IpNetwork::from_str("0.0.0.0/0").unwrap(), ++ gateway: IpAddr::from_str("192.168.1.254").unwrap(), ++ }, ++ NetworkRoute { ++ destination: IpNetwork::from_str("::/0").unwrap(), ++ gateway: IpAddr::from_str("2001:0db8:85a3:0000:0000:8a2e:0370:9999") ++ .unwrap(), ++ }, ++ ], ++ bond: None, ++ unmanaged: false, ++ required_for_online: None ++ }, ++ network::Interface { ++ name: Some("eth1".to_owned()), ++ mac_address: Some(MacAddr::from_str("01:23:45:67:89:99").unwrap()), ++ path: None, ++ priority: 20, ++ nameservers: vec![], ++ ip_addresses: vec![ ++ IpNetwork::from_str("192.168.42.1/24").unwrap(), ++ IpNetwork::from_str("2001:0db8:85a3:0000:0000:8a2e:4242:0/24").unwrap(), ++ ], ++ routes: vec![ ++ NetworkRoute { ++ destination: IpNetwork::from_str("0.0.0.0/0").unwrap(), ++ gateway: IpAddr::from_str("192.168.42.254").unwrap(), ++ }, ++ NetworkRoute { ++ destination: IpNetwork::from_str("::/0").unwrap(), ++ gateway: IpAddr::from_str("2001:0db8:85a3:0000:0000:8a2e:4242:9999") ++ .unwrap(), ++ }, ++ ], ++ bond: None, ++ unmanaged: false, ++ required_for_online: None ++ }, ++ ] ++ ); ++} +diff --git a/systemd/afterburn-sshkeys@.service.in b/systemd/afterburn-sshkeys@.service.in +index 9e889fb..d8382dc 100644 +--- a/systemd/afterburn-sshkeys@.service.in ++++ b/systemd/afterburn-sshkeys@.service.in +@@ -15,6 +15,7 @@ ConditionKernelCommandLine=|ignition.platform.id=gcp + ConditionKernelCommandLine=|ignition.platform.id=hetzner + ConditionKernelCommandLine=|ignition.platform.id=ibmcloud + ConditionKernelCommandLine=|ignition.platform.id=openstack ++ConditionKernelCommandLine=|ignition.platform.id=proxmoxve + ConditionKernelCommandLine=|ignition.platform.id=scaleway + ConditionKernelCommandLine=|ignition.platform.id=packet + ConditionKernelCommandLine=|ignition.platform.id=powervs +diff --git a/tests/fixtures/proxmoxve/dhcp/meta-data b/tests/fixtures/proxmoxve/dhcp/meta-data +new file mode 100644 +index 0000000..bd5926b +--- /dev/null ++++ b/tests/fixtures/proxmoxve/dhcp/meta-data +@@ -0,0 +1 @@ ++instance-id: 15a9919cb91024fbd1d70fa07f0efa749cbba03b +diff --git a/tests/fixtures/proxmoxve/dhcp/network-config b/tests/fixtures/proxmoxve/dhcp/network-config +new file mode 100644 +index 0000000..4031d02 +--- /dev/null ++++ b/tests/fixtures/proxmoxve/dhcp/network-config +@@ -0,0 +1,13 @@ ++version: 1 ++config: ++ - type: physical ++ name: eth0 ++ mac_address: '01:23:45:67:89:00' ++ subnets: ++ - type: dhcp4 ++ - type: nameserver ++ address: ++ - '1.1.1.1' ++ - '8.8.8.8' ++ search: ++ - 'local.com' +diff --git a/tests/fixtures/proxmoxve/dhcp/user-data b/tests/fixtures/proxmoxve/dhcp/user-data +new file mode 100644 +index 0000000..e62a3e6 +--- /dev/null ++++ b/tests/fixtures/proxmoxve/dhcp/user-data +@@ -0,0 +1,13 @@ ++#cloud-config ++hostname: dummy ++manage_etc_hosts: true ++fqdn: dummy.local.com ++user: dummy-user ++password: $5$6LDowW6p$.RyFu8lVH7Cw3AB.pPS/K2lmB8IczVs99A7gbcUCLV2 ++ssh_authorized_keys: ++ - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDd1hElre4j44sbmULXyO5j6dRnkRFCMjEGtRSy2SuvFD8WyB5uectcEMvz7ORhQIVbPlz94wFjpSX5wl/gmSKL/7GOyerJo0Y2cvyjJJahuDn+JnIL0tT0HS1pJ5iJqQpxXeOAzMK5Heum+uGw9BzbiUHnRzjJr8Ltx4CAGMfubevD4SX32Q8BTQiaU4ZnGtdHo16pWwRsq1f6/UtL4gDCni9vm8QmmGDRloi/pBn1csjKw+volFyu/kSEmGLWow6NuT6TrhGAbMKas5HfYq0Mn3LGPZL7XjqJQ6CO0TzkG/BNplZT2tiwHtsvXsbePTp4ZUi4dkCMz2xR4eikaI1V dummy@dummy.local ++chpasswd: ++ expire: False ++users: ++ - default ++package_upgrade: true +diff --git a/tests/fixtures/proxmoxve/dhcp/vendor-data b/tests/fixtures/proxmoxve/dhcp/vendor-data +new file mode 100644 +index 0000000..e69de29 +diff --git a/tests/fixtures/proxmoxve/static/meta-data b/tests/fixtures/proxmoxve/static/meta-data +new file mode 100644 +index 0000000..bd5926b +--- /dev/null ++++ b/tests/fixtures/proxmoxve/static/meta-data +@@ -0,0 +1 @@ ++instance-id: 15a9919cb91024fbd1d70fa07f0efa749cbba03b +diff --git a/tests/fixtures/proxmoxve/static/network-config b/tests/fixtures/proxmoxve/static/network-config +new file mode 100644 +index 0000000..e708ca1 +--- /dev/null ++++ b/tests/fixtures/proxmoxve/static/network-config +@@ -0,0 +1,30 @@ ++version: 1 ++config: ++ - type: physical ++ name: eth0 ++ mac_address: '01:23:45:67:89:00' ++ subnets: ++ - type: static ++ address: '192.168.1.1' ++ netmask: '255.255.255.0' ++ gateway: '192.168.1.254' ++ - type: static6 ++ address: '2001:0db8:85a3:0000:0000:8a2e:0370:0/24' ++ gateway: '2001:0db8:85a3:0000:0000:8a2e:0370:9999' ++ - type: physical ++ name: eth1 ++ mac_address: '01:23:45:67:89:99' ++ subnets: ++ - type: static ++ address: '192.168.42.1' ++ netmask: '255.255.255.0' ++ gateway: '192.168.42.254' ++ - type: static6 ++ address: '2001:0db8:85a3:0000:0000:8a2e:4242:0/24' ++ gateway: '2001:0db8:85a3:0000:0000:8a2e:4242:9999' ++ - type: nameserver ++ address: ++ - '1.1.1.1' ++ - '8.8.8.8' ++ search: ++ - 'local.com' +diff --git a/tests/fixtures/proxmoxve/static/user-data b/tests/fixtures/proxmoxve/static/user-data +new file mode 100644 +index 0000000..e62a3e6 +--- /dev/null ++++ b/tests/fixtures/proxmoxve/static/user-data +@@ -0,0 +1,13 @@ ++#cloud-config ++hostname: dummy ++manage_etc_hosts: true ++fqdn: dummy.local.com ++user: dummy-user ++password: $5$6LDowW6p$.RyFu8lVH7Cw3AB.pPS/K2lmB8IczVs99A7gbcUCLV2 ++ssh_authorized_keys: ++ - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDd1hElre4j44sbmULXyO5j6dRnkRFCMjEGtRSy2SuvFD8WyB5uectcEMvz7ORhQIVbPlz94wFjpSX5wl/gmSKL/7GOyerJo0Y2cvyjJJahuDn+JnIL0tT0HS1pJ5iJqQpxXeOAzMK5Heum+uGw9BzbiUHnRzjJr8Ltx4CAGMfubevD4SX32Q8BTQiaU4ZnGtdHo16pWwRsq1f6/UtL4gDCni9vm8QmmGDRloi/pBn1csjKw+volFyu/kSEmGLWow6NuT6TrhGAbMKas5HfYq0Mn3LGPZL7XjqJQ6CO0TzkG/BNplZT2tiwHtsvXsbePTp4ZUi4dkCMz2xR4eikaI1V dummy@dummy.local ++chpasswd: ++ expire: False ++users: ++ - default ++package_upgrade: true +diff --git a/tests/fixtures/proxmoxve/static/vendor-data b/tests/fixtures/proxmoxve/static/vendor-data +new file mode 100644 +index 0000000..e69de29 +-- +2.45.0 + diff --git a/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/files/0005-proxmoxve-ignore-user-data-file-if-header-is-not-pre.patch b/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/files/0005-proxmoxve-ignore-user-data-file-if-header-is-not-pre.patch new file mode 100644 index 00000000000..19c974c7ac2 --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/files/0005-proxmoxve-ignore-user-data-file-if-header-is-not-pre.patch @@ -0,0 +1,175 @@ +From d14b1a7a912dc905eaf395fda461265b2754586b Mon Sep 17 00:00:00 2001 +From: Arthur Chaloin +Date: Tue, 16 Apr 2024 12:19:23 +0000 +Subject: [PATCH 2/2] proxmoxve: ignore user-data file if header is not present + +--- + src/providers/proxmoxve/cloudconfig.rs | 42 +++++++++++++++---- + src/providers/proxmoxve/tests.rs | 10 +++++ + .../proxmoxve/invalid-user-data/meta-data | 1 + + .../invalid-user-data/network-config | 30 +++++++++++++ + .../proxmoxve/invalid-user-data/user-data | 5 +++ + .../proxmoxve/invalid-user-data/vendor-data | 0 + 6 files changed, 79 insertions(+), 9 deletions(-) + create mode 100644 tests/fixtures/proxmoxve/invalid-user-data/meta-data + create mode 100644 tests/fixtures/proxmoxve/invalid-user-data/network-config + create mode 100644 tests/fixtures/proxmoxve/invalid-user-data/user-data + create mode 100644 tests/fixtures/proxmoxve/invalid-user-data/vendor-data + +diff --git a/src/providers/proxmoxve/cloudconfig.rs b/src/providers/proxmoxve/cloudconfig.rs +index 3535e26..a213e50 100644 +--- a/src/providers/proxmoxve/cloudconfig.rs ++++ b/src/providers/proxmoxve/cloudconfig.rs +@@ -11,6 +11,7 @@ use slog_scope::warn; + use std::{ + collections::HashMap, + fs::File, ++ io::{BufRead, BufReader}, + net::{AddrParseError, IpAddr}, + path::Path, + str::FromStr, +@@ -19,7 +20,7 @@ use std::{ + #[derive(Debug)] + pub struct ProxmoxVECloudConfig { + pub meta_data: ProxmoxVECloudMetaData, +- pub user_data: ProxmoxVECloudUserData, ++ pub user_data: Option, + pub vendor_data: ProxmoxVECloudVendorData, + pub network_config: ProxmoxVECloudNetworkConfig, + } +@@ -81,9 +82,26 @@ pub struct ProxmoxVECloudNetworkConfigSubnet { + + impl ProxmoxVECloudConfig { + pub fn try_new(path: &Path) -> Result { ++ let user_data_file = BufReader::new(File::open(path.join("user-data"))?); ++ let mut user_data = None; ++ ++ if let Some(first_line) = user_data_file.lines().next() { ++ if let Ok(first_line) = first_line { ++ if first_line.starts_with("#cloud-config") { ++ user_data = serde_yaml::from_reader(File::open(path.join("user-data"))?)?; ++ } ++ } ++ } ++ ++ if user_data.is_none() { ++ warn!( ++ "user-data does not have the expected header `#cloud-config`, ignoring this file" ++ ); ++ } ++ + Ok(Self { ++ user_data, + 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"))?)?, + }) +@@ -119,16 +137,22 @@ impl MetadataProvider for ProxmoxVECloudConfig { + } + + fn hostname(&self) -> Result> { +- Ok(Some(self.user_data.hostname.clone())) ++ Ok(self ++ .user_data ++ .as_ref() ++ .map(|user_data| user_data.hostname.clone())) + } + + fn ssh_keys(&self) -> Result> { +- Ok(self +- .user_data +- .ssh_authorized_keys +- .iter() +- .map(|key| PublicKey::from_str(key)) +- .collect::, _>>()?) ++ if let Some(user_data) = &self.user_data { ++ return Ok(user_data ++ .ssh_authorized_keys ++ .iter() ++ .map(|key| PublicKey::from_str(key)) ++ .collect::, _>>()?); ++ } ++ ++ Ok(vec![]) + } + + fn networks(&self) -> Result> { +diff --git a/src/providers/proxmoxve/tests.rs b/src/providers/proxmoxve/tests.rs +index 060ee8d..ddef64b 100644 +--- a/src/providers/proxmoxve/tests.rs ++++ b/src/providers/proxmoxve/tests.rs +@@ -141,3 +141,13 @@ fn test_network_static() { + ] + ); + } ++ ++#[test] ++fn test_invalid_user_data() { ++ let config = ++ ProxmoxVECloudConfig::try_new(Path::new("tests/fixtures/proxmoxve/invalid-user-data")) ++ .expect("cannot parse config"); ++ ++ assert_eq!(config.hostname().unwrap().is_none(), true); ++ assert_eq!(config.ssh_keys().unwrap(), vec![]); ++} +diff --git a/tests/fixtures/proxmoxve/invalid-user-data/meta-data b/tests/fixtures/proxmoxve/invalid-user-data/meta-data +new file mode 100644 +index 0000000..bd5926b +--- /dev/null ++++ b/tests/fixtures/proxmoxve/invalid-user-data/meta-data +@@ -0,0 +1 @@ ++instance-id: 15a9919cb91024fbd1d70fa07f0efa749cbba03b +diff --git a/tests/fixtures/proxmoxve/invalid-user-data/network-config b/tests/fixtures/proxmoxve/invalid-user-data/network-config +new file mode 100644 +index 0000000..e708ca1 +--- /dev/null ++++ b/tests/fixtures/proxmoxve/invalid-user-data/network-config +@@ -0,0 +1,30 @@ ++version: 1 ++config: ++ - type: physical ++ name: eth0 ++ mac_address: '01:23:45:67:89:00' ++ subnets: ++ - type: static ++ address: '192.168.1.1' ++ netmask: '255.255.255.0' ++ gateway: '192.168.1.254' ++ - type: static6 ++ address: '2001:0db8:85a3:0000:0000:8a2e:0370:0/24' ++ gateway: '2001:0db8:85a3:0000:0000:8a2e:0370:9999' ++ - type: physical ++ name: eth1 ++ mac_address: '01:23:45:67:89:99' ++ subnets: ++ - type: static ++ address: '192.168.42.1' ++ netmask: '255.255.255.0' ++ gateway: '192.168.42.254' ++ - type: static6 ++ address: '2001:0db8:85a3:0000:0000:8a2e:4242:0/24' ++ gateway: '2001:0db8:85a3:0000:0000:8a2e:4242:9999' ++ - type: nameserver ++ address: ++ - '1.1.1.1' ++ - '8.8.8.8' ++ search: ++ - 'local.com' +diff --git a/tests/fixtures/proxmoxve/invalid-user-data/user-data b/tests/fixtures/proxmoxve/invalid-user-data/user-data +new file mode 100644 +index 0000000..c1baeda +--- /dev/null ++++ b/tests/fixtures/proxmoxve/invalid-user-data/user-data +@@ -0,0 +1,5 @@ ++{ ++ "some": { ++ "ignition": "config" ++ } ++} +diff --git a/tests/fixtures/proxmoxve/invalid-user-data/vendor-data b/tests/fixtures/proxmoxve/invalid-user-data/vendor-data +new file mode 100644 +index 0000000..e69de29 +-- +2.45.0 + diff --git a/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/files/0006-proxmoxve-Generate-proper-network-unit-for-the-DHCP-.patch b/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/files/0006-proxmoxve-Generate-proper-network-unit-for-the-DHCP-.patch new file mode 100644 index 00000000000..efe53e40d2a --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/files/0006-proxmoxve-Generate-proper-network-unit-for-the-DHCP-.patch @@ -0,0 +1,305 @@ +From 57150985ad6fc47d03ccec1eec377377859267fb Mon Sep 17 00:00:00 2001 +From: Kai Lueke +Date: Mon, 6 May 2024 17:20:17 +0900 +Subject: [PATCH] proxmoxve: Generate proper network unit for the DHCP case + +When DHCP is not specified in a systemd network unit, the default is +"no". For a "type: dhcp4" entry in the Proxmox VE network-config file +the systemd network unit should set DHCP=ipv4, and similar for "dhcp6". +--- + src/network.rs | 62 ++++++++++++++++++++++++++ + src/providers/digitalocean/mod.rs | 1 + + src/providers/ibmcloud_classic/mod.rs | 1 + + src/providers/packet/mod.rs | 3 ++ + src/providers/proxmoxve/cloudconfig.rs | 10 ++++- + src/providers/proxmoxve/tests.rs | 5 ++- + 6 files changed, 80 insertions(+), 2 deletions(-) + +diff --git a/src/network.rs b/src/network.rs +index 7795735..26c0a6f 100644 +--- a/src/network.rs ++++ b/src/network.rs +@@ -79,6 +79,8 @@ pub struct Interface { + pub priority: u8, + pub nameservers: Vec, + pub ip_addresses: Vec, ++ // Optionally enable DHCP ++ pub dhcp: Option, + pub routes: Vec, + pub bond: Option, + pub unmanaged: bool, +@@ -128,6 +130,31 @@ impl NetDevKind { + } + } + ++/// Optional use of DHCP. ++#[allow(dead_code)] ++#[derive(Clone, Debug, PartialEq, Eq)] ++pub enum DhcpSetting { ++ Both, ++ V4, ++ V6, ++} ++ ++impl DhcpSetting { ++ /// Return DHCP setting according to `systemd.network` ++ /// ++ /// See [systemd documentation](dhcp) for the full list. ++ /// ++ /// dhcp: https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#DHCP= ++ fn sd_dhcp_setting(&self) -> String { ++ let setting = match *self { ++ DhcpSetting::Both => "yes", ++ DhcpSetting::V4 => "ipv4", ++ DhcpSetting::V6 => "ipv6", ++ }; ++ setting.to_string() ++ } ++} ++ + impl Interface { + /// Return a deterministic `systemd.network` unit name for this device. + pub fn sd_network_unit_name(&self) -> Result { +@@ -158,6 +185,9 @@ impl Interface { + + // [Network] section + writeln!(config, "\n[Network]").unwrap(); ++ if let Some(dhcp) = &self.dhcp { ++ writeln!(config, "DHCP={}", dhcp.sd_dhcp_setting()).unwrap(); ++ } + for ns in &self.nameservers { + writeln!(config, "DNS={ns}").unwrap() + } +@@ -246,6 +276,7 @@ mod tests { + priority: 20, + nameservers: vec![], + ip_addresses: vec![], ++ dhcp: None, + routes: vec![], + bond: None, + unmanaged: false, +@@ -261,6 +292,7 @@ mod tests { + priority: 10, + nameservers: vec![], + ip_addresses: vec![], ++ dhcp: None, + routes: vec![], + bond: None, + unmanaged: false, +@@ -276,6 +308,7 @@ mod tests { + priority: 20, + nameservers: vec![], + ip_addresses: vec![], ++ dhcp: None, + routes: vec![], + bond: None, + unmanaged: false, +@@ -291,6 +324,7 @@ mod tests { + priority: 20, + nameservers: vec![], + ip_addresses: vec![], ++ dhcp: None, + routes: vec![], + bond: None, + unmanaged: false, +@@ -306,6 +340,7 @@ mod tests { + priority: 20, + nameservers: vec![], + ip_addresses: vec![], ++ dhcp: None, + routes: vec![], + bond: None, + unmanaged: false, +@@ -330,6 +365,7 @@ mod tests { + priority: 20, + nameservers: vec![], + ip_addresses: vec![], ++ dhcp: None, + routes: vec![], + bond: None, + unmanaged: false, +@@ -387,6 +423,7 @@ mod tests { + Ipv6Network::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 128).unwrap(), + ), + ], ++ dhcp: None, + routes: vec![NetworkRoute { + destination: IpNetwork::V4( + Ipv4Network::new(Ipv4Addr::new(127, 0, 0, 1), 8).unwrap(), +@@ -428,6 +465,7 @@ Gateway=127.0.0.1 + priority: 10, + nameservers: vec![], + ip_addresses: vec![], ++ dhcp: None, + routes: vec![], + bond: None, + unmanaged: false, +@@ -447,6 +485,7 @@ Gateway=127.0.0.1 + priority: 10, + nameservers: vec![], + ip_addresses: vec![], ++ dhcp: None, + routes: vec![], + bond: None, + unmanaged: false, +@@ -470,6 +509,7 @@ RequiredForOnline=no + priority: 10, + nameservers: vec![], + ip_addresses: vec![], ++ dhcp: None, + routes: vec![], + bond: None, + unmanaged: true, +@@ -482,6 +522,28 @@ Name=* + + [Link] + Unmanaged=yes ++", ++ ), ++ // test the DHCP setting ++ ( ++ Interface { ++ name: Some("*".to_owned()), ++ mac_address: None, ++ path: None, ++ priority: 10, ++ nameservers: vec![], ++ ip_addresses: vec![], ++ dhcp: Some(DhcpSetting::V4), ++ routes: vec![], ++ bond: None, ++ unmanaged: false, ++ required_for_online: None, ++ }, ++ "[Match] ++Name=* ++ ++[Network] ++DHCP=ipv4 + ", + ), + ]; +diff --git a/src/providers/digitalocean/mod.rs b/src/providers/digitalocean/mod.rs +index 2583696..dd817dc 100644 +--- a/src/providers/digitalocean/mod.rs ++++ b/src/providers/digitalocean/mod.rs +@@ -156,6 +156,7 @@ impl DigitalOceanProvider { + mac_address: Some(mac), + nameservers: self.dns.nameservers.clone(), + ip_addresses: addrs, ++ dhcp: None, + routes, + bond: None, + name: None, +diff --git a/src/providers/ibmcloud_classic/mod.rs b/src/providers/ibmcloud_classic/mod.rs +index 6546f08..d210aca 100644 +--- a/src/providers/ibmcloud_classic/mod.rs ++++ b/src/providers/ibmcloud_classic/mod.rs +@@ -247,6 +247,7 @@ impl IBMClassicProvider { + priority: 10, + nameservers: nameservers.clone(), + ip_addresses: vec![ip_net], ++ dhcp: None, + routes, + bond: None, + unmanaged: false, +diff --git a/src/providers/packet/mod.rs b/src/providers/packet/mod.rs +index 314c642..4a8ec35 100644 +--- a/src/providers/packet/mod.rs ++++ b/src/providers/packet/mod.rs +@@ -216,6 +216,7 @@ impl PacketProvider { + priority: 10, + nameservers: Vec::new(), + ip_addresses: Vec::new(), ++ dhcp: None, + routes: Vec::new(), + // the interface should be unmanaged if it doesn't have a bond + // section +@@ -241,6 +242,7 @@ impl PacketProvider { + path: None, + bond: None, + ip_addresses: Vec::new(), ++ dhcp: None, + routes: Vec::new(), + unmanaged: false, + required_for_online: Some("degraded-carrier".to_owned()), +@@ -334,6 +336,7 @@ impl PacketProvider { + bond: None, + nameservers: Vec::new(), + ip_addresses: Vec::new(), ++ dhcp: None, + routes: Vec::new(), + required_for_online: None, + }; +diff --git a/src/providers/proxmoxve/cloudconfig.rs b/src/providers/proxmoxve/cloudconfig.rs +index a213e50..50bc42c 100644 +--- a/src/providers/proxmoxve/cloudconfig.rs ++++ b/src/providers/proxmoxve/cloudconfig.rs +@@ -1,5 +1,5 @@ + use crate::{ +- network::{self, NetworkRoute}, ++ network::{self, DhcpSetting, NetworkRoute}, + providers::MetadataProvider, + }; + use anyhow::Result; +@@ -207,6 +207,8 @@ impl ProxmoxVECloudNetworkConfigEntry { + ip_addresses: vec![], + // filled below + routes: vec![], ++ // filled below ++ dhcp: None, + // filled below because Option::try_map doesn't exist yet + mac_address: None, + +@@ -257,6 +259,12 @@ impl ProxmoxVECloudNetworkConfigEntry { + } + } + ++ if subnet.subnet_type == "dhcp" || subnet.subnet_type == "dhcp4" { ++ iface.dhcp = Some(DhcpSetting::V4) ++ } ++ if subnet.subnet_type == "dhcp6" { ++ iface.dhcp = Some(DhcpSetting::V6) ++ } + if subnet.subnet_type == "ipv6_slaac" { + warn!("subnet type \"ipv6_slaac\" not supported, ignoring"); + } +diff --git a/src/providers/proxmoxve/tests.rs b/src/providers/proxmoxve/tests.rs +index ddef64b..e4171fe 100644 +--- a/src/providers/proxmoxve/tests.rs ++++ b/src/providers/proxmoxve/tests.rs +@@ -1,6 +1,6 @@ + use super::ProxmoxVECloudConfig; + use crate::{ +- network::{self, NetworkRoute}, ++ network::{self, DhcpSetting, NetworkRoute}, + providers::MetadataProvider, + }; + use ipnetwork::IpNetwork; +@@ -64,6 +64,7 @@ fn test_network_dhcp() { + mac_address: Some(MacAddr::from_str("01:23:45:67:89:00").unwrap()), + path: None, + priority: 20, ++ dhcp: Some(DhcpSetting::V4), + nameservers: vec![ + IpAddr::from_str("1.1.1.1").unwrap(), + IpAddr::from_str("8.8.8.8").unwrap() +@@ -98,6 +99,7 @@ fn test_network_static() { + IpNetwork::from_str("192.168.1.1/24").unwrap(), + IpNetwork::from_str("2001:0db8:85a3:0000:0000:8a2e:0370:0/24").unwrap(), + ], ++ dhcp: None, + routes: vec![ + NetworkRoute { + destination: IpNetwork::from_str("0.0.0.0/0").unwrap(), +@@ -123,6 +125,7 @@ fn test_network_static() { + IpNetwork::from_str("192.168.42.1/24").unwrap(), + IpNetwork::from_str("2001:0db8:85a3:0000:0000:8a2e:4242:0/24").unwrap(), + ], ++ dhcp: None, + routes: vec![ + NetworkRoute { + destination: IpNetwork::from_str("0.0.0.0/0").unwrap(), +-- +2.45.0 + diff --git a/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/files/coreos-metadata.service b/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/files/coreos-metadata.service index 439a48bf1a6..45a46679982 100644 --- a/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/files/coreos-metadata.service +++ b/sdk_container/src/third_party/coreos-overlay/coreos-base/afterburn/files/coreos-metadata.service @@ -23,6 +23,8 @@ ConditionKernelCommandLine=|flatcar.oem.id=hetzner ConditionKernelCommandLine=|flatcar.oem.id=kubevirt +ConditionKernelCommandLine=|flatcar.oem.id=proxmoxve + Description=Flatcar Metadata Agent [Service] diff --git a/sdk_container/src/third_party/coreos-overlay/coreos-base/common-oem-files/common-oem-files-0-r8.ebuild b/sdk_container/src/third_party/coreos-overlay/coreos-base/common-oem-files/common-oem-files-0-r8.ebuild index 705942df0de..f0857414b97 100644 --- a/sdk_container/src/third_party/coreos-overlay/coreos-base/common-oem-files/common-oem-files-0-r8.ebuild +++ b/sdk_container/src/third_party/coreos-overlay/coreos-base/common-oem-files/common-oem-files-0-r8.ebuild @@ -34,6 +34,7 @@ COMMON_OEMIDS=( hetzner openstack packet + proxmoxve qemu scaleway kubevirt diff --git a/sdk_container/src/third_party/coreos-overlay/coreos-base/coreos-init/coreos-init-9999.ebuild b/sdk_container/src/third_party/coreos-overlay/coreos-base/coreos-init/coreos-init-9999.ebuild index b211353ed4d..c85be5c290c 100644 --- a/sdk_container/src/third_party/coreos-overlay/coreos-base/coreos-init/coreos-init-9999.ebuild +++ b/sdk_container/src/third_party/coreos-overlay/coreos-base/coreos-init/coreos-init-9999.ebuild @@ -10,7 +10,7 @@ CROS_WORKON_REPO="https://github.com" if [[ "${PV}" == 9999 ]]; then KEYWORDS="~amd64 ~arm ~arm64 ~x86" else - CROS_WORKON_COMMIT="96e929df6c04df58a87187cf2a769923e5ecdf41" # flatcar-master + CROS_WORKON_COMMIT="4470ae36049eebfd0ffadcac5f1fc6d27c3e319d" # TODO: flatcar-master KEYWORDS="amd64 arm arm64 x86" fi diff --git a/sdk_container/src/third_party/coreos-overlay/coreos-base/oem-proxmoxve/metadata.xml b/sdk_container/src/third_party/coreos-overlay/coreos-base/oem-proxmoxve/metadata.xml new file mode 100644 index 00000000000..097975e3adc --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/coreos-base/oem-proxmoxve/metadata.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sdk_container/src/third_party/coreos-overlay/coreos-base/oem-proxmoxve/oem-proxmoxve-0.ebuild b/sdk_container/src/third_party/coreos-overlay/coreos-base/oem-proxmoxve/oem-proxmoxve-0.ebuild new file mode 100644 index 00000000000..bb7fa6e0629 --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/coreos-base/oem-proxmoxve/oem-proxmoxve-0.ebuild @@ -0,0 +1,15 @@ +# Copyright (c) 2013 CoreOS, Inc.. All rights reserved. +# Distributed under the terms of the GNU General Public License v2 + +EAPI=8 + +DESCRIPTION="OEM suite for Proxmox VE" +HOMEPAGE="https://www.proxmox.com/en/proxmox-virtual-environment/" +SRC_URI="" + +LICENSE="GPL-2" +SLOT="0" +KEYWORDS="amd64 arm64" +IUSE="" + +OEM_NAME="Proxmox VE" diff --git a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0022-feat-proxmoxve-initial-commit.patch b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0022-feat-proxmoxve-initial-commit.patch new file mode 100644 index 00000000000..83114ee6aa3 --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0022-feat-proxmoxve-initial-commit.patch @@ -0,0 +1,163 @@ +From ff14a0da1251e3f5e23f9210382fe8caf645154e Mon Sep 17 00:00:00 2001 +From: bri <284789+b-@users.noreply.github.com> +Date: Fri, 19 Jan 2024 20:15:38 -0500 +Subject: [PATCH] feat(proxmoxve): initial commit + +Signed-off-by: Mathieu Tortuyaux +--- + internal/providers/proxmoxve/proxmoxve.go | 130 ++++++++++++++++++++++ + internal/register/providers.go | 1 + + 2 files changed, 131 insertions(+) + create mode 100644 internal/providers/proxmoxve/proxmoxve.go + +diff --git a/internal/providers/proxmoxve/proxmoxve.go b/internal/providers/proxmoxve/proxmoxve.go +new file mode 100644 +index 00000000..802894d9 +--- /dev/null ++++ b/internal/providers/proxmoxve/proxmoxve.go +@@ -0,0 +1,130 @@ ++// Copyright 2019 Red Hat, Inc. ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++ ++// The OpenStack provider fetches configurations from the userdata available in ++// both the config-drive as well as the network metadata service. Whichever ++// responds first is the config that is used. ++// NOTE: This provider is still EXPERIMENTAL. ++ ++package proxmoxve ++ ++import ( ++ "context" ++ "fmt" ++ "os" ++ "os/exec" ++ "path/filepath" ++ "time" ++ ++ "github.com/flatcar/ignition/v2/config/v3_5_experimental/types" ++ "github.com/flatcar/ignition/v2/internal/distro" ++ "github.com/flatcar/ignition/v2/internal/log" ++ "github.com/flatcar/ignition/v2/internal/platform" ++ "github.com/flatcar/ignition/v2/internal/providers/util" ++ "github.com/flatcar/ignition/v2/internal/resource" ++ ut "github.com/flatcar/ignition/v2/internal/util" ++ ++ "github.com/coreos/vcontext/report" ++) ++ ++const ( ++ cidataPath = "/user-data" ++ deviceLabel = "cidata" ++) ++ ++func init() { ++ platform.Register( ++ platform.Provider{ ++ Name: "proxmoxve", ++ Fetch: fetchConfig, ++ }, ++ ) ++} ++ ++func fetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) { ++ var data []byte ++ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ++ ++ dispatch := func(name string, fn func() ([]byte, error)) { ++ raw, err := fn() ++ if err != nil { ++ switch err { ++ case context.Canceled: ++ case context.DeadlineExceeded: ++ f.Logger.Err("timed out while fetching config from %s", name) ++ default: ++ f.Logger.Err("failed to fetch config from %s: %v", name, err) ++ } ++ return ++ } ++ ++ data = raw ++ cancel() ++ } ++ ++ go dispatch( ++ "config drive (cidata)", func() ([]byte, error) { ++ return fetchConfigFromDevice(f.Logger, ctx, filepath.Join(distro.DiskByLabelDir(), deviceLabel)) ++ }, ++ ) ++ ++ <-ctx.Done() ++ if ctx.Err() == context.DeadlineExceeded { ++ f.Logger.Info("cidata drive was not available in time. Continuing without a config...") ++ } ++ ++ return util.ParseConfig(f.Logger, data) ++} ++ ++func fileExists(path string) bool { ++ _, err := os.Stat(path) ++ return (err == nil) ++} ++ ++func fetchConfigFromDevice(logger *log.Logger, ctx context.Context, path string) ([]byte, error) { ++ for !fileExists(path) { ++ logger.Debug("config drive (%q) not found. Waiting...", path) ++ select { ++ case <-time.After(time.Second): ++ case <-ctx.Done(): ++ return nil, ctx.Err() ++ } ++ } ++ ++ logger.Debug("creating temporary mount point") ++ mnt, err := os.MkdirTemp("", "ignition-configdrive") ++ if err != nil { ++ return nil, fmt.Errorf("failed to create temp directory: %v", err) ++ } ++ defer os.Remove(mnt) ++ ++ cmd := exec.Command(distro.MountCmd(), "-o", "ro", "-t", "auto", path, mnt) ++ if _, err := logger.LogCmd(cmd, "mounting config drive"); err != nil { ++ return nil, err ++ } ++ defer func() { ++ _ = logger.LogOp( ++ func() error { ++ return ut.UmountPath(mnt) ++ }, ++ "unmounting %q at %q", path, mnt, ++ ) ++ }() ++ ++ if !fileExists(filepath.Join(mnt, cidataPath)) { ++ return nil, nil ++ } ++ ++ return os.ReadFile(filepath.Join(mnt, cidataPath)) ++} +diff --git a/internal/register/providers.go b/internal/register/providers.go +index 372ee32d..b971897c 100644 +--- a/internal/register/providers.go ++++ b/internal/register/providers.go +@@ -34,6 +34,7 @@ import ( + _ "github.com/flatcar/ignition/v2/internal/providers/openstack" + _ "github.com/flatcar/ignition/v2/internal/providers/packet" + _ "github.com/flatcar/ignition/v2/internal/providers/powervs" ++ _ "github.com/flatcar/ignition/v2/internal/providers/proxmoxve" + _ "github.com/flatcar/ignition/v2/internal/providers/qemu" + _ "github.com/flatcar/ignition/v2/internal/providers/scaleway" + _ "github.com/flatcar/ignition/v2/internal/providers/virtualbox" +-- +2.43.2 + diff --git a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-9999.ebuild b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-9999.ebuild index 491e48d419c..f78c32e0ad7 100644 --- a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-9999.ebuild +++ b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-9999.ebuild @@ -63,6 +63,7 @@ PATCHES=( "${FILESDIR}/0019-usr-share-oem-oem.patch" "${FILESDIR}/0020-internal-exec-stages-mount-Mount-oem.patch" "${FILESDIR}/0021-sgdisk-Run-partprobe-after-partition-changes.patch" + "${FILESDIR}/0022-feat-proxmoxve-initial-commit.patch" ) src_compile() { diff --git a/sdk_container/src/third_party/coreos-overlay/sys-kernel/bootengine/bootengine-9999.ebuild b/sdk_container/src/third_party/coreos-overlay/sys-kernel/bootengine/bootengine-9999.ebuild index cf8cb1db549..289bb052d94 100644 --- a/sdk_container/src/third_party/coreos-overlay/sys-kernel/bootengine/bootengine-9999.ebuild +++ b/sdk_container/src/third_party/coreos-overlay/sys-kernel/bootengine/bootengine-9999.ebuild @@ -10,7 +10,7 @@ CROS_WORKON_REPO="https://github.com" if [[ "${PV}" == 9999 ]]; then KEYWORDS="~amd64 ~arm ~arm64 ~x86" else - CROS_WORKON_COMMIT="8da532c809c89a9c434ada0fa9532a1c1bf49f4c" # flatcar-master + CROS_WORKON_COMMIT="700e2174a190c31f07f4dd50366da05b04d087f2" # TODO: flatcar-master KEYWORDS="amd64 arm arm64 x86" fi