Skip to content
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ maplit = "1.0"
nix = { version = ">= 0.19, < 0.31", "default-features" = false, "features" = [ "mount", "user"] }
openssh-keys = ">= 0.5, < 0.7"
openssl = ">= 0.10.46, < 0.11"
percent-encoding = "2.3.2"
pnet_base = ">= 0.26, < 0.36"
pnet_datalink = ">= 0.26, < 0.36"
reqwest = { version = ">= 0.10, < 0.13", features = [ "blocking" ] }
Expand Down
21 changes: 21 additions & 0 deletions dracut/30afterburn/afterburn-ignition-fragment.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[Unit]
Description=Afterburn Ignition Config Fragment Generator
Documentation=https://coreos.github.io/afterburn/

# Only run on platforms that support ignition fragment generation.
ConditionKernelCommandLine=|ignition.platform.id=azure

# Run after networking is available (needed for IMDS), and in parallel
# with ignition-fetch so this path can emit its own diagnostics while
# still completing before ignition-kargs merge processing.
Wants=network-online.target
After=network-online.target
Before=ignition-kargs.service

OnFailure=emergency.target
OnFailureJobMode=isolate

[Service]
ExecStart=/usr/bin/afterburn render-ignition --cmdline --render-ignition-dir /usr/lib/ignition/base.platform.d/azure/ --platform-extensions
Type=oneshot
RemainAfterExit=yes
4 changes: 4 additions & 0 deletions dracut/30afterburn/module-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ install() {
inst_simple "$moddir/afterburn-network-kargs.service" \
"$systemdutildir/system/afterburn-network-kargs.service"

inst_simple "$moddir/afterburn-ignition-fragment.service" \
"$systemdutildir/system/afterburn-ignition-fragment.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"
ln -s "../afterburn-ignition-fragment.service" "$initdir/$systemdsystemunitdir/ignition-complete.target.requires/afterburn-ignition-fragment.service"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The afterburn-ignition-fragment.service is being linked into ignition-complete.target.requires. This will cause it to run after all main Ignition stages are complete, which is too late for the generated fragment to be used by Ignition.

According to its own unit file, this service must run before ignition-kargs.service. To ensure correct ordering and activation, it should be made a requirement of ignition-kargs.service instead of ignition-complete.target.

Suggested change
ln -s "../afterburn-ignition-fragment.service" "$initdir/$systemdsystemunitdir/ignition-complete.target.requires/afterburn-ignition-fragment.service"
mkdir -p "$initdir/$systemdsystemunitdir/ignition-kargs.service.requires"
ln -s "../afterburn-ignition-fragment.service" "$initdir/$systemdsystemunitdir/ignition-kargs.service.requires/afterburn-ignition-fragment.service"

}
63 changes: 63 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use slog_scope::trace;

mod exp;
mod multi;
mod render_ignition;

/// Path to kernel command-line (requires procfs mount).
const CMDLINE_PATH: &str = "/proc/cmdline";
Expand All @@ -19,6 +20,7 @@ pub(crate) enum CliConfig {
Multi(multi::CliMulti),
#[clap(subcommand)]
Exp(exp::CliExp),
RenderIgnition(render_ignition::CliRenderIgnition),
}

impl CliConfig {
Expand All @@ -27,6 +29,7 @@ impl CliConfig {
match self {
CliConfig::Multi(cmd) => cmd.run(),
CliConfig::Exp(cmd) => cmd.run(),
CliConfig::RenderIgnition(cmd) => cmd.run(),
}
}
}
Expand Down Expand Up @@ -231,4 +234,64 @@ mod tests {

parse_args(t3).unwrap();
}

#[test]
fn test_render_ignition_cmd() {
let args: Vec<_> = [
"afterburn",
"render-ignition",
"--provider",
"azure",
"--render-ignition-dir",
"/tmp/fragments",
"--hostname",
"--platform-user",
]
.iter()
.map(ToString::to_string)
.collect();

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

#[test]
fn test_render_ignition_platform_extensions() {
let args: Vec<_> = [
"afterburn",
"render-ignition",
"--cmdline",
"--render-ignition-dir",
"/tmp/fragments",
"--platform-extensions",
]
.iter()
.map(ToString::to_string)
.collect();

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

#[test]
fn test_render_ignition_requires_render_ignition_dir() {
let args: Vec<_> = [
"afterburn",
"render-ignition",
"--provider",
"azure",
"--hostname",
]
.iter()
.map(ToString::to_string)
.collect();

parse_args(args).unwrap_err();
}
}
69 changes: 69 additions & 0 deletions src/cli/render_ignition.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! `render-ignition` CLI sub-command.
//!
//! Generates per-feature Ignition config fragment files in an output directory.
//! Each enabled feature writes its own `.ign` file; Ignition merges them
//! natively from `base.platform.d/<platform>/`.

use anyhow::{Context, Result};
use clap::{ArgGroup, Parser};

/// Render Ignition config fragments from cloud provider metadata
#[derive(Debug, Parser)]
#[command(group(ArgGroup::new("provider-group").args(["cmdline", "provider"]).required(true)))]
pub struct CliRenderIgnition {
/// The name of the cloud provider
#[arg(long, value_name = "name")]
provider: Option<String>,
/// Read the cloud provider from the kernel cmdline
#[arg(long)]
cmdline: bool,
/// Directory to write Ignition config fragment files into
#[arg(long = "render-ignition-dir", value_name = "path")]
render_ignition_dir: String,
/// Include hostname in a fragment file
#[arg(long)]
hostname: bool,
/// Include platform user and SSH keys in a fragment file
#[arg(long)]
platform_user: bool,
/// Enable all platform extensions (implies --hostname --platform-user)
#[arg(long)]
platform_extensions: bool,
}

impl CliRenderIgnition {
/// Run the `render-ignition` sub-command.
pub(crate) fn run(self) -> Result<()> {
let provider_id = super::get_provider(self.provider.as_deref())?;

let hostname = self.hostname || self.platform_extensions;
let platform_user = self.platform_user || self.platform_extensions;

if !hostname && !platform_user {
slog_scope::warn!("render-ignition: no features specified");
return Ok(());
}

let metadata = crate::metadata::fetch_metadata(&provider_id)
.context("fetching metadata from provider")?;

if hostname {
crate::providers::microsoft::azure::config::generate_hostname_fragment(
metadata.as_ref(),
&self.render_ignition_dir,
)
.context("generating hostname ignition fragment")?;
}

if platform_user {
crate::providers::microsoft::azure::config::generate_user_fragment(
&provider_id,
metadata.as_ref(),
&self.render_ignition_dir,
)
.context("generating platform-user ignition fragment")?;
}

Ok(())
}
}
Loading
Loading