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

Commit 4eb9203

Browse files
committed
container/encapsulate: Support copying commit metadata keys
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.
1 parent a6b23c2 commit 4eb9203

File tree

3 files changed

+58
-9
lines changed

3 files changed

+58
-9
lines changed

lib/src/cli.rs

+12-3
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ enum ContainerOpts {
110110
#[structopt(name = "label", long, short)]
111111
labels: Vec<String>,
112112

113+
/// Propagate an OSTree commit metadata key to container label
114+
#[structopt(name = "copymeta", long)]
115+
copy_meta_keys: Vec<String>,
116+
113117
/// Corresponds to the Dockerfile `CMD` instruction.
114118
#[structopt(long)]
115119
cmd: Option<Vec<String>>,
@@ -365,15 +369,19 @@ async fn container_export(
365369
rev: &str,
366370
imgref: &ImageReference,
367371
labels: BTreeMap<String, String>,
372+
copy_meta_keys: Vec<String>,
368373
cmd: Option<Vec<String>>,
369374
) -> Result<()> {
370375
let repo = &ostree::Repo::open_at(libc::AT_FDCWD, repo, gio::NONE_CANCELLABLE)?;
371376
let config = Config {
372377
labels: Some(labels),
373378
cmd,
374379
};
375-
let opts = Some(Default::default());
376-
let pushed = crate::container::encapsulate(repo, rev, &config, opts, imgref).await?;
380+
let opts = crate::container::ExportOpts {
381+
copy_meta_keys,
382+
..Default::default()
383+
};
384+
let pushed = crate::container::encapsulate(repo, rev, &config, Some(opts), imgref).await?;
377385
println!("{}", pushed);
378386
Ok(())
379387
}
@@ -492,6 +500,7 @@ where
492500
rev,
493501
imgref,
494502
labels,
503+
copy_meta_keys,
495504
cmd,
496505
} => {
497506
let labels: Result<BTreeMap<_, _>> = labels
@@ -503,7 +512,7 @@ where
503512
Ok((k.to_string(), v.to_string()))
504513
})
505514
.collect();
506-
container_export(&repo, &rev, &imgref, labels?, cmd).await
515+
container_export(&repo, &rev, &imgref, labels?, copy_meta_keys, cmd).await
507516
}
508517
ContainerOpts::Image(opts) => match opts {
509518
ContainerImageOpts::List { repo } => {

lib/src/container/encapsulate.rs

+36-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ use super::{ocidir, OstreeImageReference, Transport};
55
use super::{ImageReference, SignatureSource, OSTREE_COMMIT_LABEL};
66
use crate::container::skopeo;
77
use crate::tar as ostree_tar;
8-
use anyhow::Context;
9-
use anyhow::Result;
8+
use anyhow::{anyhow, Context, Result};
109
use fn_error_context::context;
1110
use gio::glib;
1211
use oci_spec::image as oci_image;
@@ -22,7 +21,6 @@ use tracing::{instrument, Level};
2221
/// schema, it's not actually useful today. But, we keep it
2322
/// out of principle.
2423
const BLOB_OSTREE_ANNOTATION: &str = "ostree.encapsulated";
25-
2624
/// Configuration for the generated container.
2725
#[derive(Debug, Default)]
2826
pub struct Config {
@@ -46,6 +44,32 @@ fn export_ostree_ref(
4644
w.complete()
4745
}
4846

47+
fn commit_meta_to_labels<'a>(
48+
meta: &glib::VariantDict,
49+
keys: impl IntoIterator<Item = &'a str>,
50+
labels: &mut HashMap<String, String>,
51+
) -> Result<()> {
52+
for k in keys {
53+
let v = meta
54+
.lookup::<String>(k)
55+
.context("Expected string for commit metadata value")?
56+
.ok_or_else(|| anyhow!("Could not find commit metadata key: {}", k))?;
57+
labels.insert(k.to_string(), v);
58+
}
59+
// Copy standard metadata keys `ostree.bootable` and `ostree.linux`.
60+
// Bootable is an odd one out in being a boolean.
61+
if let Some(v) = meta.lookup::<bool>(*ostree::METADATA_KEY_BOOTABLE)? {
62+
labels.insert(ostree::METADATA_KEY_BOOTABLE.to_string(), v.to_string());
63+
}
64+
// Handle any other string-typed values here.
65+
for k in &[&ostree::METADATA_KEY_LINUX] {
66+
if let Some(v) = meta.lookup::<String>(k)? {
67+
labels.insert(k.to_string(), v);
68+
}
69+
}
70+
Ok(())
71+
}
72+
4973
/// Generate an OCI image from a given ostree root
5074
#[context("Building oci")]
5175
fn build_oci(
@@ -76,6 +100,13 @@ fn build_oci(
76100
let mut ctrcfg = oci_image::Config::default();
77101
let mut imgcfg = oci_image::ImageConfiguration::default();
78102
let labels = ctrcfg.labels_mut().get_or_insert_with(Default::default);
103+
104+
commit_meta_to_labels(
105+
&commit_meta,
106+
opts.copy_meta_keys.iter().map(|k| k.as_str()),
107+
labels,
108+
)?;
109+
79110
let mut manifest = ocidir::new_empty_manifest().build().unwrap();
80111

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

203236
/// Given an OSTree repository and ref, generate a container image.

lib/tests/it/main.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ fn generate_test_repo(dir: &Utf8Path) -> Result<Utf8PathBuf> {
5454
indoc! {"
5555
cd {dir}
5656
ostree --repo=repo init --mode=archive
57-
ostree --repo=repo commit -b {testref} --bootable --no-bindings --add-metadata-string=version=42.0 --gpg-homedir={gpghome} --gpg-sign={keyid} \
58-
--add-detached-metadata-string=my-detached-key=my-detached-value --tree=tar=exampleos.tar.zst >/dev/null
57+
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} \
58+
--add-detached-metadata-string=my-detached-key=my-detached-value --tree=tar=exampleos.tar.zst >/dev/null
5959
ostree --repo=repo show {testref} >/dev/null
6060
"},
6161
testref = TESTREF,
@@ -464,11 +464,15 @@ async fn test_container_import_export() -> Result<()> {
464464
),
465465
cmd: Some(vec!["/bin/bash".to_string()]),
466466
};
467+
let opts = ostree_ext::container::ExportOpts {
468+
copy_meta_keys: vec!["buildsys.checksum".to_string()],
469+
..Default::default()
470+
};
467471
let digest = ostree_ext::container::encapsulate(
468472
&fixture.srcrepo,
469473
TESTREF,
470474
&config,
471-
None,
475+
Some(opts),
472476
&srcoci_imgref,
473477
)
474478
.await
@@ -479,6 +483,9 @@ async fn test_container_import_export() -> Result<()> {
479483
assert!(inspect.contains(r#""version": "42.0""#));
480484
assert!(inspect.contains(r#""foo": "bar""#));
481485
assert!(inspect.contains(r#""test": "value""#));
486+
assert!(inspect.contains(
487+
r#""buildsys.checksum": "41af286dc0b172ed2f1ca934fd2278de4a1192302ffa07087cea2682e7d372e3""#
488+
));
482489

483490
let srcoci_unverified = OstreeImageReference {
484491
sigverify: SignatureSource::ContainerPolicyAllowInsecure,

0 commit comments

Comments
 (0)