Skip to content

Commit 4d4196e

Browse files
committed
rpmostreed-transaction-types: don't use base-db when idempotent
When booted from a container image, skip the base commit rpmdb check during package installation transactions. Container images don't have meaningful base-db separation like traditional ostree commits do. This fixes idempotent layering on booted hosts when deployed via container, where packages already present in the base image would incorrectly fail. Now, if the DNF transaction is empty (all packages already in base), we properly exit cleanly. Also adds tests for idempotent layering scenarios with container images. Signed-off-by: Joseph Marrero Corchado <jmarrero@redhat.com>
1 parent f5b263a commit 4d4196e

File tree

9 files changed

+234
-33
lines changed

9 files changed

+234
-33
lines changed

src/daemon/rpmostree-sysroot-upgrader.cxx

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,11 @@ struct RpmOstreeSysrootUpgrader
111111

112112
gboolean layering_initialized; /* Whether layering_type is known */
113113
RpmOstreeSysrootUpgraderLayeringType layering_type;
114-
gboolean layering_changed; /* Whether changes to layering should result in a new commit */
115-
gboolean pkgs_imported; /* Whether pkgs to be layered have been downloaded & imported */
116-
char *base_revision; /* Non-layered replicated commit */
117-
char *final_revision; /* Computed by layering; if NULL, only using base_revision */
114+
gboolean layering_changed; /* Whether changes to layering should result in a new commit */
115+
gboolean allow_empty_transaction; /* Allow empty DNF transaction for idempotent layering */
116+
gboolean pkgs_imported; /* Whether pkgs to be layered have been downloaded & imported */
117+
char *base_revision; /* Non-layered replicated commit */
118+
char *final_revision; /* Computed by layering; if NULL, only using base_revision */
118119

119120
char **kargs_strv; /* Kernel argument list to be written into deployment */
120121
};
@@ -401,6 +402,13 @@ rpmostree_sysroot_upgrader_get_sack (RpmOstreeSysrootUpgrader *self, GError **er
401402
return self->rpmmd_sack;
402403
}
403404

405+
void
406+
rpmostree_sysroot_upgrader_set_allow_empty_transaction (RpmOstreeSysrootUpgrader *self,
407+
gboolean allow)
408+
{
409+
self->allow_empty_transaction = allow;
410+
}
411+
404412
/*
405413
* Like ostree_sysroot_upgrader_pull(), but also handles the `baserefspec` we
406414
* use when doing layered packages.
@@ -998,10 +1006,24 @@ prep_local_assembly (RpmOstreeSysrootUpgrader *self, GCancellable *cancellable,
9981006
self->layering_changed = strcmp (previous_state_sha512, new_state_sha512) != 0;
9991007
}
10001008
else
1001-
/* Otherwise, we're transitioning from not-layered to layered, so it
1002-
definitely changed */
1003-
self->layering_changed = TRUE;
1004-
1009+
{
1010+
/* Otherwise, we're transitioning from not-layered to layered, so it
1011+
definitely changed */
1012+
self->layering_changed = TRUE;
1013+
/* Special case for containers: if the DNF transaction is empty (all packages already in
1014+
* base), don't consider this a change even if transitioning from unlayered to layered. This
1015+
* happens with idempotent layering when packages are already in the container image. */
1016+
if (self->layering_type == RPMOSTREE_SYSROOT_UPGRADER_LAYERING_RPMMD_REPOS)
1017+
{
1018+
auto refspec = rpmostree_origin_get_refspec (self->computed_origin);
1019+
if (refspec.kind == rpmostreecxx::RefspecType::Container
1020+
&& rpmostree_dnf_context_has_empty_transaction (
1021+
rpmostree_context_get_dnf (self->ctx)))
1022+
{
1023+
self->layering_changed = FALSE;
1024+
}
1025+
}
1026+
}
10051027
return TRUE;
10061028
}
10071029

