diff --git a/0001-app-rebase-Support-local-repo-remotes.patch b/0001-app-rebase-Support-local-repo-remotes.patch new file mode 100644 index 0000000..743775b --- /dev/null +++ b/0001-app-rebase-Support-local-repo-remotes.patch @@ -0,0 +1,424 @@ +From 7cceb35219e5192b0cb37d8f46f7dfbd7401381e Mon Sep 17 00:00:00 2001 +From: Jonathan Lebon +Date: Mon, 4 Feb 2019 11:51:18 -0500 +Subject: [PATCH] app/rebase: Support local repo remotes + +Teach rpm-ostree to interpret rebases where the remote component is a +path to a local repo, e.g.: + + rpm-ostree rebase /mnt/ostree/repo:my/target/ref + +Essentially, the local remote in this case is considered "ephemeral". +It's kind of the equivalent of, on traditional systems: + + dnf install --repofrompath repo,/path/to/repodata ... + +The use case for this is in OpenShift v4, in which upgrades are done +from containers containing the OSTree commit. There, we want to point +RPM-OSTree directly at the repo in the mounted container and rebase to +the checksum. + +For now, the option is marked experimental. One major reason for this is +that the way we pass the repo differs on RHEL7 vs other platforms. (See +comment block in `rpmostree-dbus-helpers.c` for details). + +Related: https://github.com/openshift/machine-config-operator/issues/314 + +Co-authored-by: Colin Walters + +Closes: #1732 +Approved by: cgwalters +--- + configure.ac | 9 +++ + src/app/rpmostree-builtin-deploy.c | 1 + + src/app/rpmostree-builtin-rebase.c | 30 ++++++++- + src/app/rpmostree-builtin-reset.c | 2 +- + src/app/rpmostree-builtin-upgrade.c | 1 + + src/app/rpmostree-dbus-helpers.c | 24 +++++++ + src/app/rpmostree-dbus-helpers.h | 1 + + src/app/rpmostree-override-builtins.c | 1 + + src/app/rpmostree-pkg-builtins.c | 1 + + src/daemon/rpmostreed-transaction-types.c | 80 ++++++++++++++++++++++- + tests/vmcheck/test-misc-2.sh | 12 +++- + 11 files changed, 155 insertions(+), 7 deletions(-) + +diff --git a/configure.ac b/configure.ac +index d60430c0..e398eed1 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -135,6 +135,14 @@ AS_IF([pkg-config --atleast-version=4.14.2 rpm], [ + ]) + librpm_version="$(pkg-config rpm --modversion)" + ++AC_ARG_ENABLE(dfd_over_dbus, ++ AS_HELP_STRING([--enable-dfd-over-dbus], ++ [Enable passing dfd over D-Bus (default: yes)]),, ++ [enable_dfd_over_dbus=yes]) ++AS_IF([test x$enable_dfd_over_dbus = xyes], [ ++ AC_DEFINE(HAVE_DFD_OVER_DBUS, 1, [Define if we can pass dfd over D-Bus]) ++]) ++ + AC_PATH_PROG([XSLTPROC], [xsltproc]) + + GLIB_TESTS +@@ -303,4 +311,5 @@ echo " + gtk-doc: $enable_gtk_doc + rust: $rust_debug_release (lto: ${enable_lto:-no}) + cbindgen: ${cbindgen:-internal} ++ use dfd over D-Bus: $enable_dfd_over_dbus + " +diff --git a/src/app/rpmostree-builtin-deploy.c b/src/app/rpmostree-builtin-deploy.c +index 9cda3d6a..c2bd29a5 100644 +--- a/src/app/rpmostree-builtin-deploy.c ++++ b/src/app/rpmostree-builtin-deploy.c +@@ -130,6 +130,7 @@ rpmostree_builtin_deploy (int argc, + NULL, /* override replace */ + NULL, /* override remove */ + NULL, /* override reset */ ++ NULL, /* local_repo_remote */ + options, + &transaction_address, + cancellable, +diff --git a/src/app/rpmostree-builtin-rebase.c b/src/app/rpmostree-builtin-rebase.c +index e8deec50..9f38915a 100644 +--- a/src/app/rpmostree-builtin-rebase.c ++++ b/src/app/rpmostree-builtin-rebase.c +@@ -127,12 +127,37 @@ rpmostree_builtin_rebase (int argc, + new_provided_refspec = opt_branch; + } + ++ const char *remainder = NULL; + RpmOstreeRefspecType refspectype; +- if (!rpmostree_refspec_classify (new_provided_refspec, &refspectype, NULL, error)) ++ if (!rpmostree_refspec_classify (new_provided_refspec, &refspectype, &remainder, error)) + return FALSE; + if (!opt_experimental && refspectype == RPMOSTREE_REFSPEC_TYPE_ROJIG) + return glnx_throw (error, "rojig:// refspec requires --experimental"); + ++ /* catch "ostree://" or "rojig://"; we'd error out much later in the daemon otherwise */ ++ if (strlen (remainder) == 0) ++ return glnx_throw (error, "Refspec is empty"); ++ ++ /* Check if remote refers to a local repo */ ++ g_autofree char *local_repo_remote = NULL; ++ if (G_IN_SET (refspectype, RPMOSTREE_REFSPEC_TYPE_OSTREE, ++ RPMOSTREE_REFSPEC_TYPE_CHECKSUM)) ++ { ++ if (*remainder == '/') ++ { ++ if (!opt_experimental) ++ return glnx_throw (error, "Local repo rebase requires --experimental"); ++ const char *ref = strrchr (remainder, ':'); ++ if (!ref) ++ return glnx_throw (error, "Missing ':' in LOCALPATH:REF rebase"); ++ local_repo_remote = g_strndup (remainder, ref - remainder); ++ new_provided_refspec = ref + 1; ++ /* just don't support "/path/to/repo:" for now */ ++ if (strlen (new_provided_refspec) == 0) ++ return glnx_throw (error, "Missing REF in LOCALPATH:REF rebase"); ++ } ++ } ++ + g_autoptr(GVariant) previous_deployment = rpmostree_os_dup_default_deployment (os_proxy); + + GVariantDict dict; +@@ -153,7 +178,7 @@ rpmostree_builtin_rebase (int argc, + g_autoptr(GVariant) options = g_variant_ref_sink (g_variant_dict_end (&dict)); + + /* Use newer D-Bus API only if we have to. */ +- if (install_pkgs || uninstall_pkgs) ++ if (install_pkgs || uninstall_pkgs || local_repo_remote) + { + if (!rpmostree_update_deployment (os_proxy, + new_provided_refspec, +@@ -163,6 +188,7 @@ rpmostree_builtin_rebase (int argc, + NULL, /* override replace */ + NULL, /* override remove */ + NULL, /* override reset */ ++ local_repo_remote, + options, + &transaction_address, + cancellable, +diff --git a/src/app/rpmostree-builtin-reset.c b/src/app/rpmostree-builtin-reset.c +index 4b19db63..18b13f12 100644 +--- a/src/app/rpmostree-builtin-reset.c ++++ b/src/app/rpmostree-builtin-reset.c +@@ -103,7 +103,7 @@ rpmostree_builtin_reset (int argc, + g_autoptr(GVariant) options = g_variant_ref_sink (g_variant_dict_end (&dict)); + + if (!rpmostree_update_deployment (os_proxy, NULL, NULL, install_pkgs, uninstall_pkgs, +- NULL, NULL, NULL, options, &transaction_address, ++ NULL, NULL, NULL, NULL, options, &transaction_address, + cancellable, error)) + return FALSE; + +diff --git a/src/app/rpmostree-builtin-upgrade.c b/src/app/rpmostree-builtin-upgrade.c +index c52ff96f..cc56ae6d 100644 +--- a/src/app/rpmostree-builtin-upgrade.c ++++ b/src/app/rpmostree-builtin-upgrade.c +@@ -175,6 +175,7 @@ rpmostree_builtin_upgrade (int argc, + NULL, /* override replace */ + NULL, /* override remove */ + NULL, /* override reset */ ++ NULL, /* local_repo_remote */ + options, + &transaction_address, + cancellable, +diff --git a/src/app/rpmostree-dbus-helpers.c b/src/app/rpmostree-dbus-helpers.c +index 0748b887..42b28914 100644 +--- a/src/app/rpmostree-dbus-helpers.c ++++ b/src/app/rpmostree-dbus-helpers.c +@@ -1123,6 +1123,7 @@ get_modifiers_variant (const char *set_refspec, + const char *const* override_replace_pkgs, + const char *const* override_remove_pkgs, + const char *const* override_reset_pkgs, ++ const char *local_repo_remote, + GVariant **out_modifiers, + GUnixFDList **out_fd_list, + GError **error) +@@ -1154,6 +1155,27 @@ get_modifiers_variant (const char *set_refspec, + vardict_insert_strv (&dict, "override-remove-packages", override_remove_pkgs); + vardict_insert_strv (&dict, "override-reset-packages", override_reset_pkgs); + ++ if (local_repo_remote) ++ { ++ /* Unfortunately, we can't pass an fd to a dir through D-Bus on el7 right now. So ++ * there, we just pass the path. Once that's fixed (or we no longer care about ++ * supporting this feature on el7), we can drop this buildopt. See: ++ * https://bugzilla.redhat.com/show_bug.cgi?id=1672404 */ ++#ifdef HAVE_DFD_OVER_DBUS ++ glnx_fd_close int repo_dfd = -1; ++ if (!glnx_opendirat (AT_FDCWD, local_repo_remote, TRUE, &repo_dfd, error)) ++ return FALSE; ++ ++ int idx = g_unix_fd_list_append (fd_list, repo_dfd, error); ++ if (idx < 0) ++ return FALSE; ++ ++ g_variant_dict_insert (&dict, "ex-local-repo-remote", "h", idx); ++#else ++ g_variant_dict_insert (&dict, "ex-local-repo-remote", "s", local_repo_remote); ++#endif ++ } ++ + *out_fd_list = g_steal_pointer (&fd_list); + *out_modifiers = g_variant_ref_sink (g_variant_dict_end (&dict)); + return TRUE; +@@ -1168,6 +1190,7 @@ rpmostree_update_deployment (RPMOSTreeOS *os_proxy, + const char *const* override_replace_pkgs, + const char *const* override_remove_pkgs, + const char *const* override_reset_pkgs, ++ const char *local_repo_remote, + GVariant *options, + char **out_transaction_address, + GCancellable *cancellable, +@@ -1180,6 +1203,7 @@ rpmostree_update_deployment (RPMOSTreeOS *os_proxy, + override_replace_pkgs, + override_remove_pkgs, + override_reset_pkgs, ++ local_repo_remote, + &modifiers, &fd_list, error)) + return FALSE; + +diff --git a/src/app/rpmostree-dbus-helpers.h b/src/app/rpmostree-dbus-helpers.h +index 89e6b729..539589c6 100644 +--- a/src/app/rpmostree-dbus-helpers.h ++++ b/src/app/rpmostree-dbus-helpers.h +@@ -108,6 +108,7 @@ rpmostree_update_deployment (RPMOSTreeOS *os_proxy, + const char *const* override_replace_pkgs, + const char *const* override_remove_pkgs, + const char *const* override_reset_pkgs, ++ const char *local_repo_remote, + GVariant *options, + char **out_transaction_address, + GCancellable *cancellable, +diff --git a/src/app/rpmostree-override-builtins.c b/src/app/rpmostree-override-builtins.c +index 7cd4eb9b..9e2d217c 100644 +--- a/src/app/rpmostree-override-builtins.c ++++ b/src/app/rpmostree-override-builtins.c +@@ -94,6 +94,7 @@ handle_override (RPMOSTreeSysroot *sysroot_proxy, + override_replace, + override_remove, + override_reset, ++ NULL, + options, + &transaction_address, + cancellable, +diff --git a/src/app/rpmostree-pkg-builtins.c b/src/app/rpmostree-pkg-builtins.c +index 469a9c94..2e1274f6 100644 +--- a/src/app/rpmostree-pkg-builtins.c ++++ b/src/app/rpmostree-pkg-builtins.c +@@ -114,6 +114,7 @@ pkg_change (RpmOstreeCommandInvocation *invocation, + NULL, /* override replace */ + NULL, /* override remove */ + NULL, /* override reset */ ++ NULL, /* local_repo_remote */ + options, + &transaction_address, + cancellable, +diff --git a/src/daemon/rpmostreed-transaction-types.c b/src/daemon/rpmostreed-transaction-types.c +index a26ebdd9..e817d25d 100644 +--- a/src/daemon/rpmostreed-transaction-types.c ++++ b/src/daemon/rpmostreed-transaction-types.c +@@ -595,6 +595,7 @@ typedef struct { + GUnixFDList *override_replace_local_pkgs; + char **override_remove_pkgs; /* strv but strings owned by modifiers */ + char **override_reset_pkgs; /* strv but strings owned by modifiers */ ++ int local_repo_remote_dfd; + } DeployTransaction; + + typedef RpmostreedTransactionClass DeployTransactionClass; +@@ -851,6 +852,47 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, + g_autoptr(RpmOstreeOrigin) origin = + rpmostree_sysroot_upgrader_dup_origin (upgrader); + ++ /* Handle local repo remotes immediately; the idea is that the remote is "transient" ++ * (otherwise, one should set up a proper file:/// remote), so we only support rebasing to ++ * a checksum. We don't want to import a ref. */ ++ if (self->local_repo_remote_dfd != -1) ++ { ++ /* self->refspec is the rev in the other local repo we'll rebase to */ ++ g_assert (self->refspec); ++ ++ RpmOstreeRefspecType refspectype; ++ const char *ref; ++ if (!rpmostree_refspec_classify (self->refspec, &refspectype, &ref, error)) ++ return FALSE; ++ ++ if (refspectype == RPMOSTREE_REFSPEC_TYPE_ROJIG) ++ return glnx_throw (error, "Local repo remotes not supported for rojig://"); ++ ++ g_autoptr(OstreeRepo) local_repo_remote = ++ ostree_repo_open_at (self->local_repo_remote_dfd, ".", cancellable, error); ++ if (!local_repo_remote) ++ return glnx_prefix_error (error, "Failed to open local repo"); ++ g_autofree char *rev = NULL; ++ if (!ostree_repo_resolve_rev (local_repo_remote, ref, FALSE, &rev, error)) ++ return FALSE; ++ ++ g_autoptr(OstreeAsyncProgress) progress = ostree_async_progress_new (); ++ rpmostreed_transaction_connect_download_progress (transaction, progress); ++ ++ /* pull-local into the system repo */ ++ const char *refs_to_fetch[] = { rev, NULL }; ++ g_autofree char *local_repo_uri = ++ g_strdup_printf ("file:///proc/self/fd/%d", self->local_repo_remote_dfd); ++ if (!ostree_repo_pull (repo, local_repo_uri, (char**)refs_to_fetch, ++ OSTREE_REPO_PULL_FLAGS_NONE, progress, cancellable, error)) ++ return FALSE; ++ rpmostree_transaction_emit_progress_end (RPMOSTREE_TRANSACTION (transaction)); ++ ++ /* as far as the rest of the code is concerned, we're rebasing to :SHA256 now */ ++ g_clear_pointer (&self->refspec, g_free); ++ self->refspec = g_strdup_printf (":%s", rev); ++ } ++ + g_autofree gchar *new_refspec = NULL; + g_autofree gchar *old_refspec = NULL; + if (self->refspec) +@@ -1357,6 +1399,7 @@ deploy_transaction_class_init (DeployTransactionClass *class) + static void + deploy_transaction_init (DeployTransaction *self) + { ++ self->local_repo_remote_dfd = -1; + } + + static char ** +@@ -1517,16 +1560,36 @@ rpmostreed_transaction_new_deploy (GDBusMethodInvocation *invocation, + g_autoptr(GVariant) override_replace_local_pkgs_idxs = + g_variant_dict_lookup_value (self->modifiers, "override-replace-local-packages", + G_VARIANT_TYPE("ah")); ++ int local_repo_remote_idx = -1; ++ /* See related blurb in get_modifiers_variant() */ ++#ifdef HAVE_DFD_OVER_DBUS ++ g_variant_dict_lookup (self->modifiers, "ex-local-repo-remote", "h", &local_repo_remote_idx); ++#else ++ const char *local_repo_remote = ++ vardict_lookup_ptr (self->modifiers, "ex-local-repo-remote", "&s"); ++ if (local_repo_remote) ++ { ++ if (!glnx_opendirat (AT_FDCWD, local_repo_remote, TRUE, ++ &self->local_repo_remote_dfd, error)) ++ return FALSE; ++ } ++#endif + +- /* We only use the fd list right now to transfer local RPM fds, which are relevant in the ++ /* First in the fd list is local RPM fds, which are relevant in the + * `install foo.rpm` case and the `override replace foo.rpm` case. Let's make sure that +- * the actual number of fds passed is what we expect. */ +- ++ * the actual number of fds passed is what we expect. ++ * ++ * A more recent addition is the local-repo-remote fd. ++ * ++ * Here we validate the number of fds provided against the arguments. ++ */ + guint expected_fdn = 0; + if (install_local_pkgs_idxs) + expected_fdn += g_variant_n_children (install_local_pkgs_idxs); + if (override_replace_local_pkgs_idxs) + expected_fdn += g_variant_n_children (override_replace_local_pkgs_idxs); ++ if (local_repo_remote_idx != -1) ++ expected_fdn += 1; + + guint actual_fdn = 0; + if (fd_list) +@@ -1555,6 +1618,13 @@ rpmostreed_transaction_new_deploy (GDBusMethodInvocation *invocation, + get_fd_array_from_sparse (fds, nfds, override_replace_local_pkgs_idxs); + self->override_replace_local_pkgs = g_unix_fd_list_new_from_array (new_fds, -1); + } ++ ++ if (local_repo_remote_idx != -1) ++ { ++ g_assert_cmpint (local_repo_remote_idx, >=, 0); ++ g_assert_cmpint (local_repo_remote_idx, <, nfds); ++ self->local_repo_remote_dfd = fds[local_repo_remote_idx]; ++ } + } + + /* Also check for conflicting options -- this is after all a public API. */ +@@ -1580,6 +1650,10 @@ rpmostreed_transaction_new_deploy (GDBusMethodInvocation *invocation, + self->override_replace_pkgs || override_replace_local_pkgs_idxs)) + return glnx_null_throw (error, "Can't specify no-overrides if setting " + "override modifiers"); ++ if (!self->refspec && self->local_repo_remote_dfd != -1) ++ return glnx_null_throw (error, "Missing ref for transient local rebases"); ++ if (self->revision && self->local_repo_remote_dfd != -1) ++ return glnx_null_throw (error, "Revision overrides for transient local rebases not implemented yet"); + + return (RpmostreedTransaction *) g_steal_pointer (&self); + } +diff --git a/tests/vmcheck/test-misc-2.sh b/tests/vmcheck/test-misc-2.sh +index 99c0aef4..f111cac4 100755 +--- a/tests/vmcheck/test-misc-2.sh ++++ b/tests/vmcheck/test-misc-2.sh +@@ -26,7 +26,9 @@ set -x + + # More miscellaneous tests + +-# Custom origin https://github.com/projectatomic/rpm-ostree/pull/1406 ++# Custom origin and local repo rebases. This is essentially the RHCOS workflow. ++# https://github.com/projectatomic/rpm-ostree/pull/1406 ++# https://github.com/projectatomic/rpm-ostree/pull/1732 + booted_csum=$(vm_get_booted_csum) + oscontainer_source="oscontainer://quay.io/exampleos@sha256:98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4" + if vm_rpmostree rebase --skip-purge --custom-origin-url "${oscontainer_source}" \ +@@ -43,6 +45,14 @@ assert_file_has_content_literal status.txt "${oscontainer_source}" + vm_rpmostree upgrade >out.txt + assert_file_has_content_literal out.txt 'Pinned to commit by custom origin: Updated via pivot' + vm_rpmostree cleanup -p ++echo "ok rebase with custom origin" ++ ++# Try again but making it think it's pulling from another local repo ++vm_rpmostree rebase --skip-purge /sysroot/ostree/repo:${booted_csum} --experimental ++vm_rpmostree upgrade >out.txt ++assert_file_has_content_literal out.txt 'Pinned to commit; no upgrade available' ++vm_rpmostree cleanup -p ++echo "ok rebase from local repo remote" + + # Add metadata string containing EnfOfLife attribtue + META_ENDOFLIFE_MESSAGE="this is a test for metadata message" +-- +2.20.1 + diff --git a/rpm-ostree.spec b/rpm-ostree.spec index fd651c8..df5c470 100644 --- a/rpm-ostree.spec +++ b/rpm-ostree.spec @@ -9,6 +9,8 @@ Source0: rpm-ostree-%{version}.tar.xz License: LGPLv2+ URL: https://github.com/projectatomic/rpm-ostree +Patch0: 0001-app-rebase-Support-local-repo-remotes.patch + %if !%{defined rust_arches} # It's not defined yet in the base CentOS7 root %define rust_arches x86_64 i686 armv7hl aarch64 ppc64 ppc64le s390x