Skip to content

Commit

Permalink
add /usr/lib/bootc/kargs.d support
Browse files Browse the repository at this point in the history
Fixes #255. Allows users to
create files within /usr/lib/bootc/kargs.d with kernel arguments. These
arguments can now be applied on a switch, upgrade, or edit.

General process:
- use ostree commit of fetched container image to return
the file tree
- navigate to /usr/lib/bootc/kargs.d
- read each file within the directory
- calculate the diff between the booted and fetched kargs in kargs.d
- apply the diff to the kargs currently on the running system
- pass the kargs to the stage() function

Signed-off-by: Luke Yang <luyang@redhat.com>
  • Loading branch information
lukewarmtemp committed May 8, 2024
1 parent 613b6e5 commit e4acb9f
Show file tree
Hide file tree
Showing 5 changed files with 410 additions and 9 deletions.
21 changes: 17 additions & 4 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
}
} else {
let fetched = crate::deploy::pull(sysroot, imgref, opts.quiet).await?;
let kargs = crate::kargs::get_kargs(repo, &booted_deployment, fetched.as_ref())?;
let staged_digest = staged_image.as_ref().map(|s| s.image_digest.as_str());
let fetched_digest = fetched.manifest_digest.as_str();
tracing::debug!("staged: {staged_digest:?}");
Expand All @@ -378,7 +379,10 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
println!("No update available.")
} else {
let osname = booted_deployment.osname();
crate::deploy::stage(sysroot, &osname, &fetched, &spec).await?;
let mut opts = ostree::SysrootDeployTreeOpts::default();
let kargs: Vec<&str> = kargs.iter().map(|s| s.as_str() ).collect();
opts.override_kernel_argv = Some(kargs.as_slice());
crate::deploy::stage(sysroot, &osname, &fetched, &spec, Some(opts)).await?;
changed = true;
if let Some(prev) = booted_image.as_ref() {
if let Some(fetched_manifest) = fetched.get_manifest(repo)? {
Expand Down Expand Up @@ -451,6 +455,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
let new_spec = RequiredHostSpec::from_spec(&new_spec)?;

let fetched = crate::deploy::pull(sysroot, &target, opts.quiet).await?;
let kargs = crate::kargs::get_kargs(repo, &booted_deployment, fetched.as_ref())?;

if !opts.retain {
// By default, we prune the previous ostree ref so it will go away after later upgrades
Expand All @@ -464,7 +469,10 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
}

let stateroot = booted_deployment.osname();
crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec).await?;
let mut opts = ostree::SysrootDeployTreeOpts::default();
let kargs: Vec<&str> = kargs.iter().map(|s| s.as_str() ).collect();
opts.override_kernel_argv = Some(kargs.as_slice());
crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec, Some(opts)).await?;

Ok(())
}
Expand All @@ -489,15 +497,20 @@ async fn edit(opts: EditOpts) -> Result<()> {

if new_host.spec == host.spec {
println!("Edit cancelled, no changes made.");
return Ok(());
return Ok(());
}
let new_spec = RequiredHostSpec::from_spec(&new_host.spec)?;
let fetched = crate::deploy::pull(sysroot, new_spec.image, opts.quiet).await?;
let repo = &sysroot.repo();
let kargs = crate::kargs::get_kargs(repo, &booted_deployment, fetched.as_ref())?;

// TODO gc old layers here

let stateroot = booted_deployment.osname();
crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec).await?;
let mut opts = ostree::SysrootDeployTreeOpts::default();
let kargs: Vec<&str> = kargs.iter().map(|s| s.as_str() ).collect();
opts.override_kernel_argv = Some(kargs.as_slice());
crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec, Some(opts)).await?;

Ok(())
}
Expand Down
8 changes: 6 additions & 2 deletions lib/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use ostree::{gio, glib};
use ostree_container::OstreeImageReference;
use ostree_ext::container as ostree_container;
use ostree_ext::container::store::PrepareResult;
use ostree_ext::ostree;
use ostree_ext::ostree::{self};
use ostree_ext::ostree::Deployment;
use ostree_ext::sysroot::SysrootLock;

