From 54a011df408b8ae6e181a60729293bbdcc8eae62 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 11 May 2021 17:57:43 -0400 Subject: [PATCH] Update to ostree-ext v0.1.2, add new `ex-container` command This new `rpm-ostree ex-container` CLI is just code copied from the `ostree-ext-cli container` binary code. In the future I may just add the CLI code as a library API too to simplify this. For now, I don't want to try to add a new Rust CLI as an RPM package for example. This exposes it via rpm-ostree, and in the future rpm-ostree may have some layering on top of this anyways. --- Cargo.lock | 2 + Cargo.toml | 2 + rust/src/container.rs | 104 ++++++++++++++++++++++++ rust/src/lib.rs | 1 + rust/src/main.rs | 1 + tests/kolainst/destructive/ex-container | 37 +++++++++ 6 files changed, 147 insertions(+) create mode 100644 rust/src/container.rs create mode 100755 tests/kolainst/destructive/ex-container diff --git a/Cargo.lock b/Cargo.lock index 3b177d5f..96f8c8db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1532,6 +1532,7 @@ dependencies = [ "env_logger", "envsubst", "fn-error-context", + "futures", "gio", "gio-sys", "glib", @@ -1566,6 +1567,7 @@ dependencies = [ "system-deps 3.1.1", "systemd", "tempfile", + "tokio", "tracing", "tracing-subscriber", ] diff --git a/Cargo.toml b/Cargo.toml index d29c6f18..3f514691 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ cxx = "1.0.49" envsubst = "0.2.0" env_logger = "0.8.3" fn-error-context = "0.1.2" +futures = "0.3.15" gio = "0.9.1" gio-sys = "0.10.1" gobject-sys = "0.10.0" @@ -70,6 +71,7 @@ systemd = "0.8.2" tempfile = "3.2.0" tracing = "0.1" tracing-subscriber = "0.2" +tokio = { version = "1.6.0", features = ["full"] } [build-dependencies] anyhow = "1.0" diff --git a/rust/src/container.rs b/rust/src/container.rs new file mode 100644 index 00000000..afa12f68 --- /dev/null +++ b/rust/src/container.rs @@ -0,0 +1,104 @@ +//! CLI exposing `ostree-rs-ext container` + +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use std::convert::TryInto; + +use anyhow::{Context, Result}; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +enum Opts { + /// Import an ostree commit embedded in a remote container image + Import { + /// Path to the repository + #[structopt(long)] + repo: String, + + /// Image reference, e.g. registry:quay.io/exampleos/exampleos:latest + imgref: String, + }, + + /// Print information about an exported ostree-container image. + Info { + /// Image reference, e.g. registry:quay.io/exampleos/exampleos:latest + imgref: String, + }, + + /// Export an ostree commit to an OCI layout + Export { + /// Path to the repository + #[structopt(long)] + repo: String, + + /// The ostree ref or commit to export + rev: String, + + /// Image reference, e.g. registry:quay.io/exampleos/exampleos:latest + imgref: String, + }, +} + +async fn container_import(repo: &str, imgref: &str) -> Result<()> { + let repo = &ostree::Repo::open_at(libc::AT_FDCWD, repo, gio::NONE_CANCELLABLE)?; + let imgref = imgref.try_into()?; + let (tx_progress, rx_progress) = tokio::sync::watch::channel(Default::default()); + let target = indicatif::ProgressDrawTarget::stdout(); + let style = indicatif::ProgressStyle::default_bar(); + let pb = indicatif::ProgressBar::new_spinner(); + pb.set_draw_target(target); + pb.set_style(style.template("{spinner} {prefix} {msg}")); + pb.enable_steady_tick(200); + pb.set_message("Downloading..."); + let import = ostree_ext::container::import(repo, &imgref, Some(tx_progress)); + tokio::pin!(import); + tokio::pin!(rx_progress); + loop { + tokio::select! { + _ = rx_progress.changed() => { + let n = rx_progress.borrow().processed_bytes; + pb.set_message(&format!("Processed: {}", indicatif::HumanBytes(n))); + } + import = &mut import => { + pb.finish(); + println!("Imported: {}", import?.ostree_commit); + return Ok(()) + } + } + } +} + +async fn container_export(repo: &str, rev: &str, imgref: &str) -> Result<()> { + let repo = &ostree::Repo::open_at(libc::AT_FDCWD, repo, gio::NONE_CANCELLABLE)?; + let imgref = imgref.try_into()?; + let pushed = ostree_ext::container::export(repo, rev, &imgref).await?; + println!("{}", pushed); + Ok(()) +} + +async fn container_info(imgref: &str) -> Result<()> { + let imgref = imgref.try_into()?; + let info = ostree_ext::container::fetch_manifest_info(&imgref).await?; + println!("{} @{}", imgref, info.manifest_digest); + Ok(()) +} + +/// Main entrypoint for container +pub fn entrypoint(args: &[&str]) -> Result<()> { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .context("Failed to build tokio runtime")? + .block_on(async { + match Opts::from_iter(args.iter().skip(1)) { + Opts::Import { repo, imgref } => { + container_import(repo.as_str(), imgref.as_str()).await + } + Opts::Info { imgref } => container_info(imgref.as_str()).await, + Opts::Export { repo, rev, imgref } => { + container_export(repo.as_str(), rev.as_str(), imgref.as_str()).await + } + } + })?; + Ok(()) +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 58632827..ae858223 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -519,6 +519,7 @@ pub(crate) use bwrap::*; mod client; pub(crate) use client::*; mod cliwrap; +pub mod container; pub use cliwrap::*; mod composepost; pub mod countme; diff --git a/rust/src/main.rs b/rust/src/main.rs index 983d57ad..9c2eb523 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -7,6 +7,7 @@ fn inner_main(args: &Vec<&str>) -> Result<()> { match args.get(1).map(|s| *s) { // Add custom Rust commands here, and also in `libmain.cxx` if user-visible. Some("countme") => rpmostree_rust::countme::entrypoint(args), + Some("ex-container") => rpmostree_rust::container::entrypoint(args), _ => { // Otherwise fall through to C++ main(). Ok(rpmostree_rust::ffi::rpmostree_main(&args)?) diff --git a/tests/kolainst/destructive/ex-container b/tests/kolainst/destructive/ex-container new file mode 100755 index 00000000..896b7874 --- /dev/null +++ b/tests/kolainst/destructive/ex-container @@ -0,0 +1,37 @@ +#!/bin/bash +# +# Copyright (C) 2021 Red Hat Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +set -euo pipefail + +. ${KOLA_EXT_DATA}/libtest.sh + +set -x + +libtest_prepare_offline + +booted_commit=$(rpm-ostree status --json | jq -r '.deployments[0].checksum') +rpm-ostree ex-container export --repo=/ostree/repo ${booted_commit} containers-storage:localhost/fcos +target_sha256=$(podman run --entrypoint bash --rm -i localhost/fcos -c 'sha256sum /usr/bin/rpm-ostree' | cut -f 1 -d ' ') +src_sha256=$(sha256sum /usr/bin/rpm-ostree | cut -f 1 -d ' ') +assert_streq "${src_sha256}" "${target_sha256}" +echo "ok container export" + +# TODO test container import too, ideally via registry. But I don't want to +# make this test super flaky right now by pulling from the internet, and +# we need infrastructure to mock up serving via local registry.