Skip to content

Commit

Permalink
providers: setup network kernel arguments in initrd
Browse files Browse the repository at this point in the history
This adds initial/experimental support for providing supplemental
network kernel arguments in the initrd.
It is meant to support environment where custom network kargs may
be provided via a back-channel.
Such arguments are only retrieved and applied on first boot, and
their format is defined by dracut [0].
The feature is currently reachable as a dedicated subcommand,
but this does not contain any platform-specific implementation.

[0] https://mirrors.edge.kernel.org/pub/linux/utils/boot/dracut/dracut.html#_network
  • Loading branch information
lucab committed Apr 20, 2020
1 parent 3ac4ff6 commit f78f03a
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 79 deletions.
18 changes: 18 additions & 0 deletions dracut/30afterburn/afterburn-network-kargs.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[Unit]
Description=Afterburn Initrd Setup Network Kernel Arguments

# This service may produce additional kargs fragments,
# which are then consumed by dracut-cmdline(8).
DefaultDependencies=no
Before=dracut-cmdline.service
Before=ignition-fetch.service
After=systemd-journald.socket

OnFailure=emergency.target
OnFailureJobMode=isolate

[Service]
Environment=AFTERBURN_OPT_DEFAULT_VALUE=--default-value=ip=dhcp,dhcp6
Environment=AFTERBURN_OPT_PROVIDER=--cmdline
ExecStart=/usr/bin/afterburn exp rd-network-kargs ${AFTERBURN_OPT_PROVIDER} ${AFTERBURN_OPT_DEFAULT_VALUE}
Type=oneshot
7 changes: 5 additions & 2 deletions dracut/30afterburn/module-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ install() {
inst_simple "$moddir/afterburn-hostname.service" \
"$systemdutildir/system/afterburn-hostname.service"

# We want the afterburn-hostname to be firstboot only, so Ignition-provided
# hostname changes do not get overwritten on subsequent boots
inst_simple "$moddir/afterburn-network-kargs.service" \
"$systemdutildir/system/afterburn-network-kargs.service"

# These services are only run once on first-boot, so they piggyback
# on Ignition completion target.
mkdir -p "$initdir/$systemdsystemunitdir/ignition-complete.target.requires"
ln -s "../afterburn-hostname.service" "$initdir/$systemdsystemunitdir/ignition-complete.target.requires/afterburn-hostname.service"
ln -s "../afterburn-network-kargs.service" "$initdir/$systemdsystemunitdir/ignition-complete.target.requires/afterburn-network-kargs.service"
}
77 changes: 77 additions & 0 deletions src/cli/exp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! `exp` CLI sub-command.
use crate::errors::*;
use crate::{initrd, util};
use clap::ArgMatches;
use error_chain::bail;

/// Experimental subcommands.
#[derive(Debug)]
pub enum CliExp {
RdNetworkKargs(CliRdNetworkKargs),
}

impl CliExp {
/// Parse sub-command into configuration.
pub(crate) fn parse(app_matches: &ArgMatches) -> Result<super::CliConfig> {
if app_matches.subcommand_name().is_none() {
bail!("missing subcommand for 'exp'");
}

let cfg = match app_matches.subcommand() {
("rd-network-kargs", Some(matches)) => CliRdNetworkKargs::parse(matches)?,
(x, _) => unreachable!("unrecognized subcommand for 'exp': '{}'", x),
};

Ok(super::CliConfig::Exp(cfg))
}

// Run sub-command.
pub(crate) fn run(&self) -> Result<()> {
match self {
CliExp::RdNetworkKargs(cmd) => cmd.run()?,
};
Ok(())
}
}

/// Sub-command for network kernel arguments.
#[derive(Debug)]
pub struct CliRdNetworkKargs {
platform: String,
default_kargs: String,
}

