Skip to content
This repository has been archived by the owner on Jan 15, 2025. It is now read-only.

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
cgwalters committed Oct 5, 2021
1 parent 2d84f93 commit d60de71
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 19 deletions.
42 changes: 40 additions & 2 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
use anyhow::Result;
use ostree::gio;
use std::collections::BTreeMap;
use std::convert::TryInto;
use std::convert::{TryFrom, TryInto};
use std::ffi::OsString;
use structopt::StructOpt;

use crate::container::store::{LayeredImageImporter, PrepareResult};
use crate::container::{Config, ImportOptions};
use crate::container::{Config, ImportOptions, OstreeImageReference};

#[derive(Debug, StructOpt)]
struct BuildOpts {
Expand Down Expand Up @@ -132,6 +132,25 @@ enum ContainerImageOpts {
/// Image reference, e.g. ostree-remote-image:someremote:registry:quay.io/exampleos/exampleos:latest
imgref: String,
},

/// Perform initial deployment for a container image
Deploy {
/// Path to the system root
#[structopt(long)]
sysroot: String,

/// Name for the state directory, also known as "osname".
#[structopt(long)]
stateroot: String,

/// Image reference, e.g. ostree-remote-image:someremote:registry:quay.io/exampleos/exampleos:latest
#[structopt(long)]
imgref: String,

#[structopt(long)]
/// Add a kernel argument
karg: Option<Vec<String>>,
},
}

/// Options for the Integrity Measurement Architecture (IMA).
Expand Down Expand Up @@ -390,6 +409,25 @@ where
Ok(())
}
ContainerImageOpts::Pull { repo, imgref } => container_store(&repo, &imgref).await,
ContainerImageOpts::Deploy {
sysroot,
stateroot,
imgref,
karg,
} => {
let sysroot = &ostree::Sysroot::new(Some(&gio::File::for_path(&sysroot)));
let imgref = OstreeImageReference::try_from(imgref.as_str())?;
let kargs = karg.as_deref();
let kargs = kargs.map(|v| {
let r: Vec<_> = v.iter().map(|s| s.as_str()).collect();
r
});
let options = crate::container::deploy::DeployOpts {
kargs: kargs.as_deref(),
};
crate::container::deploy::deploy(sysroot, &stateroot, &imgref, Some(options))
.await
}
},
},
Opt::ImaSign(ref opts) => ima_sign(opts),
Expand Down
53 changes: 53 additions & 0 deletions lib/src/container/deploy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! Perform initial setup for a container image based system root
use super::OstreeImageReference;
use crate::container::store::PrepareResult;
use anyhow::Result;
use ostree::glib;

/// The key in the OSTree origin which holds a serialized [`super::OstreeImageReference`].
pub const ORIGIN_CONTAINER: &str = "container";

async fn pull_idempotent(repo: &ostree::Repo, imgref: &OstreeImageReference) -> Result<String> {
let mut imp = super::store::LayeredImageImporter::new(repo, imgref).await?;
match imp.prepare().await? {
PrepareResult::AlreadyPresent(r) => Ok(r),
PrepareResult::Ready(prep) => Ok(imp.import(prep).await?.commit),
}
}

/// Options configuring deployment.
#[derive(Debug, Default)]
pub struct DeployOpts<'a> {
/// Kernel arguments to use.
pub kargs: Option<&'a [&'a str]>,
}

/// Write a container image to an OSTree deployment.
///
/// This API is currently intended for only an initial deployment.
pub async fn deploy<'opts>(
sysroot: &ostree::Sysroot,
stateroot: &str,
imgref: &OstreeImageReference,
options: Option<DeployOpts<'opts>>,
) -> Result<()> {
let cancellable = ostree::gio::NONE_CANCELLABLE;
let options = options.unwrap_or_default();
let repo = &sysroot.repo().unwrap();
let commit = &pull_idempotent(repo, imgref).await?;
let origin = glib::KeyFile::new();
origin.set_string("ostree", ORIGIN_CONTAINER, &imgref.to_string());
let deployment = &sysroot.deploy_tree(
Some(stateroot),
commit,
Some(&origin),
None,
options.kargs.unwrap_or_default(),
cancellable,
)?;
let flags = ostree::SysrootSimpleWriteDeploymentFlags::NONE;
sysroot.simple_write_deployment(Some(stateroot), deployment, None, flags, cancellable)?;
sysroot.cleanup(cancellable)?;
Ok(())
}
1 change: 1 addition & 0 deletions lib/src/container/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ impl std::fmt::Display for OstreeImageReference {
}
}

pub mod deploy;
mod export;
pub use export::*;
mod import;
Expand Down
68 changes: 51 additions & 17 deletions lib/src/container/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use super::*;
use crate::refescape;
use anyhow::{anyhow, Context};
use fn_error_context::context;
use ostree::gio;
use ostree::prelude::{Cast, ToVariant};
use ostree::{gio, glib};
use std::collections::{BTreeMap, HashMap};