@@ -1021,6 +1043,7 @@ perform_local_assembly (RpmOstreeSysrootUpgrader *self, GCancellable *cancellabl
10211043

10221044
rpmostree_context_set_devino_cache (self->ctx, self->devino_cache);
10231045
rpmostree_context_set_tmprootfs_dfd (self->ctx, self->tmprootfs_dfd);
1046+
rpmostree_context_set_allow_empty_transaction (self->ctx, self->allow_empty_transaction);
10241047

10251048
if (self->layering_type == RPMOSTREE_SYSROOT_UPGRADER_LAYERING_RPMMD_REPOS)
10261049
{

src/daemon/rpmostree-sysroot-upgrader.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ const char *rpmostree_sysroot_upgrader_get_base (RpmOstreeSysrootUpgrader *self)
9898

9999
DnfSack *rpmostree_sysroot_upgrader_get_sack (RpmOstreeSysrootUpgrader *self, GError **error);
100100

101+
void rpmostree_sysroot_upgrader_set_allow_empty_transaction (RpmOstreeSysrootUpgrader *self,
102+
gboolean allow);
103+
101104
gboolean rpmostree_sysroot_upgrader_pull_base (RpmOstreeSysrootUpgrader *self,
102105
const char *dir_to_pull, OstreeRepoPullFlags flags,
103106
OstreeAsyncProgress *progress, gboolean *out_changed,

src/daemon/rpmostreed-transaction-types.cxx

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,8 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, GCancellable *ca
11281128
if (upgrader == NULL)
11291129
return FALSE;
11301130

1131+
rpmostree_sysroot_upgrader_set_allow_empty_transaction (upgrader, idempotent_layering);
1132+
11311133
g_autoptr (RpmOstreeOrigin) origin = rpmostree_sysroot_upgrader_dup_origin (upgrader);
11321134

11331135
/* Handle local repo remotes immediately; the idea is that the remote is "transient"
@@ -1191,6 +1193,7 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, GCancellable *ca
11911193
}
11921194

11931195
gboolean changed = FALSE;
1196+
gboolean remove_changed = FALSE;
11941197
if (no_initramfs
11951198
&& (rpmostree_origin_get_regenerate_initramfs (origin)
11961199
|| rpmostree_origin_has_initramfs_etc_files (origin)))
@@ -1214,7 +1217,10 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, GCancellable *ca
12141217
if (no_layering)
12151218
{
12161219
if (rpmostree_origin_remove_all_packages (origin))
1217-
changed = TRUE;
1220+
{
1221+
changed = TRUE;
1222+
remove_changed = TRUE;
1223+
}
12181224
}
12191225
else
12201226
{
@@ -1223,43 +1229,60 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, GCancellable *ca
12231229
* create a new deployment; see https://github.com/projectatomic/rpm-ostree/issues/753 */
12241230
if (!rpmostree_origin_remove_packages (origin,
12251231
util::rust_stringvec_from_strv (uninstall_pkgs),
1226-
idempotent_layering, &changed, error))
1232+
idempotent_layering, &remove_changed, error))
12271233
return FALSE;
1234+
if (remove_changed)
1235+
changed = TRUE;
12281236
}
12291237

12301238
/* lazily loaded cache that's used in a few conditional blocks */
12311239
g_autoptr (RpmOstreeRefSack) base_rsack = NULL;
12321240

