Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- name: Run tests
run: cargo test -- --nocapture --quiet
- name: Manpage generation
run: mkdir -p target/man && cargo run --features=docgen -- man --directory target/man
run: cargo xtask update-generated
- name: Clippy (gate on correctness and suspicous)
run: make validate-rust
fedora-container-tests:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/scheduled-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
dnf -y install pandoc
dnf -y install go-md2man
cargo install cargo-edit

# If version is provided via workflow dispatch, validate and use it
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

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

11 changes: 11 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,14 @@ run-container-external-tests:

unittest *ARGS:
podman build --jobs=4 --target units -t localhost/bootc-units --build-arg=unitargs={{ARGS}} .

# Update all generated files (man pages and JSON schemas)
#
# This is the unified command that:
# - Auto-discovers new CLI commands and creates man page templates
# - Syncs CLI options from Rust code to existing man page templates
# - Updates JSON schema files
#
# Use this after adding, removing, or modifying CLI options or schemas.
update-generated:
cargo run -p xtask update-generated
36 changes: 26 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,28 @@ TAR_REPRODUCIBLE = tar --mtime="@${SOURCE_DATE_EPOCH}" --sort=name --owner=0 --g
# (Note we should also make installation of the units conditional on the rhsm feature)
CARGO_FEATURES ?= $(shell . /usr/lib/os-release; if echo "$$ID_LIKE" |grep -qF rhel; then echo rhsm; fi)

all:
all: bin manpages

bin:
cargo build --release --features "$(CARGO_FEATURES)"

