Skip to content

Commit

Permalink
container/encapsulate: Support copying commit metadata keys
Browse files Browse the repository at this point in the history
For coreos/coreos-assembler#2685
we want to copy e.g. `rpmostree.input-hash` into the container image.
Extend the `ExportOpts` struct to support this, and also expose
it via the CLI, e.g.
`ostree container encapsulate --copymeta=rpmostree.input-hash ...`.

And while I was thinking about this...we should by default copy
some core ostree keys, such as `ostree.bootable` and `ostree.linux`
since they are key pieces of metadata.
  • Loading branch information
cgwalters committed Feb 3, 2022
1 parent a6b23c2 commit d074754
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 8 deletions.
15 changes: 12 additions & 3 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ enum ContainerOpts {
#[structopt(name = "label", long, short)]
labels: Vec<String>,

/// Propagate an OSTree commit metadata key to container label
#[structopt(name = "copymeta", long)]
copy_meta_keys: Vec<String>,

/// Corresponds to the Dockerfile `CMD` instruction.
#[structopt(long)]
cmd: Option<Vec<String>>,
Expand Down Expand Up @@ -365,15 +369,19 @@ async fn container_export(
rev: &str,
imgref: &ImageReference,
labels: BTreeMap<String, String>,
copy_meta_keys: Vec<String>,
cmd: Option<Vec<String>>,
) -> Result<()> {
let repo = &ostree::Repo::open_at(libc::AT_FDCWD, repo, gio::NONE_CANCELLABLE)?;
let config = Config {
labels: Some(labels),
cmd,
};
let opts = Some(Default::default());
let pushed = crate::container::encapsulate(repo, rev, &config, opts, imgref).await?;
let opts = crate::container::ExportOpts {
copy_meta_keys,
..Default::default()
};
let pushed = crate::container::encapsulate(repo, rev, &config, Some(opts), imgref).await?;
println!("{}", pushed);
Ok(())
}
Expand Down Expand Up @@ -492,6 +500,7 @@ where
rev,
imgref,
labels,
copy_meta_keys,
cmd,
} => {
let labels: Result<BTreeMap<_, _>> = labels
Expand All @@ -503,7 +512,7 @@ where
Ok((k.to_string(), v.to_string()))
})
.collect();
container_export(&repo, &rev, &imgref, labels?, cmd).await
container_export(&repo, &rev, &imgref, labels?, copy_meta_keys, cmd).await
}
ContainerOpts::Image(opts) => match opts {
ContainerImageOpts::List { repo } => {
Expand Down
39 changes: 36 additions & 3 deletions lib/src/container/encapsulate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use super::{ocidir, OstreeImageReference, Transport};
use super::{ImageReference, SignatureSource, OSTREE_COMMIT_LABEL};
use crate::container::skopeo;
use crate::tar as ostree_tar;
use anyhow::Context;
use anyhow::Result;
use anyhow::{anyhow, Context, Result};
use fn_error_context::context;
use gio::glib;
use oci_spec::image as oci_image;
Expand All @@ -22,7 +21,6 @@ use tracing::{instrument, Level};
/// schema, it's not actually useful today. But, we keep it
/// out of principle.
const BLOB_OSTREE_ANNOTATION: &str = "ostree.encapsulated";

/// Configuration for the generated container.
#[derive(Debug, Default)]
pub struct Config {
Expand All @@ -46,6 +44,32 @@ fn export_ostree_ref(
w.complete()
}

fn commit_meta_to_labels<'a>(
meta: &glib::VariantDict,
keys: impl IntoIterator<Item = &'a str>,
labels: &mut HashMap<String, String>,
) -> Result<()> {
for k in keys {
let v = meta
.lookup::<String>(k)
.context("Expected string for commit metadata value")?
.ok_or_else(|| anyhow!("Could not find commit metadata key: {}", k))?;
labels.insert(k.to_string(), v);
}
// Copy standard metadata keys `ostree.bootable` and `ostree.linux`.
// Bootable is an odd one out in being a boolean.
if let Some(v) = meta.lookup::<bool>(*ostree::METADATA_KEY_BOOTABLE)? {
labels.insert(ostree::METADATA_KEY_BOOTABLE.to_string(), v.to_string());
}
// Handle any other string-typed values here.
for k in &[&ostree::METADATA_KEY_LINUX] {
if let Some(v) = meta.lookup::<String>(k)? {
labels.insert(k.to_string(), v);
}
}
Ok(())
}

/// Generate an OCI image from a given ostree root
#[context("Building oci")]
fn build_oci(
Expand Down Expand Up @@ -76,6 +100,13 @@ fn build_oci(
let mut ctrcfg = oci_image::Config::default();
let mut imgcfg = oci_image::ImageConfiguration::default();
let labels = ctrcfg.labels_mut().get_or_insert_with(Default::default);

commit_meta_to_labels(
&commit_meta,
opts.copy_meta_keys.iter().map(|k| k.as_str()),
labels,
)?;

let mut manifest = ocidir::new_empty_manifest().build().unwrap();

if let Some(version) =
Expand Down Expand Up @@ -198,6 +229,8 @@ async fn build_impl(
pub struct ExportOpts {
/// If true, perform gzip compression of the tar layers.
pub compress: bool,
/// A set of commit metadata keys to copy as image labels.
pub copy_meta_keys: Vec<String>,
}

/// Given an OSTree repository and ref, generate a container image.
Expand Down
13 changes: 11 additions & 2 deletions lib/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ fn generate_test_repo(dir: &Utf8Path) -> Result<Utf8PathBuf> {
indoc! {"
cd {dir}
ostree --repo=repo init --mode=archive
ostree --repo=repo commit -b {testref} --bootable --no-bindings --add-metadata-string=version=42.0 --gpg-homedir={gpghome} --gpg-sign={keyid} \
ostree --repo=repo commit -b {testref} --bootable --no-bindings --add-metadata-string=version=42.0 \
--add-metadata-string=buildsys.checksum=41af286dc0b172ed2f1ca934fd2278de4a1192302ffa07087cea2682e7d372e3 \
--gpg-homedir={gpghome} --gpg-sign={keyid} \
--add-detached-metadata-string=my-detached-key=my-detached-value --tree=tar=exampleos.tar.zst >/dev/null
ostree --repo=repo show {testref} >/dev/null
"},
Expand Down Expand Up @@ -464,11 +466,15 @@ async fn test_container_import_export() -> Result<()> {
),
cmd: Some(vec!["/bin/bash".to_string()]),
};
let opts = ostree_ext::container::ExportOpts {
copy_meta_keys: vec!["buildsys.checksum".to_string()],
..Default::default()
};
let digest = ostree_ext::container::encapsulate(
&fixture.srcrepo,
TESTREF,
&config,
None,
Some(opts),
&srcoci_imgref,
)
.await
Expand All @@ -479,6 +485,9 @@ async fn test_container_import_export() -> Result<()> {
assert!(inspect.contains(r#""version": "42.0""#));
assert!(inspect.contains(r#""foo": "bar""#));
assert!(inspect.contains(r#""test": "value""#));
assert!(inspect.contains(
r#""buildsys.checksum": "41af286dc0b172ed2f1ca934fd2278de4a1192302ffa07087cea2682e7d372e3""#
));

let srcoci_unverified = OstreeImageReference {
sigverify: SignatureSource::ContainerPolicyAllowInsecure,
Expand Down

0 comments on commit d074754

Please sign in to comment.