Skip to content

Commit

Permalink
install: Add hidden ensure-completion verb
Browse files Browse the repository at this point in the history
This will be runnable via
```
%post --erroronfail
bootc install ensure-completion
%end
```

in Anaconda to work around the fact that it's not today
using `bootc install to-filesystem`.

Signed-off-by: Colin Walters <[email protected]>
  • Loading branch information
cgwalters committed Nov 20, 2024
1 parent 9d10a78 commit f116a60
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 0 deletions.
16 changes: 16 additions & 0 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,15 @@ pub(crate) enum InstallOpts {
/// will be wiped, but the content of the existing root will otherwise be retained, and will
/// need to be cleaned up if desired when rebooted into the new root.
ToExistingRoot(crate::install::InstallToExistingRootOpts),
/// Intended for use in environments that are performing an ostree-based installation, not bootc.
///
/// In this scenario the installation may be missing bootc specific features such as
/// kernel arguments, logically bound images and more. This command can be used to attempt
/// to reconcile. At the current time, the only tested environment is Anaconda using `ostreecontainer`
/// and it is recommended to avoid usage outside of that environment. Instead, ensure your
/// code is using `bootc install to-filesystem` from the start.
#[clap(hide = true)]
EnsureCompletion,
/// Output JSON to stdout that contains the merged installation configuration
/// as it may be relevant to calling processes using `install to-filesystem`
/// that in particular want to discover the desired root filesystem type from the container image.
Expand Down Expand Up @@ -971,6 +980,13 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
crate::install::install_to_existing_root(opts).await
}
InstallOpts::PrintConfiguration => crate::install::print_configuration(),
InstallOpts::EnsureCompletion => {
tokio::task::spawn_blocking(move || {
let rootfs = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
crate::install::completion::run(rootfs)
})
.await?
}
},
#[cfg(feature = "install")]
Opt::ExecInHostMountNamespace { args } => {
Expand Down
2 changes: 2 additions & 0 deletions lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// This sub-module is the "basic" installer that handles creating basic block device
// and filesystem setup.
pub(crate) mod baseline;
pub(crate) mod completion;
pub(crate) mod config;
mod osbuild;
pub(crate) mod osconfig;
Expand Down Expand Up @@ -740,6 +741,7 @@ async fn install_container(
)?;
let kargsd = kargsd.iter().map(|s| s.as_str());

// Keep this in sync with install/completion.rs for the Anaconda fixups
let install_config_kargs = state
.install_config
.as_ref()
Expand Down
76 changes: 76 additions & 0 deletions lib/src/install/completion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! This module handles finishing/completion after an ostree-based
//! install from e.g. Anaconda.
use std::os::fd::{AsFd, AsRawFd};

use anyhow::Result;
use cap_std_ext::cap_std::fs::Dir;
use ostree_ext::{gio, ostree};

use crate::utils::medium_visibility_warning;

use super::config;

/// An environment variable set by anaconda that hints
/// we are running as part of that environment.
const ANACONDA_ENV_HINT: &str = "ANA_INSTALL_PATH";

fn reconcile_kargs(rootfs: &Dir, sysroot: &ostree::Sysroot) -> Result<()> {
let cancellable = gio::Cancellable::NONE;

let deployments = sysroot.deployments();
let deployment = match deployments.as_slice() {
[d] => d,
o => anyhow::bail!("Expected exactly 1 deployment, not {}", o.len()),
};

let current_kargs = deployment
.bootconfig()
.expect("bootconfig for deployment")
.get("options");
let current_kargs = current_kargs
.as_ref()
.map(|s| s.as_str())
.unwrap_or_default();
tracing::debug!("current_kargs={current_kargs}");
let current_kargs = ostree::KernelArgs::from_string(&current_kargs);

// Keep this in sync with install_container
let install_config = config::load_config()?;
let install_config_kargs = install_config
.as_ref()
.and_then(|c| c.kargs.as_ref())
.into_iter()
.flatten()
.map(|s| s.as_str())
.collect::<Vec<_>>();
let kargsd = crate::kargs::get_kargs_in_root(rootfs, std::env::consts::ARCH)?;
let kargsd = kargsd.iter().map(|s| s.as_str()).collect::<Vec<_>>();

current_kargs.append_argv(&install_config_kargs);
current_kargs.append_argv(&kargsd);
let new_kargs = current_kargs.to_string();
tracing::debug!("new_kargs={new_kargs}");

sysroot.deployment_set_kargs_in_place(deployment, Some(&new_kargs), cancellable)?;
Ok(())
}

/// Core entrypoint.
pub(crate) fn run(rootfs: &Dir) -> Result<()> {
let cancellable = gio::Cancellable::NONE;
if std::env::var_os(ANACONDA_ENV_HINT).is_none() {
// Be loud if a user is invoking this outside of the expected setup.
medium_visibility_warning(&format!("Missing environment variable {ANACONDA_ENV_HINT}"));
}

let sysroot = {
let path = format!("/proc/self/fd/{}", rootfs.as_fd().as_raw_fd());
ostree::Sysroot::new(Some(&gio::File::for_path(path)))
};
sysroot.load(cancellable)?;

reconcile_kargs(rootfs, &sysroot)?;

Ok(())
}

0 comments on commit f116a60

Please sign in to comment.