Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Podman pull prep #214

Merged
merged 3 commits into from
Dec 4, 2023
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
69 changes: 9 additions & 60 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ use camino::Utf8PathBuf;
use clap::Parser;
use fn_error_context::context;
use ostree::gio;
use ostree_container::store::LayeredImageState;
use ostree_container::store::PrepareResult;
use ostree_container::OstreeImageReference;
use ostree_ext::container as ostree_container;
use ostree_ext::container::SignatureSource;
use ostree_ext::keyfileext::KeyFileExt;
Expand Down Expand Up @@ -214,57 +212,6 @@ pub(crate) async fn get_locked_sysroot() -> Result<ostree_ext::sysroot::SysrootL
Ok(sysroot)
}

/// Wrapper for pulling a container image, wiring up status output.
async fn new_importer(
repo: &ostree::Repo,
imgref: &ostree_container::OstreeImageReference,
) -> Result<ostree_container::store::ImageImporter> {
let config = Default::default();
let mut imp = ostree_container::store::ImageImporter::new(repo, imgref, config).await?;
imp.require_bootable();
Ok(imp)
}

/// Wrapper for pulling a container image, wiring up status output.
#[context("Pulling")]
async fn pull(
repo: &ostree::Repo,
imgref: &ImageReference,
quiet: bool,
) -> Result<Box<LayeredImageState>> {
let imgref = &OstreeImageReference::from(imgref.clone());
let mut imp = new_importer(repo, imgref).await?;
let prep = match imp.prepare().await? {
PrepareResult::AlreadyPresent(c) => {
println!("No changes in {} => {}", imgref, c.manifest_digest);
return Ok(c);
}
PrepareResult::Ready(p) => p,
};
if let Some(warning) = prep.deprecated_warning() {
ostree_ext::cli::print_deprecated_warning(warning).await;
}
ostree_ext::cli::print_layer_status(&prep);
let printer = (!quiet).then(|| {
let layer_progress = imp.request_progress();
let layer_byte_progress = imp.request_layer_progress();
tokio::task::spawn(async move {
ostree_ext::cli::handle_layer_progress_print(layer_progress, layer_byte_progress).await
})
});
let import = imp.import(prep).await;
if let Some(printer) = printer {
let _ = printer.await;
}
let import = import?;
if let Some(msg) =
ostree_container::store::image_filtered_content_warning(repo, &imgref.imgref)?
{
eprintln!("{msg}")
}
Ok(import)
}

#[context("Querying root privilege")]
pub(crate) fn require_root() -> Result<()> {
let uid = rustix::process::getuid();
Expand Down Expand Up @@ -327,7 +274,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
let mut changed = false;
if opts.check {
let imgref = imgref.clone().into();
let mut imp = new_importer(repo, &imgref).await?;
let mut imp = crate::deploy::new_importer(repo, &imgref).await?;
match imp.prepare().await? {
PrepareResult::AlreadyPresent(_) => {
println!("No changes in: {}", imgref);
Expand All @@ -347,7 +294,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
}
}
} else {
let fetched = pull(&sysroot.repo(), imgref, opts.quiet).await?;
let fetched = crate::deploy::pull(&sysroot, imgref, opts.quiet).await?;
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 @@ -368,8 +315,11 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
crate::deploy::stage(sysroot, &osname, &fetched, &spec).await?;
changed = true;
if let Some(prev) = booted_image.as_ref() {
let diff = ostree_container::ManifestDiff::new(&prev.manifest, &fetched.manifest);
diff.print();
if let Some(fetched_manifest) = fetched.get_manifest(repo)? {
let diff =
ostree_container::ManifestDiff::new(&prev.manifest, &fetched_manifest);
diff.print();
}
}
}
}
Expand Down Expand Up @@ -424,7 +374,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
}
let new_spec = RequiredHostSpec::from_spec(&new_spec)?;

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