/// The ostree ref prefix for blobs.
Expand Down Expand Up @@ -105,6 +105,17 @@ pub struct CompletedImport {
pub layer_filtered_content: BTreeMap<String, BTreeMap<String, u32>>,
}

// Given a manifest, compute its ostree ref name and cached ostree commit
fn query_layer(repo: &ostree::Repo, layer: ManifestLayer) -> Result<ManifestLayerState> {
let ostree_ref = ref_for_layer(&layer)?;
let commit = repo.resolve_rev(&ostree_ref, true)?.map(|s| s.to_string());
Ok(ManifestLayerState {
layer,
ostree_ref,
commit,
})
}

impl LayeredImageImporter {
/// Create a new importer.
pub async fn new(repo: &ostree::Repo, imgref: &OstreeImageReference) -> Result<Self> {
Expand All @@ -119,20 +130,6 @@ impl LayeredImageImporter {
})
}

// Given a manifest, compute its ostree ref name and cached ostree commit
fn query_layer(&self, layer: ManifestLayer) -> Result<ManifestLayerState> {
let ostree_ref = ref_for_layer(&layer)?;
let commit = self
.repo
.resolve_rev(&ostree_ref, true)?
.map(|s| s.to_string());
Ok(ManifestLayerState {
layer,
ostree_ref,
commit,
})
}

/// Determine if there is a new manifest, and if so return its digest.
#[context("Fetching manifest")]
pub async fn prepare(&mut self) -> Result<PrepareResult> {
Expand Down Expand Up @@ -173,10 +170,10 @@ impl LayeredImageImporter {
let mut layers = manifest.layers.iter().cloned();
// We require a base layer.
let base_layer = layers.next().ok_or_else(|| anyhow!("No layers found"))?;
let base_layer = self.query_layer(base_layer)?;
let base_layer = query_layer(&self.repo, base_layer)?;

let layers: Result<Vec<_>> = layers
.map(|layer| -> Result<_> { self.query_layer(layer) })
.map(|layer| -> Result<_> { query_layer(&self.repo, layer) })
.collect();
let layers = layers?;

Expand Down Expand Up @@ -307,6 +304,43 @@ pub fn list_images(repo: &ostree::Repo) -> Result<Vec<String>> {
Ok(r?)
}

/// Copy a downloaded image from one repository to another.
pub async fn copy(
src_repo: &ostree::Repo,
dest_repo: &ostree::Repo,
imgref: &OstreeImageReference,
) -> Result<()> {
let ostree_ref = ref_for_image(&imgref.imgref)?;
let rev = src_repo.resolve_rev(&ostree_ref, false)?.unwrap();
let (commit_obj, _) = src_repo.load_commit(rev.as_str())?;
let commit_meta = &commit_obj.child_value(0);
let commit_meta = glib::VariantDict::new(Some(commit_meta));
let manifest_bytes = commit_meta
.lookup::<String>(META_MANIFEST)?
.ok_or_else(|| anyhow!("Failed to find {} metadata key", META_MANIFEST))?;
let manifest: oci::Manifest = serde_json::from_str(&manifest_bytes)?;
// Create a task to copy each layer, plus the final ref
let layer_refs = manifest
.layers
.iter()
.map(|layer| ref_for_layer(&layer))
.chain(std::iter::once(Ok(ostree_ref)));
for ostree_ref in layer_refs {
let ostree_ref = ostree_ref?;
let src_repo = src_repo.clone();
let dest_repo = dest_repo.clone();
crate::tokio_util::spawn_blocking_cancellable(move |cancellable| -> Result<_> {
let cancellable = Some(cancellable);
let srcfd = &format!("file:///proc/self/fd/{}", src_repo.dfd());
let flags = ostree::RepoPullFlags::NONE;
dest_repo.pull(&srcfd, &[ostree_ref.as_str()], flags, None, cancellable)?;
Ok(())
})
.await??;
}
Ok(())
}

/// Remove the specified images and their corresponding blobs.
pub fn prune_images(_repo: &ostree::Repo, _imgs: &[&str]) -> Result<()> {
// Most robust approach is to iterate over all known images, load the
Expand Down
14 changes: 14 additions & 0 deletions lib/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,20 @@ async fn test_container_write_derive() -> Result<()> {
};
assert_eq!(import.commit, already_present);

// Create a new repo, and copy to it
let destrepo2 = ostree::Repo::create_at(
ostree::AT_FDCWD,
fixture.path.join("destrepo2").as_str(),
ostree::RepoMode::Archive,
None,
gio::NONE_CANCELLABLE,
)?;
ostree_ext::container::store::copy(&fixture.destrepo, &destrepo2, &exampleos_ref).await?;

let images = ostree_ext::container::store::list_images(&destrepo2)?;
assert_eq!(images.len(), 1);
assert_eq!(images[0], exampleos_ref.imgref.to_string());

Ok(())
}

Expand Down

0 comments on commit d60de71

Please sign in to comment.