1241+
/* Check if we should skip the base-db check:
1242+
* When booted from a container image on an ostree host,
1243+
* we skip the base commit rpmdb check because container images don't have
1244+
* meaningful base-db separation like traditional ostree commits do. */
1245+
gboolean skip_base_check = FALSE;
1246+
CXX_TRY_VAR (host_type, rpmostreecxx::get_system_host_type (), error);
1247+
if (host_type == rpmostreecxx::SystemHostType::OstreeHost)
1248+
{
1249+
const auto refspec = rpmostree_origin_get_refspec (origin);
1250+
skip_base_check = (refspec.kind == rpmostreecxx::RefspecType::Container);
1251+
}
1252+
12331253
if (install_pkgs)
12341254
{
12351255
/* we run a special check here; let's just not allow trying to install a pkg that will
12361256
* right away become inactive because it's already installed */
12371257

1238-
if (!base_rsack)
1258+
if (!skip_base_check)
12391259
{
1240-
const char *base = rpmostree_sysroot_upgrader_get_base (upgrader);
1241-
base_rsack = rpmostree_get_refsack_for_commit (repo, base, cancellable, error);
1242-
if (base_rsack == NULL)
1243-
return FALSE;
1244-
}
1260+
if (!base_rsack)
1261+
{
1262+
const char *base = rpmostree_sysroot_upgrader_get_base (upgrader);
1263+
base_rsack = rpmostree_get_refsack_for_commit (repo, base, cancellable, error);
1264+
if (base_rsack == NULL)
1265+
return FALSE;
1266+
}
12451267

1246-
for (char **it = install_pkgs; it && *it; it++)
1247-
{
1248-
const char *pkg = *it;
1249-
g_autoptr (GPtrArray) pkgs = rpmostree_get_matching_packages (base_rsack->sack, pkg);
1250-
if (pkgs->len > 0 && !allow_inactive)
1268+
for (char **it = install_pkgs; it && *it; it++)
12511269
{
1252-
g_autoptr (GString) pkgnames = g_string_new ("");
1253-
for (guint i = 0; i < pkgs->len; i++)
1270+
const char *pkg = *it;
1271+
g_autoptr (GPtrArray) pkgs = rpmostree_get_matching_packages (base_rsack->sack, pkg);
1272+
if (pkgs->len > 0 && !allow_inactive)
12541273
{
1255-
auto p = static_cast<DnfPackage *> (pkgs->pdata[i]);
1256-
g_string_append_printf (pkgnames, " %s", dnf_package_get_nevra (p));
1274+
g_autoptr (GString) pkgnames = g_string_new ("");
1275+
for (guint i = 0; i < pkgs->len; i++)
1276+
{
1277+
auto p = static_cast<DnfPackage *> (pkgs->pdata[i]);
1278+
g_string_append_printf (pkgnames, " %s", dnf_package_get_nevra (p));
1279+
}
1280+
return glnx_throw (error,
1281+
"\"%s\" is already provided by:%s. Use "
1282+
"--allow-inactive to explicitly "
1283+
"require it.",
1284+
pkg, pkgnames->str);
12571285
}
1258-
return glnx_throw (error,
1259-
"\"%s\" is already provided by:%s. Use "
1260-
"--allow-inactive to explicitly "
1261-
"require it.",
1262-
pkg, pkgnames->str);
12631286
}
12641287
}
12651288

@@ -1565,6 +1588,15 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, GCancellable *ca
15651588
return FALSE;
15661589
changed = changed || layering_changed;
15671590

1591+
/* When using containers and nothing changed after prep_layering, return early.
1592+
* This happens when all requested packages are already present in the container image.
1593+
* For idempotent mode, this avoids the "No packages in transaction" error.
1594+
* However, if packages were removed from the origin, we need to deploy to update the origin
1595+
* even if no layering is needed. Similarly, if we're rebasing (refspec) or deploying a
1596+
* specific revision, we need to deploy. */
1597+
if (skip_base_check && !layering_changed && !remove_changed && !self->refspec && !self->revision)
1598+
return TRUE;
1599+
15681600
if (dry_run)
15691601
/* Note early return here; we printed the transaction already */
15701602
return TRUE;

src/libpriv/rpmostree-core-private.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ struct _RpmOstreeContext
4040
std::optional<rust::Box<rpmostreecxx::Treefile>> treefile_owned;
4141
rpmostreecxx::Treefile *treefile_rs; /* For composes for now */
4242
gboolean empty;
43+
gboolean allow_empty_transaction;
4344
gboolean disable_selinux;
4445
char *ref;
4546

src/libpriv/rpmostree-core.cxx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,12 @@ rpmostree_context_set_is_empty (RpmOstreeContext *self)
381381
self->empty = TRUE;
382382
}
383383