impl CliRdNetworkKargs {
/// Hard-coded fallback for default network arguments.
const DEFAULT_NETWORK_KARGS: &'static str = "ip=dhcp,dhcp6";

/// Parse sub-command into configuration.
pub(crate) fn parse(matches: &ArgMatches) -> Result<CliExp> {
let platform = super::parse_provider(matches)?;
let default_kargs = matches
.value_of("default-value")
.unwrap_or_else(|| Self::DEFAULT_NETWORK_KARGS)
.to_string();

let cfg = Self {
platform,
default_kargs,
};
Ok(CliExp::RdNetworkKargs(cfg))
}

/// Run the sub-command.
pub(crate) fn run(&self) -> Result<()> {
if util::has_network_kargs(super::CMDLINE_PATH)? {
slog_scope::warn!("kernel cmdline already specifies network arguments, skipping");
return Ok(());
};

let provider_kargs = initrd::fetch_network_kargs(&self.platform)?;
let kargs = provider_kargs
.as_ref()
.unwrap_or_else(|| &self.default_kargs);
initrd::write_network_kargs(kargs)
}
}
198 changes: 141 additions & 57 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
use crate::errors::*;
use clap::{crate_version, App, Arg, ArgMatches, SubCommand};
use error_chain::bail;
use slog_scope::trace;

mod exp;
mod multi;

/// Path to kernel command-line (requires procfs mount).
Expand All @@ -13,13 +15,15 @@ const CMDLINE_PATH: &str = "/proc/cmdline";
#[derive(Debug)]
pub(crate) enum CliConfig {
Multi(multi::CliMulti),
Exp(exp::CliExp),
}

impl CliConfig {
/// Parse CLI sub-commands into configuration.
pub fn parse_subcommands(app_matches: ArgMatches) -> Result<Self> {
let cfg = match app_matches.subcommand() {
("multi", Some(matches)) => multi::CliMulti::parse(matches)?,
("exp", Some(matches)) => exp::CliExp::parse(matches)?,
(x, _) => unreachable!("unrecognized subcommand '{}'", x),
};

Expand All @@ -30,6 +34,7 @@ impl CliConfig {
pub fn run(self) -> Result<()> {
match self {
CliConfig::Multi(cmd) => cmd.run(),
CliConfig::Exp(cmd) => cmd.run(),
}
}
}
Expand All @@ -44,62 +49,103 @@ pub(crate) fn parse_args(argv: impl IntoIterator<Item = String>) -> Result<CliCo
Ok(cfg)
}

/// Parse provider ID from flag or kargs.
fn parse_provider(matches: &clap::ArgMatches) -> Result<String> {
let provider = match (matches.value_of("provider"), matches.is_present("cmdline")) {
(Some(provider), false) => String::from(provider),
(None, true) => crate::util::get_platform(CMDLINE_PATH)?,
(None, false) => bail!("must set either --provider or --cmdline"),
(Some(_), true) => bail!("cannot process both --provider and --cmdline"),
};

Ok(provider)
}

/// CLI setup, covering all sub-commands and arguments.
fn cli_setup<'a, 'b>() -> App<'a, 'b> {
// NOTE(lucab): due to legacy translation there can't be global arguments
// here, i.e. a sub-command is always expected first.
App::new("Afterburn").version(crate_version!()).subcommand(
SubCommand::with_name("multi")
.about("Perform multiple tasks in a single call")
.arg(
Arg::with_name("legacy-cli")
.long("legacy-cli")
.help("Whether this command was translated from legacy CLI args")
.hidden(true),
)
.arg(
Arg::with_name("provider")
.long("provider")
.help("The name of the cloud provider")
.global(true)
.takes_value(true),
)
.arg(
Arg::with_name("cmdline")
.long("cmdline")
.global(true)
.help("Read the cloud provider from the kernel cmdline"),
)
.arg(
Arg::with_name("attributes")
.long("attributes")
.help("The file into which the metadata attributes are written")
.takes_value(true),
)
.arg(
Arg::with_name("check-in")
.long("check-in")
.help("Check-in this instance boot with the cloud provider"),
)
.arg(
Arg::with_name("hostname")
.long("hostname")
.help("The file into which the hostname should be written")
.takes_value(true),
)
.arg(
Arg::with_name("network-units")
.long("network-units")
.help("The directory into which network units are written")
.takes_value(true),
)
.arg(
Arg::with_name("ssh-keys")
.long("ssh-keys")
.help("Update SSH keys for the given user")
.takes_value(true),
),
)
App::new("Afterburn")
.version(crate_version!())
.subcommand(
SubCommand::with_name("multi")
.about("Perform multiple tasks in a single call")
.arg(
Arg::with_name("legacy-cli")
.long("legacy-cli")
.help("Whether this command was translated from legacy CLI args")
.hidden(true),
)
.arg(
Arg::with_name("provider")
.long("provider")
.help("The name of the cloud provider")
.global(true)
.takes_value(true),
)
.arg(
Arg::with_name("cmdline")
.long("cmdline")
.global(true)
.help("Read the cloud provider from the kernel cmdline"),
)
.arg(
Arg::with_name("attributes")
.long("attributes")
.help("The file into which the metadata attributes are written")
.takes_value(true),
)
.arg(
Arg::with_name("check-in")
.long("check-in")
.help("Check-in this instance boot with the cloud provider"),
)
.arg(
Arg::with_name("hostname")
.long("hostname")
.help("The file into which the hostname should be written")
.takes_value(true),
)
.arg(
Arg::with_name("network-units")
.long("network-units")
.help("The directory into which network units are written")
.takes_value(true),
)
.arg(
Arg::with_name("ssh-keys")
.long("ssh-keys")
.help("Update SSH keys for the given user")
.takes_value(true),
),
)
.subcommand(
SubCommand::with_name("exp")
.about("experimental subcommands")
.subcommand(
SubCommand::with_name("rd-network-kargs")
.about("Supplement initrd with network configuration kargs")
.arg(
Arg::with_name("cmdline")
.long("cmdline")
.global(true)
.help("Read the cloud provider from the kernel cmdline"),
)
.arg(
Arg::with_name("provider")
.long("provider")
.help("The name of the cloud provider")
.global(true)
.takes_value(true),
)
.arg(
Arg::with_name("default-value")
.long("default-value")
.help("Default value for network kargs fallback")
.takes_value(true),
),
),
)
}

/// Translate command-line arguments from legacy mode.
Expand Down Expand Up @@ -139,8 +185,6 @@ fn translate_legacy_args(cli: impl IntoIterator<Item = String>) -> impl Iterator
})
}

