Skip to content

Commit 2081803

Browse files
cgwaltersjeckersb
authored andcommitted
WIP: Use podman pull to fetch containers
See #147 (comment) With this bootc starts to really gain support for a different backend than ostree. Here we basically just fork off `podman pull` to fetch container images into an *alternative root* in `/ostree/container-storage`, (Because otherwise basic things like `podman image prune` would delete the OS image) This is quite distinct from our use of `skopeo` in the ostree-ext project because suddenly now we gain support for things implemented in the containers/storage library like `zstd:chunked` and OCI crypt. *However*...today we still need to generate a final flattened filesystem tree (and an ostree commit) in order to maintain compatibilty with stuff in rpm-ostree. (A corrollary to this is we're not booting into a `podman mount` overlayfs stack) Related to this, we also need to handle SELinux labeling. Hence, we implement "layer squashing", and then do some final "postprocessing" on the resulting image matching the same logic that's done in ostree-ext such as `etc -> usr/etc` and handling `/var`. Note this also really wants ostreedev/ostree#3106 to avoid duplicating disk space. Signed-off-by: Colin Walters <[email protected]>
1 parent 870da02 commit 2081803

File tree

9 files changed

+777
-37
lines changed

9 files changed

+777
-37
lines changed

lib/src/cli.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ pub(crate) struct SwitchOpts {
9090

9191
/// Target image to use for the next boot.
9292
pub(crate) target: String,
93+
94+
/// The storage backend
95+
#[clap(long, hide = true)]
96+
pub(crate) backend: Option<crate::spec::Backend>,
9397
}
9498

9599
/// Options controlling rollback
@@ -251,6 +255,14 @@ impl InternalsOpts {
251255
const GENERATOR_BIN: &'static str = "bootc-systemd-generator";
252256
}
253257

258+
#[derive(Debug, clap::Parser, PartialEq, Eq)]
259+
pub(crate) struct InternalPodmanOpts {
260+
#[clap(long, value_parser, default_value = "/")]
261+
root: Utf8PathBuf,
262+
#[clap(trailing_var_arg = true, allow_hyphen_values = true)]
263+
args: Vec<std::ffi::OsString>,
264+
}
265+
254266
/// Deploy and transactionally in-place with bootable container images.
255267
///
256268
/// The `bootc` project currently uses ostree-containers as a backend
@@ -379,6 +391,9 @@ pub(crate) enum Opt {
379391
#[clap(subcommand)]
380392
#[clap(hide = true)]
381393
Internals(InternalsOpts),
394+
/// Execute podman in our internal configuration
395+
#[clap(hide = true)]
396+
InternalPodman(InternalPodmanOpts),
382397
#[clap(hide(true))]
383398
#[cfg(feature = "docgen")]
384399
Man(ManOpts),
@@ -540,7 +555,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
540555
}
541556
}
542557
} else {
543-
let fetched = crate::deploy::pull(sysroot, imgref, opts.quiet).await?;
558+
let fetched = crate::deploy::pull(sysroot, spec.backend, imgref, opts.quiet).await?;
544559
let kargs = crate::kargs::get_kargs(repo, &booted_deployment, fetched.as_ref())?;
545560
let staged_digest = staged_image.as_ref().map(|s| s.image_digest.as_str());
546561
let fetched_digest = fetched.manifest_digest.as_str();
@@ -628,6 +643,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
628643
let new_spec = {
629644
let mut new_spec = host.spec.clone();
630645
new_spec.image = Some(target.clone());
646+
new_spec.backend = opts.backend.unwrap_or_default();
631647
new_spec
632648
};
633649

@@ -637,7 +653,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
637653
}
638654
let new_spec = RequiredHostSpec::from_spec(&new_spec)?;
639655

640-
let fetched = crate::deploy::pull(sysroot, &target, opts.quiet).await?;
656+
let fetched = crate::deploy::pull(sysroot, new_spec.backend, &target, opts.quiet).await?;
641657
let kargs = crate::kargs::get_kargs(repo, &booted_deployment, fetched.as_ref())?;
642658

643659
if !opts.retain {
@@ -697,7 +713,8 @@ async fn edit(opts: EditOpts) -> Result<()> {
697713
return crate::deploy::rollback(sysroot).await;
698714
}
699715

700-
let fetched = crate::deploy::pull(sysroot, new_spec.image, opts.quiet).await?;
716+
let fetched =
717+
crate::deploy::pull(sysroot, new_spec.backend, new_spec.image, opts.quiet).await?;
701718
let repo = &sysroot.repo();
702719
let kargs = crate::kargs::get_kargs(repo, &booted_deployment, fetched.as_ref())?;
703720

@@ -813,6 +830,12 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
813830
}
814831
InternalsOpts::FixupEtcFstab => crate::deploy::fixup_etc_fstab(&root),
815832
},
833+
Opt::InternalPodman(args) => {
834+
prepare_for_write()?;
835+
// This also remounts writable
836+
let _sysroot = get_locked_sysroot().await?;
837+
crate::podman::exec(args.root.as_path(), args.args.as_slice())
838+
}
816839
#[cfg(feature = "docgen")]
817840
Opt::Man(manopts) => crate::docgen::generate_manpages(&manopts.directory),
818841
}