384+
void
385+
rpmostree_context_set_allow_empty_transaction (RpmOstreeContext *self, gboolean allow)
386+
{
387+
self->allow_empty_transaction = allow;
388+
}
389+
384390
void
385391
rpmostree_context_disable_selinux (RpmOstreeContext *self)
386392
{
@@ -784,7 +790,8 @@ rpmostree_context_setup (RpmOstreeContext *self, const char *install_root, const
784790
* we're only installing local RPMs. Missing deps will cause the regular
785791
* 'not found' error from libdnf. */
786792
auto pkgs = self->treefile_rs->get_packages ();
787-
if (!pkgs.empty ())
793+
auto local_pkgs = self->treefile_rs->get_local_packages ();
794+
if (!pkgs.empty () && local_pkgs.empty ())
788795
return glnx_throw (error, "No enabled repositories");
789796
}
790797

@@ -4229,8 +4236,11 @@ rpmostree_context_assemble (RpmOstreeContext *self, GCancellable *cancellable, G
42294236
g_autoptr (GPtrArray) overrides_remove = dnf_goal_get_packages (
42304237
dnf_context_get_goal (dnfctx), DNF_PACKAGE_INFO_REMOVE, DNF_PACKAGE_INFO_OBSOLETE, -1);
42314238

4232-
if (overlays->len == 0 && overrides_remove->len == 0 && overrides_replace->len == 0)
4233-
return glnx_throw (error, "No packages in transaction");
4239+
if (rpmostree_dnf_context_has_empty_transaction (dnfctx))
4240+
{
4241+
if (!self->allow_empty_transaction)
4242+
return glnx_throw (error, "No packages in transaction");
4243+
}
42344244

42354245
/* Sort the packages as rpmtsOrder() only reorder to satisfy dependencies
42364246
* but doesn't impose any ordering to packages with the same dependencies.

src/libpriv/rpmostree-core.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ void rpmostree_context_configure_from_deployment (RpmOstreeContext *self, Ostree
109109
OstreeDeployment *cfg_deployment);
110110

111111
void rpmostree_context_set_is_empty (RpmOstreeContext *self);
112+
void rpmostree_context_set_allow_empty_transaction (RpmOstreeContext *self, gboolean allow);
112113
void rpmostree_context_disable_selinux (RpmOstreeContext *self);
113114
const char *rpmostree_context_get_ref (RpmOstreeContext *self);
114115

src/libpriv/rpmostree-rpm-util.cxx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,21 @@ rpmostree_print_transaction (DnfContext *dnfctx)
10481048
rpmostree_output_message ("Empty transaction");
10491049
}
10501050

1051+
/* Helper function to check if a DNF transaction has any packages to install, remove, or update */
1052+
gboolean
1053+
rpmostree_dnf_context_has_empty_transaction (DnfContext *dnfctx)
1054+
{
1055+
HyGoal goal = dnf_context_get_goal (dnfctx);
1056+
1057+
g_autoptr (GPtrArray) install_pkgs = dnf_goal_get_packages (goal, DNF_PACKAGE_INFO_INSTALL, -1);
1058+
g_autoptr (GPtrArray) remove_pkgs
1059+
= dnf_goal_get_packages (goal, DNF_PACKAGE_INFO_REMOVE, DNF_PACKAGE_INFO_OBSOLETE, -1);
1060+
g_autoptr (GPtrArray) update_pkgs
1061+
= dnf_goal_get_packages (goal, DNF_PACKAGE_INFO_UPDATE, DNF_PACKAGE_INFO_DOWNGRADE, -1);
1062+
1063+
return (install_pkgs->len == 0 && remove_pkgs->len == 0 && update_pkgs->len == 0);
1064+
}
1065+
10511066
G_DEFINE_AUTO_CLEANUP_FREE_FUNC (cap_t, cap_free, NULL)
10521067

10531068
GVariant *

src/libpriv/rpmostree-rpm-util.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ gint rpmostree_pkg_array_compare (DnfPackage **p_pkg1, DnfPackage **p_pkg2);
117117

118118
void rpmostree_print_transaction (DnfContext *context);
119119

120+
gboolean rpmostree_dnf_context_has_empty_transaction (DnfContext *dnfctx);
121+
120122
GVariant *rpmostree_fcap_to_ostree_xattr (const char *fcap, GError **error);
121123
GVariant *rpmostree_fcap_to_xattr_variant (const char *fcap, GError **error);
122124

0 commit comments

Comments
 (0)