impl CliConfig {}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -167,7 +211,11 @@ mod tests {
.map(ToString::to_string)
.collect();

parse_args(legacy).unwrap();
let cmd = parse_args(legacy).unwrap();
match cmd {
CliConfig::Multi(_) => {}
x => panic!("unexpected cmd: {:?}", x),
};
}

#[test]
Expand All @@ -183,7 +231,11 @@ mod tests {
.map(ToString::to_string)
.collect();

parse_args(args).unwrap();
let cmd = parse_args(args).unwrap();
match cmd {
CliConfig::Multi(_) => {}
x => panic!("unexpected cmd: {:?}", x),
};
}

#[test]
Expand All @@ -193,6 +245,38 @@ mod tests {
.map(ToString::to_string)
.collect();

parse_args(args).unwrap();
let cmd = parse_args(args).unwrap();
match cmd {
CliConfig::Multi(_) => {}
x => panic!("unexpected cmd: {:?}", x),
};
}

#[test]
fn test_exp_cmd() {
let args: Vec<_> = [
"afterburn",
"exp",
"rd-network-kargs",
"--provider",
"gcp",
"--default-value",
"ip=dhcp",
]
.iter()
.map(ToString::to_string)
.collect();

let cmd = parse_args(args).unwrap();
let subcmd = match cmd {
CliConfig::Exp(v) => v,
x => panic!("unexpected cmd: {:?}", x),
};

match subcmd {
exp::CliExp::RdNetworkKargs(_) => {}
#[allow(unreachable_patterns)]
x => panic!("unexpected 'exp' sub-command: {:?}", x),
};
}
}
Loading

0 comments on commit f78f03a

Please sign in to comment.