lib/src/deploy.rs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,19 @@ use anyhow::{anyhow, Context, Result};
99
use cap_std::fs::{Dir, MetadataExt};
1010
use cap_std_ext::cap_std;
1111
use cap_std_ext::dirext::CapStdExtDirExt;
12+
use chrono::DateTime;
1213
use fn_error_context::context;
1314
use ostree::{gio, glib};
1415
use ostree_container::OstreeImageReference;
1516
use ostree_ext::container as ostree_container;
1617
use ostree_ext::container::store::PrepareResult;
18+
use ostree_ext::oci_spec;
1719
use ostree_ext::ostree;
1820
use ostree_ext::ostree::Deployment;
1921
use ostree_ext::sysroot::SysrootLock;
2022

2123
use crate::spec::ImageReference;
22-
use crate::spec::{BootOrder, HostSpec};
24+
use crate::spec::{Backend, BootOrder, HostSpec};
2325
use crate::status::labels_of_config;
2426

2527
// TODO use https://github.com/ostreedev/ostree-rs-ext/pull/493/commits/afc1837ff383681b947de30c0cefc70080a4f87a
@@ -31,11 +33,14 @@ const BOOTC_DERIVED_KEY: &str = "bootc.derived";
3133
/// Variant of HostSpec but required to be filled out
3234
pub(crate) struct RequiredHostSpec<'a> {
3335
pub(crate) image: &'a ImageReference,
36+
pub(crate) backend: Backend,
3437
}
3538

3639
/// State of a locally fetched image
3740
pub(crate) struct ImageState {
41+
pub(crate) backend: Backend,
3842
pub(crate) manifest_digest: String,
43+
pub(crate) created: Option<DateTime<chrono::Utc>>,
3944
pub(crate) version: Option<String>,
4045
pub(crate) ostree_commit: String,
4146
}
@@ -48,16 +53,28 @@ impl<'a> RequiredHostSpec<'a> {
4853
.image
4954
.as_ref()
5055
.ok_or_else(|| anyhow::anyhow!("Missing image in specification"))?;
51-
Ok(Self { image })
56+
Ok(Self {
57+
image,
58+
backend: spec.backend,
59+
})
5260
}
5361
}
5462

