Skip to content

Commit 908713a

Browse files
cgwaltersjeckersb
authored andcommitted
WIP: Use podman pull to fetch containers
See bootc-dev#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 c6c360a commit 908713a

File tree

9 files changed

+781
-37
lines changed

9 files changed

+781
-37
lines changed

lib/src/cli.rs

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

8989
/// Target image to use for the next boot.
9090
pub(crate) target: String,
91+
92+
/// The storage backend
93+
#[clap(long, hide = true)]
94+
pub(crate) backend: Option<crate::spec::Backend>,
9195
}
9296

9397
/// Options controlling rollback
@@ -170,6 +174,14 @@ impl InternalsOpts {
170174
const GENERATOR_BIN: &'static str = "bootc-systemd-generator";
171175
}
172176

177+
#[derive(Debug, clap::Parser, PartialEq, Eq)]
178+
pub(crate) struct InternalPodmanOpts {
179+
#[clap(long, value_parser, default_value = "/")]
180+
root: Utf8PathBuf,
181+
#[clap(trailing_var_arg = true, allow_hyphen_values = true)]
182+
args: Vec<std::ffi::OsString>,
183+
}
184+
173185
/// Deploy and transactionally in-place with bootable container images.
174186
///
175187
/// The `bootc` project currently uses ostree-containers as a backend
@@ -292,6 +304,9 @@ pub(crate) enum Opt {
292304
#[clap(subcommand)]
293305
#[clap(hide = true)]
294306
Internals(InternalsOpts),
307+
/// Execute podman in our internal configuration
308+
#[clap(hide = true)]
309+
InternalPodman(InternalPodmanOpts),
295310
#[clap(hide(true))]
296311
#[cfg(feature = "docgen")]
297312
Man(ManOpts),
@@ -430,7 +445,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
430445
}
431446
}
432447
} else {
433-
let fetched = crate::deploy::pull(sysroot, imgref, opts.quiet).await?;
448+
let fetched = crate::deploy::pull(sysroot, spec.backend, imgref, opts.quiet).await?;
434449
let staged_digest = staged_image.as_ref().map(|s| s.image_digest.as_str());
435450
let fetched_digest = fetched.manifest_digest.as_str();
436451
tracing::debug!("staged: {staged_digest:?}");
@@ -515,6 +530,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
515530
let new_spec = {
516531
let mut new_spec = host.spec.clone();
517532
new_spec.image = Some(target.clone());
533+
new_spec.backend = opts.backend.unwrap_or_default();
518534
new_spec
519535
};
520536

@@ -524,7 +540,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
524540
}
525541
let new_spec = RequiredHostSpec::from_spec(&new_spec)?;
526542

527-
let fetched = crate::deploy::pull(sysroot, &target, opts.quiet).await?;
543+
let fetched = crate::deploy::pull(sysroot, new_spec.backend, &target, opts.quiet).await?;
528544

529545
if !opts.retain {
530546
// By default, we prune the previous ostree ref so it will go away after later upgrades
@@ -582,7 +598,8 @@ async fn edit(opts: EditOpts) -> Result<()> {
582598
return crate::deploy::rollback(sysroot).await;
583599
}
584600

585-
let fetched = crate::deploy::pull(sysroot, new_spec.image, opts.quiet).await?;
601+
let fetched =
602+
crate::deploy::pull(sysroot, new_spec.backend, new_spec.image, opts.quiet).await?;
586603

587604
// TODO gc old layers here
588605

@@ -688,6 +705,12 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
688705
}
689706
InternalsOpts::FixupEtcFstab => crate::deploy::fixup_etc_fstab(&root),
690707
},
708+
Opt::InternalPodman(args) => {
709+
prepare_for_write().await?;
710+
// This also remounts writable
711+
let _sysroot = get_locked_sysroot().await?;
712+
crate::podman::exec(args.root.as_path(), args.args.as_slice())
713+
}
691714
#[cfg(feature = "docgen")]
692715
Opt::Man(manopts) => crate::docgen::generate_manpages(&manopts.directory),
693716
}

lib/src/deploy.rs

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

2224
use crate::spec::ImageReference;
23-
use crate::spec::{BootOrder, HostSpec};
25+
use crate::spec::{Backend, BootOrder, HostSpec};
2426
use crate::status::labels_of_config;
2527

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

3740
/// State of a locally fetched image
3841
pub(crate) struct ImageState {
42+
pub(crate) backend: Backend,
3943
pub(crate) manifest_digest: String,
44+
pub(crate) created: Option<DateTime<chrono::Utc>>,
4045
pub(crate) version: Option<String>,
4146
pub(crate) ostree_commit: String,
4247
}
@@ -49,16 +54,28 @@ impl<'a> RequiredHostSpec<'a> {
4954
.image
5055
.as_ref()
5156
.ok_or_else(|| anyhow::anyhow!("Missing image in specification"))?;
52-
Ok(Self { image })
57+
Ok(Self {
58+
image,
59+
backend: spec.backend,
60+
})
5361
}
5462
}
5563

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

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

lib/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ pub(crate) mod journal;
2525
mod lints;
2626
mod lsm;
2727
pub(crate) mod metadata;
28+
mod ostree_authfile;
29+
mod podman;
30+
mod podman_ostree;
2831
mod reboot;
2932
mod reexec;
3033
mod status;
@@ -44,8 +47,6 @@ mod k8sapitypes;
4447
mod kernel;
4548
#[cfg(feature = "install")]
4649
pub(crate) mod mount;
47-
#[cfg(feature = "install")]
48-
mod podman;
4950
pub mod spec;
5051

5152
#[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 once_cell::sync::OnceCell;
5+
use ostree_ext::glib;
6+
use std::fs::File;
7+
use std::path::{Path, PathBuf};
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: OnceCell<ConfigPaths> = OnceCell::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)