Attempt to implement and test setting static ips from instance metadata

This commit is contained in:
Kaare Hoff Skovgaard 2025-07-07 00:06:55 +02:00
parent dd1cfa79e7
commit 47dbb7cdd3
Signed by: khs
GPG key ID: C7D890804F01E9F0
16 changed files with 258 additions and 59 deletions

View file

@ -1,28 +0,0 @@
use clap::{Parser, Subcommand};
fn main() {
common::entrypoint(program);
}
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct Args {
#[command(subcommand)]
pub command: Commands,
}
#[derive(Debug, Subcommand)]
pub enum Commands {
/// Configures the ipv6 address using instance metadata and iproute2
Configure,
}
fn program() -> anyhow::Result<()> {
let args = Args::parse();
match args.command {
Commands::Configure => configure(),
}
}
fn configure() -> anyhow::Result<()> {
Ok(())
}

View file

@ -1,8 +1,8 @@
[package]
name = "hetzner-ipv6"
name = "hetzner-static-ip"
edition = "2024"
version = "1.0.0"
metadata.crane.name = "hetzner-ipv6"
metadata.crane.name = "hetzner-static-ip"
[dependencies]
anyhow = { workspace = true }

View file

@ -0,0 +1,71 @@
use anyhow::Context as _;
use clap::{Parser, Subcommand};
use crate::metadata::Instance;
mod metadata;
fn main() {
common::entrypoint(program);
}
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct Args {
#[command(subcommand)]
pub command: Commands,
}
#[derive(Debug, Subcommand)]
pub enum Commands {
/// Configures the ipv6 address using instance metadata and iproute2
Configure,
}
fn program() -> anyhow::Result<()> {
let args = Args::parse();
match args.command {
Commands::Configure => configure(),
}
}
fn configure() -> anyhow::Result<()> {
let metadata_api = common::env::read_env("INSTANCE_API_URI")
.unwrap_or(String::from("http://169.254.169.254/hetzner/v1/metadata"));
let metadata = common::curl::read_text_as_string(&metadata_api)?;
let metadata: Instance = common::yaml::from_str(&metadata)
.context("Could not parse instance metadata into expected format")?;
for m in metadata.network_config.config {
for subnet in m.subnets {
match subnet {
metadata::InstanceNetworkConfigConfigSubnet::Static {
ipv6,
ipv4,
address,
gateway,
} => {
let mut cmd = common::proc::Command::new("ip");
if ipv6.is_some_and(|v| v) {
cmd.arg("-6");
}
if ipv4.is_some_and(|v| v) {
cmd.arg("-4");
}
cmd.args(["addr", "add", &address, "dev", &m.name]);
cmd.try_spawn_to_string()?;
let mut cmd = common::proc::Command::new("ip");
if ipv6.is_some_and(|v| v) {
cmd.arg("-6");
}
if ipv4.is_some_and(|v| v) {
cmd.arg("-4");
}
cmd.args(["route", "add", "default", "via", &gateway, "dev", &m.name]);
cmd.try_spawn_to_string()?;
}
metadata::InstanceNetworkConfigConfigSubnet::Dhcp {} => continue,
}
}
}
Ok(())
}

View file

@ -0,0 +1,32 @@
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Instance {
#[serde(rename = "network-config")]
pub network_config: InstanceNetworkConfig,
}
#[derive(Debug, Deserialize)]
pub struct InstanceNetworkConfig {
pub config: Vec<InstanceNetworkConfigConfig>,
}
#[derive(Debug, Deserialize)]
pub struct InstanceNetworkConfigConfig {
pub name: String,
pub subnets: Vec<InstanceNetworkConfigConfigSubnet>,
}
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
pub enum InstanceNetworkConfigConfigSubnet {
#[serde(rename = "static")]
Static {
ipv6: Option<bool>,
ipv4: Option<bool>,
address: String,
gateway: String,
},
#[serde(rename = "dhcp")]
Dhcp {},
}