5563
impl From<ostree_container::store::LayeredImageState> for ImageState {
5664
fn from(value: ostree_container::store::LayeredImageState) -> Self {
5765
let version = value.version().map(|v| v.to_owned());
5866
let ostree_commit = value.get_commit().to_owned();
67+
let labels = crate::status::labels_of_config(&value.configuration);
68+
let created = labels
69+
.and_then(|l| {
70+
l.get(oci_spec::image::ANNOTATION_CREATED)
71+
.map(|s| s.as_str())
72+
})
73+
.and_then(crate::status::try_deserialize_timestamp);
5974
Self {
75+
backend: Backend::OstreeContainer,
6076
manifest_digest: value.manifest_digest,
77+
created,
6178
version,
6279
ostree_commit,
6380
}
@@ -70,8 +87,14 @@ impl ImageState {
7087
&self,
7188
repo: &ostree::Repo,
7289
) -> Result<Option<ostree_ext::oci_spec::image::ImageManifest>> {
73-
ostree_container::store::query_image_commit(repo, &self.ostree_commit)
74-
.map(|v| Some(v.manifest))
90+
match self.backend {
91+
Backend::OstreeContainer => {
92+
ostree_container::store::query_image_commit(repo, &self.ostree_commit)
93+
.map(|v| Some(v.manifest))
94+
}
95+
// TODO: Figure out if we can get the OCI manifest from podman
96+
Backend::Container => Ok(None),
97+
}
7598
}
7699
}
77100

@@ -164,6 +187,31 @@ async fn handle_layer_progress_print(
164187
/// Wrapper for pulling a container image, wiring up status output.
165188
#[context("Pulling")]
166189
pub(crate) async fn pull(
190+
sysroot: &SysrootLock,
191+
backend: Backend,
192+
imgref: &ImageReference,
193+
quiet: bool,
194+
) -> Result<Box<ImageState>> {
195+
match backend {
196+
Backend::OstreeContainer => pull_via_ostree(sysroot, imgref, quiet).await,
197+
Backend::Container => pull_via_podman(sysroot, imgref, quiet).await,
198+
}
199+
}
200+
201+
/// Wrapper for pulling a container image, wiring up status output.
202+
async fn pull_via_podman(
203+
sysroot: &SysrootLock,
204+
imgref: &ImageReference,
205+
quiet: bool,
206+
) -> Result<Box<ImageState>> {
207+
let rootfs = &Dir::reopen_dir(&crate::utils::sysroot_fd_borrowed(sysroot))?;
208+
let fetched_imageid = crate::podman::podman_pull(rootfs, imgref, quiet).await?;
209+
crate::podman_ostree::commit_image_to_ostree(sysroot, &fetched_imageid)
210+
.await
211+
.map(Box::new)
212+
}
213+
214+
async fn pull_via_ostree(
167215
sysroot: &SysrootLock,
168216
imgref: &ImageReference,
169217
quiet: bool,

lib/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ pub(crate) mod kargs;
2727
mod lints;
2828
mod lsm;
2929
pub(crate) mod metadata;
30+
mod ostree_authfile;
31+
mod podman;
32+
mod podman_ostree;
3033
mod reboot;
3134
mod reexec;
3235
mod status;
@@ -46,8 +49,6 @@ mod k8sapitypes;
4649
mod kernel;
4750
#[cfg(feature = "install")]
4851
pub(crate) mod mount;
49-
#[cfg(feature = "install")]
50-
mod podman;
5152
pub mod spec;
5253

5354
#[cfg(feature = "docgen")]

lib/src/ostree_authfile.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//! # Copy of the ostree authfile bits as they're not public
2+
3+
use anyhow::Result;
4+
use ostree_ext::glib;
5+
use std::fs::File;
6+
use std::path::{Path, PathBuf};
7+
use std::sync::OnceLock;
8+
9+
// https://docs.rs/openat-ext/0.1.10/openat_ext/trait.OpenatDirExt.html#tymethod.open_file_optional
10+
// https://users.rust-lang.org/t/why-i-use-anyhow-error-even-in-libraries/68592
11+
pub(crate) fn open_optional(path: impl AsRef<Path>) -> std::io::Result<Option<std::fs::File>> {
12+
match std::fs::File::open(path.as_ref()) {
13+
Ok(r) => Ok(Some(r)),
14+
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
15+
Err(e) => Err(e),
16+
}
17+
}
18+
19+
struct ConfigPaths {
20+
persistent: PathBuf,
21+
runtime: PathBuf,
22+
}
23+
24+
/// Get the runtime and persistent config directories. In the system (root) case, these
25+
/// system(root) case: /run/ostree /etc/ostree
26+
/// user(nonroot) case: /run/user/$uid/ostree ~/.config/ostree
27+
fn get_config_paths() -> &'static ConfigPaths {
28+
static PATHS: OnceLock<ConfigPaths> = OnceLock::new();
29+
PATHS.get_or_init(|| {
30+
let mut r = if rustix::process::getuid() == rustix::process::Uid::ROOT {
31+
ConfigPaths {
32+
persistent: PathBuf::from("/etc"),
33+
runtime: PathBuf::from("/run"),
34+
}
35+
} else {
36+
ConfigPaths {
37+
persistent: glib::user_config_dir(),
38+
runtime: glib::user_runtime_dir(),
39+
}
40+
};
41+
let path = "ostree";
42+
r.persistent.push(path);
43+
r.runtime.push(path);
44+
r
45+
})
46+
}
47+
48+
impl ConfigPaths {
49+
/// Return the path and an open fd for a config file, if it exists.
50+
pub(crate) fn open_file(&self, p: impl AsRef<Path>) -> Result<Option<(PathBuf, File)>> {
51+
let p = p.as_ref();
52+
let mut runtime = self.runtime.clone();
53+
runtime.push(p);
54+
if let Some(f) = open_optional(&runtime)? {
55+
return Ok(Some((runtime, f)));
56+
}
57+
let mut persistent = self.persistent.clone();
58+
persistent.push(p);
59+
if let Some(f) = open_optional(&persistent)? {
60+
return Ok(Some((persistent, f)));
61+
}
62+
Ok(None)
63+
}
64+
}
65+
66+
/// Return the path to the global container authentication file, if it exists.
67+
pub(crate) fn get_global_authfile_path() -> Result<Option<PathBuf>> {
68+
let paths = get_config_paths();
69+
let r = paths.open_file("auth.json")?;
70+
// TODO pass the file descriptor to the proxy, not a global path
71+
Ok(r.map(|v| v.0))
72+
}

0 commit comments

Comments
 (0)