# Generate man pages from markdown sources
MAN5_SOURCES := $(wildcard docs/src/man/*.5.md)
MAN8_SOURCES := $(wildcard docs/src/man/*.8.md)
TARGETMAN := target/man
MAN5_TARGETS := $(patsubst docs/src/man/%.5.md,$(TARGETMAN)/%.5,$(MAN5_SOURCES))
MAN8_TARGETS := $(patsubst docs/src/man/%.8.md,$(TARGETMAN)/%.8,$(MAN8_SOURCES))

$(TARGETMAN)/%.5: docs/src/man/%.5.md
@mkdir -p $(TARGETMAN)
go-md2man -in $< -out $@

$(TARGETMAN)/%.8: docs/src/man/%.8.md
@mkdir -p $(TARGETMAN)
go-md2man -in $< -out $@

manpages: $(MAN5_TARGETS) $(MAN8_TARGETS)

STORAGE_RELATIVE_PATH ?= $(shell realpath -m -s --relative-to="$(prefix)/lib/bootc/storage" /sysroot/ostree/bootc/storage)
install:
install -D -m 0755 -t $(DESTDIR)$(prefix)/bin target/release/bootc
Expand All @@ -22,15 +41,12 @@ install:
ln -s "$(STORAGE_RELATIVE_PATH)" "$(DESTDIR)$(prefix)/lib/bootc/storage"
install -D -m 0755 crates/cli/bootc-generator-stub $(DESTDIR)$(prefix)/lib/systemd/system-generators/bootc-systemd-generator
install -d $(DESTDIR)$(prefix)/lib/bootc/install
# Support installing pre-generated man pages shipped in source tarball, to avoid
# a dependency on pandoc downstream. But in local builds these end up in target/man,
# so we honor that too.
for d in man target/man; do \
if test -d $$d; then \
install -D -m 0644 -t $(DESTDIR)$(prefix)/share/man/man5 $$d/*.5; \
install -D -m 0644 -t $(DESTDIR)$(prefix)/share/man/man8 $$d/*.8; \
fi; \
done
if [ -n "$(MAN5_TARGETS)" ]; then \
install -D -m 0644 -t $(DESTDIR)$(prefix)/share/man/man5 $(MAN5_TARGETS); \
fi
if [ -n "$(MAN8_TARGETS)" ]; then \
install -D -m 0644 -t $(DESTDIR)$(prefix)/share/man/man8 $(MAN8_TARGETS); \
fi
install -D -m 0644 -t $(DESTDIR)/$(prefix)/lib/systemd/system systemd/*.service systemd/*.timer systemd/*.path systemd/*.target
install -d -m 0755 $(DESTDIR)/$(prefix)/lib/systemd/system/multi-user.target.wants
ln -s ../bootc-status-updated.path $(DESTDIR)/$(prefix)/lib/systemd/system/multi-user.target.wants/bootc-status-updated.path
Expand Down
3 changes: 3 additions & 0 deletions contrib/packaging/bootc.spec
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ BuildRequires: libzstd-devel
BuildRequires: make
BuildRequires: ostree-devel
BuildRequires: openssl-devel
BuildRequires: go-md2man
%if 0%{?rhel}
BuildRequires: rust-toolset
%else
Expand Down Expand Up @@ -107,6 +108,8 @@ export SYSTEM_REINSTALL_BOOTC_INSTALL_PODMAN_PATH=%{system_reinstall_bootc_insta
%cargo_build %cargo_args
%endif

make manpages

%cargo_vendor_manifest
# https://pagure.io/fedora-rust/rust-packaging/issue/33
sed -i -e '/https:\/\//d' cargo-vendor.txt
Expand Down
107 changes: 26 additions & 81 deletions crates/lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,9 @@ use crate::utils::sigpolicy_from_opt;
/// Shared progress options
#[derive(Debug, Parser, PartialEq, Eq)]
pub(crate) struct ProgressOptions {
/// File descriptor number which must refer to an open pipe (anonymous or named).
/// File descriptor number which must refer to an open pipe.
///
/// Interactive progress will be written to this file descriptor as "JSON lines"
/// format, where each value is separated by a newline.
/// Progress is written as JSON lines to this file descriptor.
#[clap(long, hide = true)]
pub(crate) progress_fd: Option<RawProgressFd>,
}
Expand All @@ -69,23 +68,19 @@ pub(crate) struct UpgradeOpts {

/// Check if an update is available without applying it.
///
/// This only downloads an updated manifest and image configuration (i.e. typically kilobyte-sized metadata)
/// as opposed to the image layers.
/// This only downloads updated metadata, not the full image layers.
#[clap(long, conflicts_with = "apply")]
pub(crate) check: bool,

/// Restart or reboot into the new target image.
///
/// Currently, this option always reboots. In the future this command
/// will detect the case where no kernel changes are queued, and perform
/// a userspace-only restart.
/// Currently, this always reboots. Future versions may support userspace-only restart.
#[clap(long, conflicts_with = "check")]
pub(crate) apply: bool,

/// Configure soft reboot behavior.
///
/// 'required' will fail if soft reboot is not available.
/// 'auto' will use soft reboot if available, otherwise fall back to regular reboot.
/// 'required' fails if soft reboot unavailable, 'auto' falls back to regular reboot.
#[clap(long = "soft-reboot", conflicts_with = "check")]
pub(crate) soft_reboot: Option<SoftRebootMode>,

Expand All @@ -102,16 +97,13 @@ pub(crate) struct SwitchOpts {

/// Restart or reboot into the new target image.
///
/// Currently, this option always reboots. In the future this command
/// will detect the case where no kernel changes are queued, and perform
/// a userspace-only restart.
/// Currently, this always reboots. Future versions may support userspace-only restart.
#[clap(long)]
pub(crate) apply: bool,

/// Configure soft reboot behavior.
///
/// 'required' will fail if soft reboot is not available.
/// 'auto' will use soft reboot if available, otherwise fall back to regular reboot.
/// 'required' fails if soft reboot unavailable, 'auto' falls back to regular reboot.
#[clap(long = "soft-reboot")]
pub(crate) soft_reboot: Option<SoftRebootMode>,

Expand Down Expand Up @@ -161,8 +153,7 @@ pub(crate) struct RollbackOpts {

/// Configure soft reboot behavior.
///
/// 'required' will fail if soft reboot is not available.
/// 'auto' will use soft reboot if available, otherwise fall back to regular reboot.
/// 'required' fails if soft reboot unavailable, 'auto' falls back to regular reboot.
#[clap(long = "soft-reboot")]
pub(crate) soft_reboot: Option<SoftRebootMode>,
}
Expand Down Expand Up @@ -279,14 +270,6 @@ pub(crate) enum InstallOpts {
PrintConfiguration,
}

/// Options for man page generation
#[derive(Debug, Parser, PartialEq, Eq)]
pub(crate) struct ManOpts {
#[clap(long)]
/// Output to this directory
pub(crate) directory: Utf8PathBuf,
}

/// Subcommands which can be executed as part of a container build.
#[derive(Debug, clap::Subcommand, PartialEq, Eq)]
pub(crate) enum ContainerOpts {
Expand Down Expand Up @@ -540,6 +523,9 @@ pub(crate) enum InternalsOpts {
#[clap(long)]
merge: bool,
},
#[cfg(feature = "docgen")]
/// Dump CLI structure as JSON for documentation generation
DumpCliJson,
}

#[derive(Debug, clap::Subcommand, PartialEq, Eq)]
Expand Down Expand Up @@ -625,70 +611,26 @@ pub(crate) enum Opt {
///
/// Only changes to the `spec` section are honored.
Edit(EditOpts),
/// Display status
///
/// If standard output is a terminal, this will output a description of the bootc system state.
/// If standard output is not a terminal, output a YAML-formatted object using a schema
/// intended to match a Kubernetes resource that describes the state of the booted system.
///
/// ## Parsing output via programs
///
/// Either the default YAML format or `--format=json` can be used. Do not attempt to
/// explicitly parse the output of `--format=humanreadable` as it will very likely
/// change over time.
/// Display status.
///
/// ## Programmatically detecting whether the system is deployed via bootc
///
/// Invoke e.g. `bootc status --json`, and check if `status.booted` is not `null`.
/// Shows bootc system state. Outputs YAML by default, human-readable if terminal detected.
Status(StatusOpts),
/// Adds a transient writable overlayfs on `/usr` that will be discarded on reboot.
///
/// ## Use cases
///
/// A common pattern is wanting to use tracing/debugging tools, such as `strace`
/// that may not be in the base image. A system package manager such as `apt` or
/// `dnf` can apply changes into this transient overlay that will be discarded on
/// reboot.
///
/// ## /etc and /var
///
/// However, this command has no effect on `/etc` and `/var` - changes written
/// there will persist. It is common for package installations to modify these
/// directories.
///
/// ## Unmounting
///
/// Almost always, a system process will hold a reference to the open mount point.
/// You can however invoke `umount -l /usr` to perform a "lazy unmount".
/// Add a transient writable overlayfs on `/usr`.
///
/// Allows temporary package installation that will be discarded on reboot.
#[clap(alias = "usroverlay")]
UsrOverlay,
/// Install the running container to a target.
///
/// ## Understanding installations
///
/// OCI containers are effectively layers of tarballs with JSON for metadata; they
/// cannot be booted directly. The `bootc install` flow is a highly opinionated
/// method to take the contents of the container image and install it to a target
/// block device (or an existing filesystem) in such a way that it can be booted.
///
/// For example, a Linux partition table and filesystem is used, and the bootloader and kernel
/// embedded in the container image are also prepared.
///
/// A bootc installed container currently uses OSTree as a backend, and this sets
/// it up such that a subsequent `bootc upgrade` can perform in-place updates.
///
/// An installation is not simply a copy of the container filesystem, but includes
/// other setup and metadata.
/// Takes a container image and installs it to disk in a bootable format.
#[clap(subcommand)]
Install(InstallOpts),
/// Operations which can be executed as part of a container build.
#[clap(subcommand)]
Container(ContainerOpts),
/// Operations on container images
/// Operations on container images.
///
/// Stability: This interface is not declared stable and may change or be removed
/// at any point in the future.
/// Stability: This interface may change in the future.
#[clap(subcommand, hide = true)]
Image(ImageOpts),
/// Execute the given command in the host mount namespace
Expand All @@ -704,9 +646,6 @@ pub(crate) enum Opt {
#[clap(subcommand)]
#[clap(hide = true)]
Internals(InternalsOpts),
#[clap(hide(true))]
#[cfg(feature = "docgen")]
Man(ManOpts),
}

/// Ensure we've entered a mount namespace, so that we can remount
Expand Down Expand Up @@ -1499,6 +1438,14 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
}
#[cfg(feature = "rhsm")]
InternalsOpts::PublishRhsmFacts => crate::rhsm::publish_facts(&root).await,
#[cfg(feature = "docgen")]
InternalsOpts::DumpCliJson => {
use clap::CommandFactory;
let cmd = Opt::command();
let json = crate::cli_json::dump_cli_json(&cmd)?;
println!("{}", json);
Ok(())
}
InternalsOpts::DirDiff {
pristine_etc,
current_etc,
Expand All @@ -1522,8 +1469,6 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
Ok(())
}
},
#[cfg(feature = "docgen")]
Opt::Man(manopts) => crate::docgen::generate_manpages(&manopts.directory),
Opt::State(opts) => match opts {
StateOpts::WipeOstree => {
let sysroot = ostree::Sysroot::new_default();
Expand Down
Loading