diff --git a/rust/src/lib.rs b/rust/src/lib.rs index ac970a04fc..cc4b0d7f47 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -127,6 +127,11 @@ pub mod ffi { fn cliwrap_destdir() -> String; } + // sysroot_upgrade.rs + extern "Rust" { + fn import_container(sysroot: Pin<&mut OstreeSysroot>, imgref: String) -> Result; + } + // core.rs extern "Rust" { type TempEtcGuard; @@ -583,6 +588,8 @@ pub(crate) use self::console_progress::*; mod progress; mod scripts; pub(crate) use self::scripts::*; +mod sysroot_upgrade; +pub(crate) use crate::sysroot_upgrade::*; mod rpmutils; pub(crate) use self::rpmutils::*; mod testutils; diff --git a/rust/src/sysroot_upgrade.rs b/rust/src/sysroot_upgrade.rs new file mode 100644 index 0000000000..7105325995 --- /dev/null +++ b/rust/src/sysroot_upgrade.rs @@ -0,0 +1,28 @@ +//! Rust portion of `rpmostree-sysroot-upgrader.cxx`. + +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use crate::cxxrsutil::*; +use anyhow::{Context, Result}; +use std::convert::TryInto; +use std::pin::Pin; + +/// Import ostree commit in container image using ostree-rs-ext's API. +pub fn import_container( + mut sysroot: Pin<&mut crate::FFIOstreeSysroot>, + imgref: String, +) -> Result { + let sysroot = &sysroot.gobj_wrap(); + let repo = &sysroot.get_repo(gio::NONE_CANCELLABLE)?; + let imgref = imgref.as_str().try_into()?; + let commit = build_runtime()? + .block_on(async { ostree_ext::container::import(&repo, &imgref, None).await })?; + Ok(commit.ostree_commit) +} + +fn build_runtime() -> Result { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .context("Failed to build tokio runtime") +} diff --git a/src/app/rpmostree-builtin-rebase.cxx b/src/app/rpmostree-builtin-rebase.cxx index 6b07b9ec66..bfe0ee204a 100644 --- a/src/app/rpmostree-builtin-rebase.cxx +++ b/src/app/rpmostree-builtin-rebase.cxx @@ -140,6 +140,11 @@ rpmostree_builtin_rebase (int argc, if (strlen (new_provided_refspec) == 0) return glnx_throw (error, "Refspec is empty"); + /* When using the container:// refspec type, if rebasing to a specific commit, we expect a + * specific digest tag in the refspec, not in a separate argument */ + if (revision && refspectype == RPMOSTREE_REFSPEC_TYPE_CONTAINER) + return glnx_throw (error, "Unexpected ostree revision alongside container refspec type"); + /* Check if remote refers to a local repo */ g_autofree char *local_repo_remote = NULL; if (G_IN_SET (refspectype, RPMOSTREE_REFSPEC_TYPE_OSTREE, diff --git a/src/app/rpmostree-builtin-status.cxx b/src/app/rpmostree-builtin-status.cxx index f331946c2e..af82f7b1ec 100644 --- a/src/app/rpmostree-builtin-status.cxx +++ b/src/app/rpmostree-builtin-status.cxx @@ -559,6 +559,7 @@ print_one_deployment (RPMOSTreeSysroot *sysroot_proxy, return TRUE; const gchar *origin_refspec; + RpmOstreeRefspecType refspectype = RPMOSTREE_REFSPEC_TYPE_OSTREE; g_autofree const gchar **origin_packages = NULL; g_autofree const gchar **origin_requested_packages = NULL; g_autofree const gchar **origin_requested_local_packages = NULL; @@ -568,6 +569,7 @@ print_one_deployment (RPMOSTreeSysroot *sysroot_proxy, g_autofree const gchar **origin_requested_base_local_replacements = NULL; if (g_variant_dict_lookup (dict, "origin", "&s", &origin_refspec)) { + g_variant_dict_lookup (dict, "refspec-type", "%u", &refspectype); origin_packages = lookup_array_and_canonicalize (dict, "packages"); origin_requested_packages = @@ -606,7 +608,6 @@ print_one_deployment (RPMOSTreeSysroot *sysroot_proxy, g_print ("%s ", is_booted ? libsd_special_glyph (BLACK_CIRCLE) : " "); - RpmOstreeRefspecType refspectype = RPMOSTREE_REFSPEC_TYPE_OSTREE; const char *custom_origin_url = NULL; const char *custom_origin_description = NULL; if (origin_refspec) @@ -637,6 +638,11 @@ print_one_deployment (RPMOSTreeSysroot *sysroot_proxy, g_print ("%s", origin_refspec); } break; + case RPMOSTREE_REFSPEC_TYPE_CONTAINER: + { + g_print ("%s", origin_refspec); + } + break; } } else diff --git a/src/daemon/rpmostree-sysroot-upgrader.cxx b/src/daemon/rpmostree-sysroot-upgrader.cxx index 0803b92b34..126f4d8f53 100644 --- a/src/daemon/rpmostree-sysroot-upgrader.cxx +++ b/src/daemon/rpmostree-sysroot-upgrader.cxx @@ -419,6 +419,16 @@ rpmostree_sysroot_upgrader_pull_base (RpmOstreeSysrootUpgrader *self, switch (refspec_type) { + case RPMOSTREE_REFSPEC_TYPE_CONTAINER: + { + if (override_commit) + return glnx_throw (error, "Specifying commit overrides for container:// is not supported"); + + auto commit = rpmostreecxx::import_container(*self->sysroot, std::string(refspec)); + + new_base_rev = strdup (commit.c_str()); + break; + } case RPMOSTREE_REFSPEC_TYPE_CHECKSUM: case RPMOSTREE_REFSPEC_TYPE_OSTREE: { diff --git a/src/daemon/rpmostreed-deployment-utils.cxx b/src/daemon/rpmostreed-deployment-utils.cxx index de3eb47f52..d26a83f2b6 100644 --- a/src/daemon/rpmostreed-deployment-utils.cxx +++ b/src/daemon/rpmostreed-deployment-utils.cxx @@ -253,6 +253,7 @@ rpmostreed_deployment_generate_variant (OstreeSysroot *sysroot, RpmOstreeRefspecType refspec_type; g_autofree char *refspec = rpmostree_origin_get_full_refspec (origin, &refspec_type); + g_assert (refspec); gboolean is_layered = FALSE; g_autofree char *base_checksum = NULL; @@ -301,8 +302,12 @@ rpmostreed_deployment_generate_variant (OstreeSysroot *sysroot, switch (refspec_type) { + case RPMOSTREE_REFSPEC_TYPE_CONTAINER: + g_variant_dict_insert (dict, "container-image-reference", "s", refspec); + break; case RPMOSTREE_REFSPEC_TYPE_CHECKSUM: { + g_variant_dict_insert (dict, "ostree-checksum", "s", refspec); g_autofree char *custom_origin_url = NULL; g_autofree char *custom_origin_description = NULL; rpmostree_origin_get_custom_description (origin, &custom_origin_url, @@ -315,6 +320,7 @@ rpmostreed_deployment_generate_variant (OstreeSysroot *sysroot, break; case RPMOSTREE_REFSPEC_TYPE_OSTREE: { + g_variant_dict_insert (dict, "ostree-refspec", "s", refspec); if (!variant_add_remote_status (repo, refspec, base_checksum, dict, error)) return NULL; @@ -339,9 +345,6 @@ rpmostreed_deployment_generate_variant (OstreeSysroot *sysroot, break; } - if (refspec) - g_variant_dict_insert (dict, "origin", "s", refspec); - variant_add_from_hash_table (dict, "requested-packages", rpmostree_origin_get_packages (origin)); variant_add_from_hash_table (dict, "requested-local-packages", diff --git a/src/daemon/rpmostreed-transaction-types.cxx b/src/daemon/rpmostreed-transaction-types.cxx index fa2c05f3aa..5bea5cb25f 100644 --- a/src/daemon/rpmostreed-transaction-types.cxx +++ b/src/daemon/rpmostreed-transaction-types.cxx @@ -59,9 +59,37 @@ change_origin_refspec (GVariantDict *options, gchar **out_new_refspec, GError **error) { + RpmOstreeRefspecType refspectype; + if (!rpmostree_refspec_classify (refspec, &refspectype, error)) + return FALSE; + RpmOstreeRefspecType current_refspectype; g_autofree gchar *current_refspec = rpmostree_origin_get_full_refspec (origin, ¤t_refspectype); - + + switch (refspectype) + { + case RPMOSTREE_REFSPEC_TYPE_CONTAINER: + { + if (!rpmostree_origin_set_rebase (origin, refspec, error)) + return FALSE; + + if (current_refspectype == RPMOSTREE_REFSPEC_TYPE_CONTAINER + && strcmp (current_refspec, refspec) == 0) + return glnx_throw (error, "Old and new refs are equal: %s", refspec); + + if (out_old_refspec != NULL) + *out_old_refspec = current_refspec; + if (out_new_refspec != NULL) + *out_new_refspec = g_strdup (refspec); + return TRUE; + } + case RPMOSTREE_REFSPEC_TYPE_OSTREE: + break; + case RPMOSTREE_REFSPEC_TYPE_CHECKSUM: + break; + } + + /* The rest of the code assumes TYPE_OSTREE refspec */ g_autofree gchar *new_refspec = NULL; if (!rpmostreed_refspec_parse_partial (refspec, current_refspec, @@ -149,7 +177,12 @@ apply_revision_override (RpmostreedTransaction *transaction, rpmostree_origin_classify_refspec (origin, &refspectype, NULL); if (refspectype == RPMOSTREE_REFSPEC_TYPE_CHECKSUM) - return glnx_throw (error, "Cannot look up version while pinned to commit"); + return glnx_throw (error, "Cannot look up version/checksum while pinned to commit"); + + if (refspectype == RPMOSTREE_REFSPEC_TYPE_CONTAINER) + /* NB: Not supported for now, but We can perhaps support this if we allow `revision` to + * possibly be a tag or digest */ + return glnx_throw (error, "Cannot look up version/checksum while tracking a container image"); g_autofree char *checksum = NULL; g_autofree char *version = NULL; @@ -160,6 +193,8 @@ apply_revision_override (RpmostreedTransaction *transaction, { switch (refspectype) { + case RPMOSTREE_REFSPEC_TYPE_CONTAINER: + g_assert_not_reached (); /* Handled above */ case RPMOSTREE_REFSPEC_TYPE_OSTREE: { /* Perhaps down the line we'll drive history traversal into libostree */ @@ -183,6 +218,8 @@ apply_revision_override (RpmostreedTransaction *transaction, switch (refspectype) { + case RPMOSTREE_REFSPEC_TYPE_CONTAINER: + g_assert_not_reached (); /* Handled above */ case RPMOSTREE_REFSPEC_TYPE_OSTREE: if (!skip_branch_check) { @@ -1486,6 +1523,9 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, { g_autofree char *remote = NULL; g_autofree char *ref = NULL; + + /* TODO: Handle `container-image-reference` refspectype. Currently, a new OSTree ref is not + * written if refspecdata is e.g. `registry:quay.io/fcos` */ /* The actual rebase has already succeeded, so ignore errors. */ if (ostree_parse_refspec (old_refspec, &remote, &ref, NULL)) diff --git a/src/libpriv/rpmostree-core.h b/src/libpriv/rpmostree-core.h index ef7a3c1976..01bbfdd598 100644 --- a/src/libpriv/rpmostree-core.h +++ b/src/libpriv/rpmostree-core.h @@ -63,8 +63,13 @@ G_DECLARE_FINAL_TYPE (RpmOstreeTreespec, rpmostree_treespec, RPMOSTREE, TREESPEC typedef enum { RPMOSTREE_REFSPEC_TYPE_OSTREE, RPMOSTREE_REFSPEC_TYPE_CHECKSUM, + RPMOSTREE_REFSPEC_TYPE_CONTAINER, } RpmOstreeRefspecType; +#define RPMOSTREE_REFSPEC_OSTREE_ORIGIN_KEY "refspec" +#define RPMOSTREE_REFSPEC_OSTREE_BASE_ORIGIN_KEY "baserefspec" +#define RPMOSTREE_REFSPEC_CONTAINER_ORIGIN_KEY "container-image-reference" + gboolean rpmostree_refspec_classify (const char *refspec, RpmOstreeRefspecType *out_type, GError **error); diff --git a/src/libpriv/rpmostree-origin.cxx b/src/libpriv/rpmostree-origin.cxx index 4846597b8a..f6d293f645 100644 --- a/src/libpriv/rpmostree-origin.cxx +++ b/src/libpriv/rpmostree-origin.cxx @@ -115,23 +115,45 @@ rpmostree_origin_parse_keyfile (GKeyFile *origin, ret->cached_unconfigured_state = g_key_file_get_string (ret->kf, "origin", "unconfigured-state", NULL); - g_autofree char *refspec = g_key_file_get_string (ret->kf, "origin", "refspec", NULL); - if (!refspec) + /* Note that the refspec type can be inferred from the key in the orgin file, where + * the `RPMOSTREE_REFSPEC_OSTREE_ORIGIN_KEY` and `RPMOSTREE_REFSPEC_OSTREE_BASE_ORIGIN_KEY` keys + * may correspond to either TYPE_OSTREE or TYPE_CHECKSUM (further classification is required), and + * `RPMOSTREE_REFSPEC_CONTAINER_ORIGIN_KEY` always only corresponds to TYPE_CONTAINER. */ + g_autofree char *ost_refspec = g_key_file_get_string (ret->kf, "origin", RPMOSTREE_REFSPEC_OSTREE_ORIGIN_KEY, NULL); + g_autofree char *imgref = g_key_file_get_string (ret->kf, "origin", RPMOSTREE_REFSPEC_CONTAINER_ORIGIN_KEY, NULL); + if (!ost_refspec) { - refspec = g_key_file_get_string (ret->kf, "origin", "baserefspec", NULL); - if (!refspec) - return (RpmOstreeOrigin *)glnx_null_throw (error, "No origin/refspec, or origin/baserefspec in current deployment origin; cannot handle via rpm-ostree"); + /* See if refspec if of TYPE_CHECKSUM */ + ost_refspec = g_key_file_get_string (ret->kf, "origin", RPMOSTREE_REFSPEC_OSTREE_BASE_ORIGIN_KEY, NULL); + if (!ost_refspec && !imgref) + return (RpmOstreeOrigin *)glnx_null_throw (error, + "No origin/%s, origin/%s, or origin/%s " + "in current deployment origin; cannot handle via rpm-ostree", + RPMOSTREE_REFSPEC_OSTREE_ORIGIN_KEY, + RPMOSTREE_REFSPEC_OSTREE_BASE_ORIGIN_KEY, + RPMOSTREE_REFSPEC_CONTAINER_ORIGIN_KEY); + } + if (ost_refspec && imgref) + return (RpmOstreeOrigin *)glnx_null_throw (error, + "Refspec expressed by multiple keys in deployment origin"); + else if (ost_refspec) + { + /* Classify to distinguish between TYPE_CHECKSUM and TYPE_OSTREE */ + if (!rpmostree_refspec_classify (ost_refspec, &ret->refspec_type, error)) + return FALSE; + ret->cached_refspec = util::move_nullify (ost_refspec); + ret->cached_override_commit = + g_key_file_get_string (ret->kf, "origin", "override-commit", NULL); + } + else if (imgref) + { + ret->refspec_type = RPMOSTREE_REFSPEC_TYPE_CONTAINER; + ret->cached_refspec = util::move_nullify (imgref); + } + else + { + g_assert_not_reached (); } - - if (!rpmostree_refspec_classify (refspec, &ret->refspec_type, error)) - return FALSE; - /* Note the lack of a prefix here so that code that just calls - * rpmostree_origin_get_refspec() in the ostree:// case - * sees it without the prefix for compatibility. - */ - ret->cached_refspec = util::move_nullify (refspec); - ret->cached_override_commit = - g_key_file_get_string (ret->kf, "origin", "override-commit", NULL); if (!parse_packages_strv (ret->kf, "packages", "requested", FALSE, ret->cached_packages, error)) @@ -495,8 +517,7 @@ rpmostree_origin_set_rebase_custom (RpmOstreeOrigin *origin, } /* We don't want to carry any commit overrides or version pinning during a - * rebase by default. - */ + * rebase by default. */ rpmostree_origin_set_override_commit (origin, NULL, NULL); /* See related code in rpmostree_origin_parse_keyfile() */ @@ -504,14 +525,21 @@ rpmostree_origin_set_rebase_custom (RpmOstreeOrigin *origin, return FALSE; g_free (origin->cached_refspec); origin->cached_refspec = g_strdup (new_refspec); + /* Note the following code sets different keys depending on the type of refspec; + * TYPE_OSTREE and TYPE_CHECKSUM may set either `RPMOSTREE_REFSPEC_OSTREE_BASE_ORIGIN_KEY` + * or `RPMOSTREE_REFSPEC_OSTREE_ORIGIN_KEY`, while TYPE_CONTAINER will set the + * `RPMOSTREE_REFSPEC_CONTAINER_ORIGIN_KEY` key. */ switch (origin->refspec_type) { case RPMOSTREE_REFSPEC_TYPE_CHECKSUM: case RPMOSTREE_REFSPEC_TYPE_OSTREE: { + /* Remove `TYPE_CONTAINER`-related keys */ + g_key_file_remove_key (origin->kf, "origin", RPMOSTREE_REFSPEC_CONTAINER_ORIGIN_KEY, NULL); + const char *refspec_key = - g_key_file_has_key (origin->kf, "origin", "baserefspec", NULL) ? - "baserefspec" : "refspec"; + g_key_file_has_key (origin->kf, "origin", , NULL) ? + RPMOSTREE_REFSPEC_OSTREE_BASE_ORIGIN_KEY : RPMOSTREE_REFSPEC_OSTREE_ORIGIN_KEY; g_key_file_set_string (origin->kf, "origin", refspec_key, origin->cached_refspec); if (!custom_origin_url) { @@ -528,6 +556,18 @@ rpmostree_origin_set_rebase_custom (RpmOstreeOrigin *origin, } } break; + case RPMOSTREE_REFSPEC_TYPE_CONTAINER: + { + /* Remove `TYPE_OSTREE` and `TYPE_CHECKSUM`-related keys */ + g_assert (!custom_origin_url); + g_key_file_remove_key (origin->kf, "origin", RPMOSTREE_REFSPEC_OSTREE_ORIGIN_KEY, NULL); + g_key_file_remove_key (origin->kf, "origin", RPMOSTREE_REFSPEC_OSTREE_BASE_ORIGIN_KEY, NULL); + g_key_file_remove_key (origin->kf, "origin", "custom-url", NULL); + g_key_file_remove_key (origin->kf, "origin", "custom-description", NULL); + + g_key_file_set_string (origin->kf, "origin", RPMOSTREE_REFSPEC_CONTAINER_ORIGIN_KEY, origin->cached_refspec); + } + break; } return TRUE;