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 15, 2020
1 parent f2d8ea3 commit 0a840d7
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 76 deletions.
17 changes: 17 additions & 0 deletions dracut/30afterburn/afterburn-network-kargs.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[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_PROVIDER=--cmdline
ExecStart=/usr/bin/afterburn exp rd-network-kargs ${AFTERBURN_OPT_PROVIDER}
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"
}
59 changes: 59 additions & 0 deletions src/cli/exp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//! `exp` CLI sub-command.
use crate::errors::*;
use crate::metadata;
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,
}

impl CliRdNetworkKargs {
/// Parse sub-command into configuration.
pub(crate) fn parse(matches: &ArgMatches) -> Result<CliExp> {
let platform = super::parse_provider(matches)?;

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

/// Run the sub-command.
pub(crate) fn run(&self) -> Result<()> {
let provider = metadata::fetch_metadata(&self.platform)?;
provider.rd_net_kargs()?;
Ok(())
}
}
156 changes: 102 additions & 54 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,97 @@ 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("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"),
),
),
)
}

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

impl CliConfig {}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -195,4 +233,14 @@ mod tests {

parse_args(args).unwrap();
}

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

parse_args(args).unwrap();
}
}
16 changes: 1 addition & 15 deletions src/cli/multi.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
//! `multi` CLI sub-command.
use super::CMDLINE_PATH;
use crate::errors::*;
use crate::metadata;
use error_chain::bail;

#[derive(Debug)]
pub struct CliMulti {
Expand All @@ -18,7 +16,7 @@ pub struct CliMulti {
impl CliMulti {
/// Parse flags for the `multi` sub-command.
pub(crate) fn parse(matches: &clap::ArgMatches) -> Result<super::CliConfig> {
let provider = Self::parse_provider(matches)?;
let provider = super::parse_provider(matches)?;

let multi = Self {
attributes_file: matches.value_of("attributes").map(String::from),
Expand All @@ -42,18 +40,6 @@ impl CliMulti {
Ok(super::CliConfig::Multi(multi))
}

/// 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)
}

/// Run the `multi` sub-command.
pub(crate) fn run(self) -> Result<()> {
// fetch the metadata from the configured provider
Expand Down
13 changes: 8 additions & 5 deletions src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,15 @@ pub mod openstack;
pub mod packet;
pub mod vagrant_virtualbox;

use crate::errors::*;
use crate::network;
use openssh_keys::PublicKey;
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::Path;

use openssh_keys::PublicKey;
use users::{self, User};

use crate::errors::*;
use crate::network;

#[cfg(not(feature = "cl-legacy"))]
const ENV_PREFIX: &str = "AFTERBURN_";
#[cfg(feature = "cl-legacy")]
Expand Down Expand Up @@ -185,6 +183,11 @@ pub trait MetadataProvider {
/// netdev: https://www.freedesktop.org/software/systemd/man/systemd.netdev.html
fn virtual_network_devices(&self) -> Result<Vec<network::VirtualNetDev>>;

/// Setup initrd network kernel arguments.
fn rd_net_kargs(&self) -> Result<()> {
Ok(())
}

fn write_attributes(&self, attributes_file_path: String) -> Result<()> {
let mut attributes_file = create_file(&attributes_file_path)?;
for (k, v) in self.attributes()? {
Expand Down

0 comments on commit 0a840d7

Please sign in to comment.