Expand Down Expand Up @@ -219,16 +219,18 @@ async fn deploy(
stateroot: &str,
image: &ImageState,
origin: &glib::KeyFile,
opts: Option<ostree::SysrootDeployTreeOpts<'_>>,
) -> Result<()> {
let stateroot = Some(stateroot);
let opts = opts.unwrap_or(Default::default());
// Copy to move into thread
let cancellable = gio::Cancellable::NONE;
let _new_deployment = sysroot.stage_tree_with_options(
stateroot,
image.ostree_commit.as_str(),
Some(origin),
merge_deployment,
&Default::default(),
&opts,
cancellable,
)?;
Ok(())
Expand All @@ -253,6 +255,7 @@ pub(crate) async fn stage(
stateroot: &str,
image: &ImageState,
spec: &RequiredHostSpec<'_>,
opts: Option<ostree::SysrootDeployTreeOpts<'_>>,
) -> Result<()> {
let merge_deployment = sysroot.merge_deployment(Some(stateroot));
let origin = origin_from_imageref(spec.image)?;
Expand All @@ -262,6 +265,7 @@ pub(crate) async fn stage(
stateroot,
image,
&origin,
opts,
)
.await?;
crate::deploy::cleanup(sysroot).await?;
Expand Down
229 changes: 226 additions & 3 deletions lib/src/install/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub(crate) struct InstallConfiguration {
/// Kernel arguments, applied at installation time
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) kargs: Option<Vec<String>>,
pub(crate) arch: Option<Vec<String>>,
}

fn merge_basic<T>(s: &mut Option<T>, o: Option<T>) {
Expand Down Expand Up @@ -95,9 +96,21 @@ impl Mergeable for InstallConfiguration {
merge_basic(&mut self.root_fs_type, other.root_fs_type);
self.filesystem.merge(other.filesystem);
if let Some(other_kargs) = other.kargs {
self.kargs
.get_or_insert_with(Default::default)
.extend(other_kargs)
// if arch is specified, only apply kargs if it matches the current arch
// if arch is not specified, apply kargs unconditionally
if let Some(other_arch) = other.arch {
for arch in other_arch.iter() {
if arch == std::env::consts::ARCH {
self.kargs
.get_or_insert_with(Default::default)
.extend(other_kargs.clone())
}
}
} else {
self.kargs
.get_or_insert_with(Default::default)
.extend(other_kargs)
}
}
}
}
Expand Down Expand Up @@ -179,6 +192,7 @@ root-fs-type = "xfs"
root_fs_type: Some(Filesystem::Ext4),
filesystem: None,
kargs: None,
arch: None,
}),
};
install.merge(other.install.unwrap());
Expand Down Expand Up @@ -214,6 +228,7 @@ kargs = ["console=ttyS0", "foo=bar"]
.map(ToOwned::to_owned)
.collect(),
),
arch: None,
}),
};
install.merge(other.install.unwrap());
Expand Down Expand Up @@ -252,6 +267,7 @@ type = "xfs"
}),
}),
kargs: None,
arch: None,
}),
};
install.merge(other.install.unwrap());
Expand All @@ -260,3 +276,210 @@ type = "xfs"
Filesystem::Ext4
);
}

#[test]
/// Verify that kargs are only applied to supported architectures
fn test_arch() {
use super::baseline::Filesystem;

// no arch specified, kargs ensure that kargs are applied unconditionally
std::env::set_var("ARCH", "x86_64");
let c: InstallConfigurationToplevel = toml::from_str(
r##"[install]
root-fs-type = "xfs"
"##,
)
.unwrap();
let mut install = c.install.unwrap();
let other = InstallConfigurationToplevel {
install: Some(InstallConfiguration {
root_fs_type: Some(Filesystem::Ext4),
filesystem: None,
kargs: Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
arch: None,
}),
};
install.merge(other.install.unwrap());
assert_eq!(
install.kargs,
Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect()
)
);
std::env::set_var("ARCH", "aarch64");
let c: InstallConfigurationToplevel = toml::from_str(
r##"[install]
root-fs-type = "xfs"
"##,
)
.unwrap();
let mut install = c.install.unwrap();
let other = InstallConfigurationToplevel {
install: Some(InstallConfiguration {
root_fs_type: Some(Filesystem::Ext4),
filesystem: None,
kargs: Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
arch: None,
}),
};
install.merge(other.install.unwrap());
assert_eq!(
install.kargs,
Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect()
)
);

// one arch matches and one doesn't, ensure that kargs are only applied for the matching arch
std::env::set_var("ARCH", "x86_64");
let c: InstallConfigurationToplevel = toml::from_str(
r##"[install]
root-fs-type = "xfs"
"##,
)
.unwrap();
let mut install = c.install.unwrap();
let other = InstallConfigurationToplevel {
install: Some(InstallConfiguration {
root_fs_type: Some(Filesystem::Ext4),
filesystem: None,
kargs: Some(
["console=ttyS0", "foo=bar"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
arch: Some(
["x86_64"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
}),
};
install.merge(other.install.unwrap());
assert_eq!(
install.kargs,
Some(
["console=ttyS0", "foo=bar"]
.into_iter()
.map(ToOwned::to_owned)
.collect()
)
);
let other = InstallConfigurationToplevel {
install: Some(InstallConfiguration {
root_fs_type: Some(Filesystem::Ext4),
filesystem: None,
kargs: Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
arch: Some(
["aarch64"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
)
}),
};
install.merge(other.install.unwrap());
assert_eq!(
install.kargs,
None
);

// multiple arch specified, ensure that kargs are applied to both archs
std::env::set_var("ARCH", "x86_64");
let c: InstallConfigurationToplevel = toml::from_str(
r##"[install]
root-fs-type = "xfs"
"##,
)
.unwrap();
let mut install = c.install.unwrap();
let other = InstallConfigurationToplevel {
install: Some(InstallConfiguration {
root_fs_type: Some(Filesystem::Ext4),
filesystem: None,
kargs: Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
arch: Some(
["x86_64", "aarch64"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
)
}),
};
std::env::set_var("ARCH", "x86_64");
install.merge(other.install.unwrap());
assert_eq!(
install.kargs,
Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect()
)
);
std::env::set_var("ARCH", "aarch64");
let c: InstallConfigurationToplevel = toml::from_str(
r##"[install]
root-fs-type = "xfs"
"##,
)
.unwrap();
let mut install = c.install.unwrap();
let other = InstallConfigurationToplevel {
install: Some(InstallConfiguration {
root_fs_type: Some(Filesystem::Ext4),
filesystem: None,
kargs: Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
arch: Some(
["x86_64", "aarch64"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
)
}),
};
std::env::set_var("ARCH", "x86_64");
install.merge(other.install.unwrap());
assert_eq!(
install.kargs,
Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect()
)
);
}
Loading

0 comments on commit e4acb9f

Please sign in to comment.