if !opts.retain {
// By default, we prune the previous ostree ref so it will go away after later upgrades
Expand All @@ -448,7 +398,6 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
async fn edit(opts: EditOpts) -> Result<()> {
prepare_for_write().await?;
let sysroot = &get_locked_sysroot().await?;
let repo = &sysroot.repo();
let (booted_deployment, _deployments, host) =
crate::status::get_status_require_booted(sysroot)?;
let new_host: Host = if let Some(filename) = opts.filename {
Expand All @@ -467,7 +416,7 @@ async fn edit(opts: EditOpts) -> Result<()> {
return Ok(());
}
let new_spec = RequiredHostSpec::from_spec(&new_host.spec)?;
let fetched = pull(repo, new_spec.image, opts.quiet).await?;
let fetched = crate::deploy::pull(sysroot, new_spec.image, opts.quiet).await?;

// TODO gc old layers here

Expand Down
97 changes: 87 additions & 10 deletions lib/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use anyhow::{Context, Result};

use fn_error_context::context;
use ostree::{gio, glib};
use ostree_container::store::LayeredImageState;
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::Deployment;
use ostree_ext::sysroot::SysrootLock;
Expand All @@ -27,6 +27,13 @@ pub(crate) struct RequiredHostSpec<'a> {
pub(crate) image: &'a ImageReference,
}

/// State of a locally fetched image
pub(crate) struct ImageState {
pub(crate) manifest_digest: String,
pub(crate) version: Option<String>,
pub(crate) ostree_commit: String,
}

impl<'a> RequiredHostSpec<'a> {
/// Given a (borrowed) host specification, "unwrap" its internal
/// options, giving a spec that is required to have a base container image.
Expand All @@ -39,6 +46,81 @@ impl<'a> RequiredHostSpec<'a> {
}
}

impl From<ostree_container::store::LayeredImageState> for ImageState {
fn from(value: ostree_container::store::LayeredImageState) -> Self {
let version = value.version().map(|v| v.to_owned());
let ostree_commit = value.get_commit().to_owned();
Self {
manifest_digest: value.manifest_digest,
version,
ostree_commit,
}
}
}

impl ImageState {
/// Fetch the manifest corresponding to this image. May not be available in all backends.
pub(crate) fn get_manifest(
&self,
repo: &ostree::Repo,
) -> Result<Option<ostree_ext::oci_spec::image::ImageManifest>> {
ostree_container::store::query_image_commit(repo, &self.ostree_commit)
.map(|v| Some(v.manifest))
}
}

/// Wrapper for pulling a container image, wiring up status output.
pub(crate) async fn new_importer(
repo: &ostree::Repo,
imgref: &ostree_container::OstreeImageReference,
) -> Result<ostree_container::store::ImageImporter> {
let config = Default::default();
let mut imp = ostree_container::store::ImageImporter::new(repo, imgref, config).await?;
imp.require_bootable();
Ok(imp)
}

/// Wrapper for pulling a container image, wiring up status output.
#[context("Pulling")]
pub(crate) async fn pull(
sysroot: &SysrootLock,
imgref: &ImageReference,
quiet: bool,
) -> Result<Box<ImageState>> {
let repo = &sysroot.repo();
let imgref = &OstreeImageReference::from(imgref.clone());
let mut imp = new_importer(repo, imgref).await?;
let prep = match imp.prepare().await? {
PrepareResult::AlreadyPresent(c) => {
println!("No changes in {} => {}", imgref, c.manifest_digest);
return Ok(Box::new((*c).into()));
}
PrepareResult::Ready(p) => p,
};
if let Some(warning) = prep.deprecated_warning() {
ostree_ext::cli::print_deprecated_warning(warning).await;
}
ostree_ext::cli::print_layer_status(&prep);
let printer = (!quiet).then(|| {
let layer_progress = imp.request_progress();
let layer_byte_progress = imp.request_layer_progress();
tokio::task::spawn(async move {
ostree_ext::cli::handle_layer_progress_print(layer_progress, layer_byte_progress).await
})
});
let import = imp.import(prep).await;
if let Some(printer) = printer {
let _ = printer.await;
}
let import = import?;
if let Some(msg) =
ostree_container::store::image_filtered_content_warning(repo, &imgref.imgref)?
{
eprintln!("{msg}")
}
Ok(Box::new((*import).into()))
}

pub(crate) async fn cleanup(sysroot: &SysrootLock) -> Result<()> {
let repo = sysroot.repo();
let sysroot = sysroot.sysroot.clone();
Expand Down Expand Up @@ -90,16 +172,15 @@ async fn deploy(
sysroot: &SysrootLock,
merge_deployment: Option<&Deployment>,
stateroot: &str,
image: &LayeredImageState,
image: &ImageState,
origin: &glib::KeyFile,
) -> Result<()> {
let stateroot = Some(stateroot);
// Copy to move into thread
let base_commit = image.get_commit().to_owned();
let cancellable = gio::Cancellable::NONE;
let _new_deployment = sysroot.stage_tree_with_options(
stateroot,
&base_commit,
image.ostree_commit.as_str(),
Some(origin),
merge_deployment,
&Default::default(),
Expand All @@ -113,7 +194,7 @@ async fn deploy(
pub(crate) async fn stage(
sysroot: &SysrootLock,
stateroot: &str,
image: &LayeredImageState,
image: &ImageState,
spec: &RequiredHostSpec<'_>,
) -> Result<()> {
let merge_deployment = sysroot.merge_deployment(Some(stateroot));
Expand All @@ -134,11 +215,7 @@ pub(crate) async fn stage(
.await?;
crate::deploy::cleanup(sysroot).await?;
println!("Queued for next boot: {imgref}");
if let Some(version) = image
.configuration
.as_ref()
.and_then(ostree_container::version_for_config)
{
if let Some(version) = image.version.as_deref() {
println!(" Version: {version}");
}
println!(" Digest: {}", image.manifest_digest);
Expand Down