mirror of
https://github.com/ostreedev/ostree.git
synced 2025-03-28 02:50:37 +03:00
Add concept of "staged" deployment
Add API to write a deployment state to `/run/ostree/staged-deployment`, along with a systemd service which runs at shutdown time. This is a big change to the ostree model for hosts, but it closes a longstanding set of bugs; many, many people have hit the "losing changes in /etc" problem. It also avoids the other problem of racing with programs that modify `/etc` such as LVM backups: https://bugzilla.redhat.com/show_bug.cgi?id=1365297 We need this in particular to go to a full-on model for automatically updated host systems where (like a dual-partition model) everything is fully prepared and the reboot can be taken asynchronously. Closes: https://github.com/ostreedev/ostree/issues/545 Closes: #1503 Approved by: jlebon
This commit is contained in:
parent
ff50495f67
commit
eb506c759c
@ -39,7 +39,7 @@ endif
|
||||
|
||||
if BUILDOPT_SYSTEMD
|
||||
systemdsystemunit_DATA = src/boot/ostree-prepare-root.service \
|
||||
src/boot/ostree-remount.service
|
||||
src/boot/ostree-remount.service src/boot/ostree-finalize-staged.service
|
||||
systemdtmpfilesdir = $(prefix)/lib/tmpfiles.d
|
||||
dist_systemdtmpfiles_DATA = src/boot/ostree-tmpfiles.conf
|
||||
|
||||
@ -65,6 +65,7 @@ EXTRA_DIST += src/boot/dracut/module-setup.sh \
|
||||
src/boot/mkinitcpio/ostree \
|
||||
src/boot/ostree-prepare-root.service \
|
||||
src/boot/ostree-remount.service \
|
||||
src/boot/ostree-finalize-staged.service \
|
||||
src/boot/grub2/grub2-15_ostree \
|
||||
src/boot/grub2/ostree-grub-generator \
|
||||
$(NULL)
|
||||
|
@ -67,6 +67,7 @@ ostree_SOURCES += \
|
||||
src/ostree/ot-admin-builtin-init-fs.c \
|
||||
src/ostree/ot-admin-builtin-diff.c \
|
||||
src/ostree/ot-admin-builtin-deploy.c \
|
||||
src/ostree/ot-admin-builtin-finalize-staged.c \
|
||||
src/ostree/ot-admin-builtin-undeploy.c \
|
||||
src/ostree/ot-admin-builtin-instutil.c \
|
||||
src/ostree/ot-admin-builtin-cleanup.c \
|
||||
|
@ -170,6 +170,7 @@ ostree_deployment_get_origin
|
||||
ostree_deployment_get_origin_relpath
|
||||
ostree_deployment_get_unlocked
|
||||
ostree_deployment_is_pinned
|
||||
ostree_deployment_is_staged
|
||||
ostree_deployment_set_index
|
||||
ostree_deployment_set_bootserial
|
||||
ostree_deployment_set_bootconfig
|
||||
@ -506,6 +507,7 @@ ostree_sysroot_cleanup
|
||||
ostree_sysroot_prepare_cleanup
|
||||
ostree_sysroot_repo
|
||||
ostree_sysroot_get_repo
|
||||
ostree_sysroot_get_staged_deployment
|
||||
ostree_sysroot_init_osname
|
||||
ostree_sysroot_deployment_set_kargs
|
||||
ostree_sysroot_deployment_set_mutable
|
||||
@ -514,6 +516,7 @@ ostree_sysroot_deployment_set_pinned
|
||||
ostree_sysroot_write_deployments
|
||||
ostree_sysroot_write_deployments_with_options
|
||||
ostree_sysroot_write_origin_file
|
||||
ostree_sysroot_stage_tree
|
||||
ostree_sysroot_deploy_tree
|
||||
ostree_sysroot_get_merge_deployment
|
||||
ostree_sysroot_query_deployments_for
|
||||
|
36
src/boot/ostree-finalize-staged.service
Normal file
36
src/boot/ostree-finalize-staged.service
Normal file
@ -0,0 +1,36 @@
|
||||
# Copyright (C) 2018 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.
|
||||
|
||||
# For some implementation discussion, see:
|
||||
# https://lists.freedesktop.org/archives/systemd-devel/2018-March/040557.html
|
||||
[Unit]
|
||||
Description=OSTree Finalize Staged Deployment
|
||||
ConditionPathExists=/run/ostree-booted
|
||||
DefaultDependencies=no
|
||||
|
||||
RequiresMountsFor=/sysroot
|
||||
After=basic.target
|
||||
Before=multi-user.target final.target
|
||||
Conflicts=final.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStop=/usr/bin/ostree admin finalize-staged
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -19,6 +19,9 @@
|
||||
|
||||
/* Add new symbols here. Release commits should copy this section into -released.sym. */
|
||||
LIBOSTREE_2018.5 {
|
||||
ostree_sysroot_stage_tree;
|
||||
ostree_sysroot_get_staged_deployment;
|
||||
ostree_deployment_is_staged;
|
||||
} LIBOSTREE_2018.3;
|
||||
|
||||
/* Stub section for the stable release *after* this development one; don't
|
||||
|
@ -26,7 +26,7 @@
|
||||
#include "ostree-core-private.h"
|
||||
#include "ostree-repo-pull-private.h"
|
||||
#include "ostree-repo-static-delta-private.h"
|
||||
#include "ostree-sysroot.h"
|
||||
#include "ostree-sysroot-private.h"
|
||||
#include "ostree-bootloader-grub2.h"
|
||||
|
||||
#include "otutil.h"
|
||||
@ -52,7 +52,8 @@ ostree_cmd__private__ (void)
|
||||
_ostree_repo_static_delta_dump,
|
||||
_ostree_repo_static_delta_query_exists,
|
||||
_ostree_repo_static_delta_delete,
|
||||
_ostree_repo_verify_bindings
|
||||
_ostree_repo_verify_bindings,
|
||||
_ostree_sysroot_finalize_staged,
|
||||
};
|
||||
|
||||
return &table;
|
||||
|
@ -34,6 +34,7 @@ typedef struct {
|
||||
gboolean (* ostree_static_delta_query_exists) (OstreeRepo *repo, const char *delta_id, gboolean *out_exists, GCancellable *cancellable, GError **error);
|
||||
gboolean (* ostree_static_delta_delete) (OstreeRepo *repo, const char *delta_id, GCancellable *cancellable, GError **error);
|
||||
gboolean (* ostree_repo_verify_bindings) (const char *collection_id, const char *ref_name, GVariant *commit, GError **error);
|
||||
gboolean (* ostree_finalize_staged) (OstreeSysroot *sysroot, GCancellable *cancellable, GError **error);
|
||||
} OstreeCmdPrivateVTable;
|
||||
|
||||
/* Note this not really "public", we just export the symbol, but not the header */
|
||||
|
@ -36,6 +36,7 @@ G_BEGIN_DECLS
|
||||
* @bootconfig: Bootloader configuration
|
||||
* @origin: How to construct an upgraded version of this tree
|
||||
* @unlocked: The unlocked state
|
||||
* @staged: TRUE iff this deployment is staged
|
||||
*/
|
||||
struct _OstreeDeployment
|
||||
{
|
||||
@ -50,6 +51,7 @@ struct _OstreeDeployment
|
||||
OstreeBootconfigParser *bootconfig;
|
||||
GKeyFile *origin;
|
||||
OstreeDeploymentUnlockedState unlocked;
|
||||
gboolean staged;
|
||||
};
|
||||
|
||||
void _ostree_deployment_set_bootcsum (OstreeDeployment *self, const char *bootcsum);
|
||||
|
@ -339,3 +339,16 @@ ostree_deployment_is_pinned (OstreeDeployment *self)
|
||||
return FALSE;
|
||||
return g_key_file_get_boolean (self->origin, OSTREE_ORIGIN_TRANSIENT_GROUP, "pinned", NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_deployment_is_staged:
|
||||
* @self: Deployment
|
||||
*
|
||||
* Returns: `TRUE` if deployment should be "finalized" at shutdown time
|
||||
* Since: 2018.3
|
||||
*/
|
||||
gboolean
|
||||
ostree_deployment_is_staged (OstreeDeployment *self)
|
||||
{
|
||||
return self->staged;
|
||||
}
|
||||
|
@ -73,7 +73,8 @@ OstreeBootconfigParser *ostree_deployment_get_bootconfig (OstreeDeployment *self
|
||||
_OSTREE_PUBLIC
|
||||
GKeyFile *ostree_deployment_get_origin (OstreeDeployment *self);
|
||||
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_deployment_is_staged (OstreeDeployment *self);
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_deployment_is_pinned (OstreeDeployment *self);
|
||||
|
||||
|
@ -308,6 +308,15 @@ cleanup_old_deployments (OstreeSysroot *self,
|
||||
g_hash_table_replace (active_boot_checksums, bootcsum, bootcsum);
|
||||
}
|
||||
|
||||
/* And also the staged deployment, if any */
|
||||
if (self->staged_deployment)
|
||||
{
|
||||
char *deployment_path = ostree_sysroot_get_deployment_dirpath (self, self->staged_deployment);
|
||||
g_hash_table_replace (active_deployment_dirs, deployment_path, deployment_path);
|
||||
char *bootcsum = g_strdup (ostree_deployment_get_bootcsum (self->staged_deployment));
|
||||
g_hash_table_replace (active_boot_checksums, bootcsum, bootcsum);
|
||||
}
|
||||
|
||||
/* Find all deployment directories, both active and inactive */
|
||||
g_autoptr(GPtrArray) all_deployment_dirs = NULL;
|
||||
if (!list_all_deployment_directories (self, &all_deployment_dirs,
|
||||
|
@ -684,10 +684,15 @@ selinux_relabel_dir (OstreeSysroot *sysroot,
|
||||
static gboolean
|
||||
selinux_relabel_var_if_needed (OstreeSysroot *sysroot,
|
||||
OstreeSePolicy *sepolicy,
|
||||
int os_deploy_dfd,
|
||||
OstreeDeployment *deployment,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
const char *osdeploypath = glnx_strjoina ("ostree/deploy/", ostree_deployment_get_osname (deployment));
|
||||
glnx_autofd int os_deploy_dfd = -1;
|
||||
if (!glnx_opendirat (sysroot->sysroot_fd, osdeploypath, TRUE, &os_deploy_dfd, error))
|
||||
return FALSE;
|
||||
|
||||
/* This is a bit of a hack; we should change the code at some
|
||||
* point in the distant future to only create (and label) /var
|
||||
* when doing a deployment.
|
||||
@ -743,12 +748,10 @@ prepare_deployment_etc (OstreeSysroot *sysroot,
|
||||
OstreeRepo *repo,
|
||||
OstreeDeployment *deployment,
|
||||
int deployment_dfd,
|
||||
OstreeSePolicy **out_sepolicy,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
GLNX_AUTO_PREFIX_ERROR ("Preparing /etc", error);
|
||||
g_autoptr(OstreeSePolicy) sepolicy = NULL;
|
||||
|
||||
struct stat stbuf;
|
||||
if (!glnx_fstatat_allow_noent (deployment_dfd, "etc", &stbuf, AT_SYMLINK_NOFOLLOW, error))
|
||||
@ -781,7 +784,7 @@ prepare_deployment_etc (OstreeSysroot *sysroot,
|
||||
/* Here, we initialize SELinux policy from the /usr/etc inside
|
||||
* the root - this is before we've finalized the configuration
|
||||
* merge into /etc. */
|
||||
sepolicy = ostree_sepolicy_new_at (deployment_dfd, cancellable, error);
|
||||
g_autoptr(OstreeSePolicy) sepolicy = ostree_sepolicy_new_at (deployment_dfd, cancellable, error);
|
||||
if (!sepolicy)
|
||||
return FALSE;
|
||||
if (ostree_sepolicy_get_name (sepolicy) != NULL)
|
||||
@ -796,8 +799,6 @@ prepare_deployment_etc (OstreeSysroot *sysroot,
|
||||
|
||||
}
|
||||
|
||||
if (out_sepolicy)
|
||||
*out_sepolicy = g_steal_pointer (&sepolicy);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -831,7 +832,6 @@ write_origin_file_internal (OstreeSysroot *sysroot,
|
||||
ostree_deployment_get_csum (deployment),
|
||||
ostree_deployment_get_deployserial (deployment));
|
||||
|
||||
|
||||
gsize len;
|
||||
g_autofree char *contents = g_key_file_to_data (origin, &len, error);
|
||||
if (!contents)
|
||||
@ -2324,46 +2324,47 @@ allocate_deployserial (OstreeSysroot *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_deploy_tree:
|
||||
* @self: Sysroot
|
||||
* @osname: (allow-none): osname to use for merge deployment
|
||||
* @revision: Checksum to add
|
||||
* @origin: (allow-none): Origin to use for upgrades
|
||||
* @provided_merge_deployment: (allow-none): Use this deployment for merge path
|
||||
* @override_kernel_argv: (allow-none) (array zero-terminated=1) (element-type utf8): Use these as kernel arguments; if %NULL, inherit options from provided_merge_deployment
|
||||
* @out_new_deployment: (out): The new deployment path
|
||||
* @cancellable: Cancellable
|
||||
* @error: Error
|
||||
*
|
||||
* Check out deployment tree with revision @revision, performing a 3
|
||||
* way merge with @provided_merge_deployment for configuration.
|
||||
void
|
||||
_ostree_deployment_set_bootconfig_from_kargs (OstreeDeployment *deployment,
|
||||
char **override_kernel_argv)
|
||||
{
|
||||
/* Create an empty boot configuration; we will merge things into
|
||||
* it as we go.
|
||||
*/
|
||||
g_autoptr(OstreeBootconfigParser) bootconfig = ostree_bootconfig_parser_new ();
|
||||
ostree_deployment_set_bootconfig (deployment, bootconfig);
|
||||
|
||||
/* After this, install_deployment_kernel() will set the other boot
|
||||
* options and write it out to disk.
|
||||
*/
|
||||
if (override_kernel_argv)
|
||||
{
|
||||
g_autoptr(OstreeKernelArgs) kargs = _ostree_kernel_args_new ();
|
||||
_ostree_kernel_args_append_argv (kargs, override_kernel_argv);
|
||||
g_autofree char *new_options = _ostree_kernel_args_to_string (kargs);
|
||||
ostree_bootconfig_parser_set (bootconfig, "options", new_options);
|
||||
}
|
||||
}
|
||||
|
||||
/* The first part of writing a deployment. This primarily means doing the
|
||||
* hardlink farm checkout, but we also compute some initial state.
|
||||
*/
|
||||
gboolean
|
||||
ostree_sysroot_deploy_tree (OstreeSysroot *self,
|
||||
const char *osname,
|
||||
const char *revision,
|
||||
GKeyFile *origin,
|
||||
OstreeDeployment *provided_merge_deployment,
|
||||
char **override_kernel_argv,
|
||||
OstreeDeployment **out_new_deployment,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
static gboolean
|
||||
sysroot_initialize_deployment (OstreeSysroot *self,
|
||||
const char *osname,
|
||||
const char *revision,
|
||||
GKeyFile *origin,
|
||||
char **override_kernel_argv,
|
||||
OstreeDeployment **out_new_deployment,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (osname != NULL || self->booted_deployment != NULL, FALSE);
|
||||
|
||||
if (osname == NULL)
|
||||
osname = ostree_deployment_get_osname (self->booted_deployment);
|
||||
|
||||
const char *osdeploypath = glnx_strjoina ("ostree/deploy/", osname);
|
||||
glnx_autofd int os_deploy_dfd = -1;
|
||||
if (!glnx_opendirat (self->sysroot_fd, osdeploypath, TRUE, &os_deploy_dfd, error))
|
||||
return FALSE;
|
||||
|
||||
OstreeRepo *repo = ostree_sysroot_repo (self);
|
||||
g_autoptr(OstreeDeployment) merge_deployment = NULL;
|
||||
if (provided_merge_deployment != NULL)
|
||||
merge_deployment = g_object_ref (provided_merge_deployment);
|
||||
|
||||
gint new_deployserial;
|
||||
if (!allocate_deployserial (self, osname, revision, &new_deployserial,
|
||||
@ -2388,66 +2389,328 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self,
|
||||
return FALSE;
|
||||
|
||||
_ostree_deployment_set_bootcsum (new_deployment, kernel_layout->bootcsum);
|
||||
_ostree_deployment_set_bootconfig_from_kargs (new_deployment, override_kernel_argv);
|
||||
|
||||
/* Initial empty boot configuration. */
|
||||
g_autoptr(OstreeBootconfigParser) bootconfig = ostree_bootconfig_parser_new ();
|
||||
ostree_deployment_set_bootconfig (new_deployment, bootconfig);
|
||||
if (!prepare_deployment_etc (self, repo, new_deployment, deployment_dfd,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
/* Handle kernel arguments. After this, install_deployment_kernel() will set
|
||||
* the other boot options and write it out to disk.
|
||||
*/
|
||||
if (override_kernel_argv)
|
||||
ot_transfer_out_value (out_new_deployment, &new_deployment);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sysroot_finalize_deployment (OstreeSysroot *self,
|
||||
OstreeDeployment *deployment,
|
||||
char **override_kernel_argv,
|
||||
OstreeDeployment *merge_deployment,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (self, deployment);
|
||||
glnx_autofd int deployment_dfd = -1;
|
||||
if (!glnx_opendirat (self->sysroot_fd, deployment_path, TRUE, &deployment_dfd, error))
|
||||
return FALSE;
|
||||
|
||||
/* Only use the merge if we didn't get an override */
|
||||
if (!override_kernel_argv && merge_deployment)
|
||||
{
|
||||
/* We have an override set, use it */
|
||||
g_autoptr(OstreeKernelArgs) kargs = _ostree_kernel_args_new ();
|
||||
_ostree_kernel_args_append_argv (kargs, override_kernel_argv);
|
||||
g_autofree char *new_options = _ostree_kernel_args_to_string (kargs);
|
||||
ostree_bootconfig_parser_set (bootconfig, "options", new_options);
|
||||
}
|
||||
else if (provided_merge_deployment)
|
||||
{
|
||||
/* Use the merge options by default */
|
||||
OstreeBootconfigParser *merge_bootconfig = ostree_deployment_get_bootconfig (provided_merge_deployment);
|
||||
/* Override the bootloader arguments */
|
||||
OstreeBootconfigParser *merge_bootconfig = ostree_deployment_get_bootconfig (merge_deployment);
|
||||
if (merge_bootconfig)
|
||||
{
|
||||
const char *opts = ostree_bootconfig_parser_get (merge_bootconfig, "options");
|
||||
ostree_bootconfig_parser_set (bootconfig, "options", opts);
|
||||
ostree_bootconfig_parser_set (ostree_deployment_get_bootconfig (deployment), "options", opts);
|
||||
}
|
||||
}
|
||||
|
||||
g_autoptr(OstreeSePolicy) sepolicy = NULL;
|
||||
if (!prepare_deployment_etc (self, repo, new_deployment, deployment_dfd,
|
||||
&sepolicy, cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (merge_deployment)
|
||||
{
|
||||
if (!merge_configuration_from (self, merge_deployment,
|
||||
new_deployment, deployment_dfd,
|
||||
/* And do the /etc merge */
|
||||
if (!merge_configuration_from (self, merge_deployment, deployment, deployment_dfd,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!selinux_relabel_var_if_needed (self, sepolicy, os_deploy_dfd,
|
||||
cancellable, error))
|
||||
g_autoptr(OstreeSePolicy) sepolicy = ostree_sepolicy_new_at (deployment_dfd, cancellable, error);
|
||||
if (!sepolicy)
|
||||
return FALSE;
|
||||
|
||||
if (!(self->debug_flags & OSTREE_SYSROOT_DEBUG_MUTABLE_DEPLOYMENTS))
|
||||
{
|
||||
if (!ostree_sysroot_deployment_set_mutable (self, new_deployment, FALSE,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
if (!selinux_relabel_var_if_needed (self, sepolicy, deployment, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
/* Don't fsync here, as we assume that's all done in
|
||||
* ostree_sysroot_write_deployments().
|
||||
/* Rewrite the origin using the final merged selinux config, just to be
|
||||
* conservative about getting the right labels.
|
||||
*/
|
||||
if (!write_origin_file_internal (self, sepolicy, new_deployment, NULL,
|
||||
if (!write_origin_file_internal (self, sepolicy, deployment,
|
||||
ostree_deployment_get_origin (deployment),
|
||||
GLNX_FILE_REPLACE_NODATASYNC,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
ot_transfer_out_value (out_new_deployment, &new_deployment);
|
||||
/* Seal it */
|
||||
if (!(self->debug_flags & OSTREE_SYSROOT_DEBUG_MUTABLE_DEPLOYMENTS))
|
||||
{
|
||||
if (!ostree_sysroot_deployment_set_mutable (self, deployment, FALSE,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_deploy_tree:
|
||||
* @self: Sysroot
|
||||
* @osname: (allow-none): osname to use for merge deployment
|
||||
* @revision: Checksum to add
|
||||
* @origin: (allow-none): Origin to use for upgrades
|
||||
* @provided_merge_deployment: (allow-none): Use this deployment for merge path
|
||||
* @override_kernel_argv: (allow-none) (array zero-terminated=1) (element-type utf8): Use these as kernel arguments; if %NULL, inherit options from provided_merge_deployment
|
||||
* @out_new_deployment: (out): The new deployment path
|
||||
* @cancellable: Cancellable
|
||||
* @error: Error
|
||||
*
|
||||
* Check out deployment tree with revision @revision, performing a 3
|
||||
* way merge with @provided_merge_deployment for configuration.
|
||||
*
|
||||
* While this API is not deprecated, you most likely want to use the
|
||||
* ostree_sysroot_stage_tree() API.
|
||||
*/
|
||||
gboolean
|
||||
ostree_sysroot_deploy_tree (OstreeSysroot *self,
|
||||
const char *osname,
|
||||
const char *revision,
|
||||
GKeyFile *origin,
|
||||
OstreeDeployment *provided_merge_deployment,
|
||||
char **override_kernel_argv,
|
||||
OstreeDeployment **out_new_deployment,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(OstreeDeployment) deployment = NULL;
|
||||
if (!sysroot_initialize_deployment (self, osname, revision, origin, override_kernel_argv,
|
||||
&deployment, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
if (!sysroot_finalize_deployment (self, deployment, override_kernel_argv,
|
||||
provided_merge_deployment,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
*out_new_deployment = g_steal_pointer (&deployment);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Serialize information about a deployment to a variant, used by the staging
|
||||
* code.
|
||||
*/
|
||||
static GVariant *
|
||||
serialize_deployment_to_variant (OstreeDeployment *deployment)
|
||||
{
|
||||
g_auto(GVariantBuilder) builder = OT_VARIANT_BUILDER_INITIALIZER;
|
||||
g_variant_builder_init (&builder, (GVariantType*)"a{sv}");
|
||||
g_autofree char *name =
|
||||
g_strdup_printf ("%s.%d", ostree_deployment_get_csum (deployment),
|
||||
ostree_deployment_get_deployserial (deployment));
|
||||
g_variant_builder_add (&builder, "{sv}", "name",
|
||||
g_variant_new_string (name));
|
||||
g_variant_builder_add (&builder, "{sv}", "osname",
|
||||
g_variant_new_string (ostree_deployment_get_osname (deployment)));
|
||||
g_variant_builder_add (&builder, "{sv}", "bootcsum",
|
||||
g_variant_new_string (ostree_deployment_get_bootcsum (deployment)));
|
||||
|
||||
return g_variant_builder_end (&builder);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
require_str_key (GVariantDict *dict,
|
||||
const char *name,
|
||||
const char **ret,
|
||||
GError **error)
|
||||
{
|
||||
if (!g_variant_dict_lookup (dict, name, "&s", ret))
|
||||
return glnx_throw (error, "Missing key: %s", name);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Reverse of the above; convert a variant to a deployment. Note that the
|
||||
* deployment may not actually be present; this should be verified by
|
||||
* higher level code.
|
||||
*/
|
||||
OstreeDeployment *
|
||||
_ostree_sysroot_deserialize_deployment_from_variant (GVariant *v,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(GVariantDict) dict = g_variant_dict_new (v);
|
||||
const char *name = NULL;
|
||||
if (!require_str_key (dict, "name", &name, error))
|
||||
return FALSE;
|
||||
const char *bootcsum = NULL;
|
||||
if (!require_str_key (dict, "bootcsum", &bootcsum, error))
|
||||
return FALSE;
|
||||
const char *osname = NULL;
|
||||
if (!require_str_key (dict, "osname", &osname, error))
|
||||
return FALSE;
|
||||
g_autofree char *checksum = NULL;
|
||||
gint deployserial;
|
||||
if (!_ostree_sysroot_parse_deploy_path_name (name, &checksum, &deployserial, error))
|
||||
return NULL;
|
||||
return ostree_deployment_new (-1, osname, checksum, deployserial,
|
||||
bootcsum, -1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ostree_sysroot_stage_tree:
|
||||
* @self: Sysroot
|
||||
* @osname: (allow-none): osname to use for merge deployment
|
||||
* @revision: Checksum to add
|
||||
* @origin: (allow-none): Origin to use for upgrades
|
||||
* @merge_deployment: (allow-none): Use this deployment for merge path
|
||||
* @override_kernel_argv: (allow-none) (array zero-terminated=1) (element-type utf8): Use these as kernel arguments; if %NULL, inherit options from provided_merge_deployment
|
||||
* @out_new_deployment: (out): The new deployment path
|
||||
* @cancellable: Cancellable
|
||||
* @error: Error
|
||||
*
|
||||
* Like ostree_sysroot_deploy_tree(), but "finalization" only occurs at OS
|
||||
* shutdown time.
|
||||
*/
|
||||
gboolean
|
||||
ostree_sysroot_stage_tree (OstreeSysroot *self,
|
||||
const char *osname,
|
||||
const char *revision,
|
||||
GKeyFile *origin,
|
||||
OstreeDeployment *merge_deployment,
|
||||
char **override_kernel_argv,
|
||||
OstreeDeployment **out_new_deployment,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
/* This is a bit of a hack. When adding a new service we have to end up getting
|
||||
* into the presets for downstream distros; see e.g. https://src.fedoraproject.org/rpms/ostree/pull-request/7
|
||||
*
|
||||
* Then again, it's perhaps a bit nicer to only start the service on-demand anyways.
|
||||
*/
|
||||
const char *const systemctl_argv[] = {"systemctl", "start", "ostree-finalize-staged.service", NULL};
|
||||
int estatus;
|
||||
if (!g_spawn_sync (NULL, (char**)systemctl_argv, NULL, G_SPAWN_SEARCH_PATH,
|
||||
NULL, NULL, NULL, NULL, &estatus, error))
|
||||
return FALSE;
|
||||
if (!g_spawn_check_exit_status (estatus, error))
|
||||
return FALSE;
|
||||
|
||||
g_autoptr(OstreeDeployment) deployment = NULL;
|
||||
if (!sysroot_initialize_deployment (self, osname, revision, origin, override_kernel_argv,
|
||||
&deployment, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
/* Write out the origin file using the sepolicy from the non-merged root for
|
||||
* now (i.e. using /usr/etc policy, not /etc); in practice we don't really
|
||||
* expect people to customize the label for it.
|
||||
*/
|
||||
{ g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (self, deployment);
|
||||
glnx_autofd int deployment_dfd = -1;
|
||||
if (!glnx_opendirat (self->sysroot_fd, deployment_path, FALSE,
|
||||
&deployment_dfd, error))
|
||||
return FALSE;
|
||||
g_autoptr(OstreeSePolicy) sepolicy = ostree_sepolicy_new_at (deployment_dfd, cancellable, error);
|
||||
if (!sepolicy)
|
||||
return FALSE;
|
||||
if (!write_origin_file_internal (self, sepolicy, deployment,
|
||||
ostree_deployment_get_origin (deployment),
|
||||
GLNX_FILE_REPLACE_NODATASYNC,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* After here we defer action until shutdown. The remaining arguments (merge
|
||||
* deployment, kargs) are serialized to a state file in /run.
|
||||
*/
|
||||
|
||||
/* "target" is the staged deployment */
|
||||
g_autoptr(GVariantBuilder) builder = g_variant_builder_new ((GVariantType*)"a{sv}");
|
||||
g_variant_builder_add (builder, "{sv}", "target",
|
||||
serialize_deployment_to_variant (deployment));
|
||||
|
||||
if (merge_deployment)
|
||||
g_variant_builder_add (builder, "{sv}", "merge-deployment",
|
||||
serialize_deployment_to_variant (merge_deployment));
|
||||
|
||||
if (override_kernel_argv)
|
||||
g_variant_builder_add (builder, "{sv}", "kargs",
|
||||
g_variant_new_strv ((const char *const*)override_kernel_argv, -1));
|
||||
|
||||
const char *parent = dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED));
|
||||
if (!glnx_shutil_mkdir_p_at (AT_FDCWD, parent, 0755, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
g_autoptr(GVariant) state = g_variant_ref_sink (g_variant_builder_end (builder));
|
||||
if (!glnx_file_replace_contents_at (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED,
|
||||
g_variant_get_data (state), g_variant_get_size (state),
|
||||
GLNX_FILE_REPLACE_NODATASYNC,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
/* If we have a previous one, clean it up */
|
||||
if (self->staged_deployment)
|
||||
{
|
||||
if (!_ostree_sysroot_rmrf_deployment (self, self->staged_deployment, cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!_ostree_sysroot_reload_staged (self, error))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Invoked at shutdown time by ostree-finalize-staged.service */
|
||||
gboolean
|
||||
_ostree_sysroot_finalize_staged (OstreeSysroot *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
/* It's totally fine if there's no staged deployment; perhaps down the line
|
||||
* though we could teach the ostree cmdline to tell systemd to activate the
|
||||
* service when a staged deployment is created.
|
||||
*/
|
||||
if (!self->staged_deployment)
|
||||
return TRUE;
|
||||
|
||||
g_assert (self->staged_deployment_data);
|
||||
|
||||
g_autoptr(OstreeDeployment) merge_deployment = NULL;
|
||||
g_autoptr(GVariant) merge_deployment_v = NULL;
|
||||
if (g_variant_lookup (self->staged_deployment_data, "merge-deployment", "@a{sv}",
|
||||
&merge_deployment_v))
|
||||
{
|
||||
merge_deployment =
|
||||
_ostree_sysroot_deserialize_deployment_from_variant (merge_deployment_v, error);
|
||||
if (!merge_deployment)
|
||||
return FALSE;
|
||||
}
|
||||
g_autofree char **kargs = NULL;
|
||||
g_variant_lookup (self->staged_deployment_data, "kargs", "^a&s", &kargs);
|
||||
|
||||
/* Unlink the staged state now; if we're interrupted in the middle,
|
||||
* we don't want e.g. deal with the partially written /etc merge.
|
||||
*/
|
||||
if (!glnx_unlinkat (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED, 0, error))
|
||||
return FALSE;
|
||||
|
||||
if (!sysroot_finalize_deployment (self, self->staged_deployment, NULL, merge_deployment,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
/* TODO: Proxy across flags too? */
|
||||
OstreeSysrootSimpleWriteDeploymentFlags flags = 0;
|
||||
if (!ostree_sysroot_simple_write_deployment (self, ostree_deployment_get_osname (self->staged_deployment),
|
||||
self->staged_deployment, merge_deployment, flags,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,8 @@ struct OstreeSysroot {
|
||||
int bootversion;
|
||||
int subbootversion;
|
||||
OstreeDeployment *booted_deployment;
|
||||
OstreeDeployment *staged_deployment;
|
||||
GVariant *staged_deployment_data;
|
||||
struct timespec loaded_ts;
|
||||
|
||||
/* Only access through ostree_sysroot_[_get]repo() */
|
||||
@ -71,6 +73,7 @@ struct OstreeSysroot {
|
||||
|
||||
#define OSTREE_SYSROOT_LOCKFILE "ostree/lock"
|
||||
/* We keep some transient state in /run */
|
||||
#define _OSTREE_SYSROOT_RUNSTATE_STAGED "/run/ostree/staged-deployment"
|
||||
#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_DIR "/run/ostree/deployment-state/"
|
||||
#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT "unlocked-development"
|
||||
|
||||
@ -105,6 +108,22 @@ _ostree_sysroot_list_deployment_dirs_for_os (int deploydir_dfd,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
void
|
||||
_ostree_deployment_set_bootconfig_from_kargs (OstreeDeployment *deployment,
|
||||
char **override_kernel_argv);
|
||||
|
||||
gboolean
|
||||
_ostree_sysroot_reload_staged (OstreeSysroot *self, GError **error);
|
||||
|
||||
gboolean
|
||||
_ostree_sysroot_finalize_staged (OstreeSysroot *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
OstreeDeployment *
|
||||
_ostree_sysroot_deserialize_deployment_from_variant (GVariant *v,
|
||||
GError **error);
|
||||
|
||||
char *
|
||||
_ostree_sysroot_get_origin_relpath (GFile *path,
|
||||
guint32 *out_device,
|
||||
@ -118,6 +137,8 @@ _ostree_sysroot_rmrf_deployment (OstreeSysroot *sysroot,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
char * _ostree_sysroot_get_runstate_path (OstreeDeployment *deployment, const char *key);
|
||||
|
||||
char *_ostree_sysroot_join_lines (GPtrArray *lines);
|
||||
|
||||
gboolean _ostree_sysroot_query_bootloader (OstreeSysroot *sysroot,
|
||||
|
@ -82,6 +82,8 @@ ostree_sysroot_finalize (GObject *object)
|
||||
g_clear_object (&self->repo);
|
||||
g_clear_pointer (&self->deployments, g_ptr_array_unref);
|
||||
g_clear_object (&self->booted_deployment);
|
||||
g_clear_object (&self->staged_deployment);
|
||||
g_clear_pointer (&self->staged_deployment_data, (GDestroyNotify)g_variant_unref);
|
||||
|
||||
glnx_release_lock_file (&self->lock);
|
||||
|
||||
@ -584,14 +586,14 @@ parse_bootlink (const char *bootlink,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static char *
|
||||
get_unlocked_development_path (OstreeDeployment *deployment)
|
||||
char *
|
||||
_ostree_sysroot_get_runstate_path (OstreeDeployment *deployment, const char *key)
|
||||
{
|
||||
return g_strdup_printf ("%s%s.%d/%s",
|
||||
_OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_DIR,
|
||||
ostree_deployment_get_csum (deployment),
|
||||
ostree_deployment_get_deployserial (deployment),
|
||||
_OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT);
|
||||
key);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@ -636,9 +638,10 @@ parse_deployment (OstreeSysroot *self,
|
||||
return FALSE;
|
||||
|
||||
/* See if this is the booted deployment */
|
||||
const gboolean root_is_ostree_booted =
|
||||
(self->ostree_booted && self->root_is_sysroot);
|
||||
const gboolean looking_for_booted_deployment =
|
||||
(self->ostree_booted && self->root_is_sysroot &&
|
||||
!self->booted_deployment);
|
||||
(root_is_ostree_booted && !self->booted_deployment);
|
||||
gboolean is_booted_deployment = FALSE;
|
||||
if (looking_for_booted_deployment)
|
||||
{
|
||||
@ -665,7 +668,8 @@ parse_deployment (OstreeSysroot *self,
|
||||
ostree_deployment_set_origin (ret_deployment, origin);
|
||||
|
||||
ret_deployment->unlocked = OSTREE_DEPLOYMENT_UNLOCKED_NONE;
|
||||
g_autofree char *unlocked_development_path = get_unlocked_development_path (ret_deployment);
|
||||
g_autofree char *unlocked_development_path =
|
||||
_ostree_sysroot_get_runstate_path (ret_deployment, _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT);
|
||||
struct stat stbuf;
|
||||
if (lstat (unlocked_development_path, &stbuf) == 0)
|
||||
ret_deployment->unlocked = OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT;
|
||||
@ -789,6 +793,60 @@ ensure_repo (OstreeSysroot *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Reload the staged deployment from the file in /run */
|
||||
gboolean
|
||||
_ostree_sysroot_reload_staged (OstreeSysroot *self,
|
||||
GError **error)
|
||||
{
|
||||
GLNX_AUTO_PREFIX_ERROR ("Loading staged deployment", error);
|
||||
const gboolean root_is_ostree_booted =
|
||||
self->ostree_booted && self->root_is_sysroot;
|
||||
if (!root_is_ostree_booted)
|
||||
return TRUE; /* Note early return */
|
||||
|
||||
g_assert (self->booted_deployment);
|
||||
|
||||
g_clear_object (&self->staged_deployment);
|
||||
g_clear_pointer (&self->staged_deployment_data, (GDestroyNotify)g_variant_unref);
|
||||
|
||||
/* Read the staged state from disk */
|
||||
glnx_autofd int fd = -1;
|
||||
if (!ot_openat_ignore_enoent (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED, &fd, error))
|
||||
return FALSE;
|
||||
if (fd != -1)
|
||||
{
|
||||
g_autoptr(GBytes) contents = ot_fd_readall_or_mmap (fd, 0, error);
|
||||
if (!contents)
|
||||
return FALSE;
|
||||
g_autoptr(GVariant) staged_deployment_data =
|
||||
g_variant_new_from_bytes ((GVariantType*)"a{sv}", contents, TRUE);
|
||||
g_autoptr(GVariantDict) staged_deployment_dict =
|
||||
g_variant_dict_new (staged_deployment_data);
|
||||
|
||||
/* Parse it */
|
||||
g_autoptr(GVariant) target = NULL;
|
||||
g_autofree char **kargs = NULL;
|
||||
g_variant_dict_lookup (staged_deployment_dict, "target", "@a{sv}", &target);
|
||||
g_variant_dict_lookup (staged_deployment_dict, "kargs", "^a&s", &kargs);
|
||||
if (target)
|
||||
{
|
||||
self->staged_deployment =
|
||||
_ostree_sysroot_deserialize_deployment_from_variant (target, error);
|
||||
if (!self->staged_deployment)
|
||||
return FALSE;
|
||||
_ostree_deployment_set_bootconfig_from_kargs (self->staged_deployment, kargs);
|
||||
self->staged_deployment_data = g_steal_pointer (&staged_deployment_data);
|
||||
/* We set this flag for ostree_deployment_is_staged() because that API
|
||||
* doesn't have access to the sysroot, which currently has the
|
||||
* canonical "staged_deployment" reference.
|
||||
*/
|
||||
self->staged_deployment->staged = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
ostree_sysroot_load_if_changed (OstreeSysroot *self,
|
||||
gboolean *out_changed,
|
||||
@ -857,6 +915,7 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self,
|
||||
|
||||
g_clear_pointer (&self->deployments, g_ptr_array_unref);
|
||||
g_clear_object (&self->booted_deployment);
|
||||
g_clear_object (&self->staged_deployment);
|
||||
self->bootversion = -1;
|
||||
self->subbootversion = -1;
|
||||
|
||||
@ -880,17 +939,23 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self,
|
||||
}
|
||||
}
|
||||
|
||||
if (self->ostree_booted && self->root_is_sysroot
|
||||
&& !self->booted_deployment)
|
||||
const gboolean root_is_ostree_booted =
|
||||
self->ostree_booted && self->root_is_sysroot;
|
||||
if (root_is_ostree_booted && !self->booted_deployment)
|
||||
return glnx_throw (error, "Unexpected state: /run/ostree-booted found and in / sysroot but not in a booted deployment");
|
||||
|
||||
/* Ensure the entires are sorted */
|
||||
g_ptr_array_sort (deployments, compare_deployments_by_boot_loader_version_reversed);
|
||||
/* And then set their index variables */
|
||||
for (guint i = 0; i < deployments->len; i++)
|
||||
{
|
||||
OstreeDeployment *deployment = deployments->pdata[i];
|
||||
ostree_deployment_set_index (deployment, i);
|
||||
}
|
||||
|
||||
if (!_ostree_sysroot_reload_staged (self, error))
|
||||
return FALSE;
|
||||
|
||||
/* Determine whether we're "physical" or not, the first time we initialize */
|
||||
if (!self->loaded)
|
||||
{
|
||||
@ -949,6 +1014,20 @@ ostree_sysroot_get_booted_deployment (OstreeSysroot *self)
|
||||
return self->booted_deployment;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_get_staged_deployment:
|
||||
* @self: Sysroot
|
||||
*
|
||||
* Returns: (transfer none): The currently staged deployment, or %NULL if none
|
||||
*/
|
||||
OstreeDeployment *
|
||||
ostree_sysroot_get_staged_deployment (OstreeSysroot *self)
|
||||
{
|
||||
g_return_val_if_fail (self->loaded, NULL);
|
||||
|
||||
return self->staged_deployment;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_get_deployments:
|
||||
* @self: Sysroot
|
||||
@ -1769,7 +1848,8 @@ ostree_sysroot_deployment_unlock (OstreeSysroot *self,
|
||||
break;
|
||||
case OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT:
|
||||
{
|
||||
g_autofree char *devpath = get_unlocked_development_path (deployment);
|
||||
g_autofree char *devpath =
|
||||
_ostree_sysroot_get_runstate_path (deployment, _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT);
|
||||
g_autofree char *devpath_parent = dirname (g_strdup (devpath));
|
||||
|
||||
if (!glnx_shutil_mkdir_p_at (AT_FDCWD, devpath_parent, 0755, cancellable, error))
|
||||
|
@ -74,6 +74,8 @@ _OSTREE_PUBLIC
|
||||
GPtrArray *ostree_sysroot_get_deployments (OstreeSysroot *self);
|
||||
_OSTREE_PUBLIC
|
||||
OstreeDeployment *ostree_sysroot_get_booted_deployment (OstreeSysroot *self);
|
||||
_OSTREE_PUBLIC
|
||||
OstreeDeployment *ostree_sysroot_get_staged_deployment (OstreeSysroot *self);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
GFile *ostree_sysroot_get_deployment_directory (OstreeSysroot *self,
|
||||
@ -174,6 +176,17 @@ gboolean ostree_sysroot_deploy_tree (OstreeSysroot *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sysroot_stage_tree (OstreeSysroot *self,
|
||||
const char *osname,
|
||||
const char *revision,
|
||||
GKeyFile *origin,
|
||||
OstreeDeployment *merge_deployment,
|
||||
char **override_kernel_argv,
|
||||
OstreeDeployment **out_new_deployment,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sysroot_deployment_set_mutable (OstreeSysroot *self,
|
||||
OstreeDeployment *deployment,
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
static gboolean opt_retain;
|
||||
static gboolean opt_stage;
|
||||
static gboolean opt_retain_pending;
|
||||
static gboolean opt_retain_rollback;
|
||||
static gboolean opt_not_as_default;
|
||||
@ -50,6 +51,7 @@ static GOptionEntry options[] = {
|
||||
{ "origin-file", 0, 0, G_OPTION_ARG_FILENAME, &opt_origin_path, "Specify origin file", "FILENAME" },
|
||||
{ "no-prune", 0, 0, G_OPTION_ARG_NONE, &opt_no_prune, "Don't prune the repo when done", NULL},
|
||||
{ "retain", 0, 0, G_OPTION_ARG_NONE, &opt_retain, "Do not delete previous deployments", NULL },
|
||||
{ "stage", 0, 0, G_OPTION_ARG_NONE, &opt_stage, "Complete deployment at OS shutdown", NULL },
|
||||
{ "retain-pending", 0, 0, G_OPTION_ARG_NONE, &opt_retain_pending, "Do not delete pending deployments", NULL },
|
||||
{ "retain-rollback", 0, 0, G_OPTION_ARG_NONE, &opt_retain_rollback, "Do not delete rollback deployments", NULL },
|
||||
{ "not-as-default", 0, 0, G_OPTION_ARG_NONE, &opt_not_as_default, "Append rather than prepend new deployment", NULL },
|
||||
@ -157,31 +159,45 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat
|
||||
|
||||
g_autoptr(OstreeDeployment) new_deployment = NULL;
|
||||
g_auto(GStrv) kargs_strv = _ostree_kernel_args_to_strv (kargs);
|
||||
if (!ostree_sysroot_deploy_tree (sysroot, opt_osname, revision, origin, merge_deployment,
|
||||
kargs_strv, &new_deployment, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
OstreeSysrootSimpleWriteDeploymentFlags flags = OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN;
|
||||
if (opt_retain)
|
||||
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN;
|
||||
if (opt_stage)
|
||||
{
|
||||
if (opt_retain_pending || opt_retain_rollback)
|
||||
return glnx_throw (error, "--stage cannot currently be combined with --retain arguments");
|
||||
if (opt_not_as_default)
|
||||
return glnx_throw (error, "--stage cannot currently be combined with --not-as-default");
|
||||
if (!ostree_sysroot_stage_tree (sysroot, opt_osname, revision, origin, merge_deployment,
|
||||
kargs_strv, &new_deployment, cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (opt_retain_pending)
|
||||
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PENDING;
|
||||
if (opt_retain_rollback)
|
||||
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK;
|
||||
if (!ostree_sysroot_deploy_tree (sysroot, opt_osname, revision, origin, merge_deployment,
|
||||
kargs_strv, &new_deployment, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
OstreeSysrootSimpleWriteDeploymentFlags flags = OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN;
|
||||
if (opt_retain)
|
||||
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN;
|
||||
else
|
||||
{
|
||||
if (opt_retain_pending)
|
||||
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PENDING;
|
||||
if (opt_retain_rollback)
|
||||
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK;
|
||||
}
|
||||
|
||||
if (opt_not_as_default)
|
||||
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NOT_DEFAULT;
|
||||
|
||||
if (!ostree_sysroot_simple_write_deployment (sysroot, opt_osname, new_deployment,
|
||||
merge_deployment, flags, cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (opt_not_as_default)
|
||||
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NOT_DEFAULT;
|
||||
|
||||
if (!ostree_sysroot_simple_write_deployment (sysroot, opt_osname, new_deployment,
|
||||
merge_deployment, flags, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
/* And finally, cleanup of any leftover data.
|
||||
/* And finally, cleanup of any leftover data. In stage mode, we
|
||||
* don't do a full cleanup as we didn't touch the bootloader.
|
||||
*/
|
||||
if (opt_no_prune)
|
||||
if (opt_no_prune || opt_stage)
|
||||
{
|
||||
if (!ostree_sysroot_prepare_cleanup (sysroot, cancellable, error))
|
||||
return FALSE;
|
||||
|
58
src/ostree/ot-admin-builtin-finalize-staged.c
Normal file
58
src/ostree/ot-admin-builtin-finalize-staged.c
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Red Hat, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0+
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "ot-main.h"
|
||||
#include "ot-admin-builtins.h"
|
||||
#include "ot-admin-functions.h"
|
||||
#include "ostree.h"
|
||||
#include "otutil.h"
|
||||
|
||||
#include "ostree-cmdprivate.h"
|
||||
#include "ostree.h"
|
||||
|
||||
/* Called by ostree-finalize-staged.service, and in turn
|
||||
* invokes a cmdprivate function inside the shared library.
|
||||
*/
|
||||
gboolean
|
||||
ot_admin_builtin_finalize_staged (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
|
||||
{
|
||||
/* Just a sanity check; we shouldn't be called outside of the service though.
|
||||
*/
|
||||
struct stat stbuf;
|
||||
if (fstatat (AT_FDCWD, "/run/ostree-booted", &stbuf, 0) < 0)
|
||||
return TRUE;
|
||||
|
||||
g_autoptr(GFile) sysroot_file = g_file_new_for_path ("/");
|
||||
g_autoptr(OstreeSysroot) sysroot = ostree_sysroot_new (sysroot_file);
|
||||
|
||||
if (!ostree_sysroot_load (sysroot, cancellable, error))
|
||||
return FALSE;
|
||||
if (!ostree_cmd__private__()->ostree_finalize_staged (sysroot, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
@ -96,7 +96,9 @@ deployment_print_status (OstreeSysroot *sysroot,
|
||||
GKeyFile *origin = ostree_deployment_get_origin (deployment);
|
||||
|
||||
const char *deployment_status = "";
|
||||
if (is_pending)
|
||||
if (ostree_deployment_is_staged (deployment))
|
||||
deployment_status = " (staged)";
|
||||
else if (is_pending)
|
||||
deployment_status = " (pending)";
|
||||
else if (is_rollback)
|
||||
deployment_status = " (rollback)";
|
||||
@ -199,6 +201,16 @@ ot_admin_builtin_status (int argc, char **argv, OstreeCommandInvocation *invocat
|
||||
}
|
||||
else
|
||||
{
|
||||
OstreeDeployment *staged = ostree_sysroot_get_staged_deployment (sysroot);
|
||||
if (staged)
|
||||
{
|
||||
if (!deployment_print_status (sysroot, repo, staged,
|
||||
FALSE, FALSE, FALSE,
|
||||
cancellable,
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (guint i = 0; i < deployments->len; i++)
|
||||
{
|
||||
OstreeDeployment *deployment = deployments->pdata[i];
|
||||
|
@ -40,6 +40,7 @@ BUILTINPROTO(undeploy);
|
||||
BUILTINPROTO(deploy);
|
||||
BUILTINPROTO(cleanup);
|
||||
BUILTINPROTO(pin);
|
||||
BUILTINPROTO(finalize_staged);
|
||||
BUILTINPROTO(unlock);
|
||||
BUILTINPROTO(status);
|
||||
BUILTINPROTO(set_origin);
|
||||
|
@ -57,6 +57,9 @@ static OstreeCommand admin_subcommands[] = {
|
||||
{ "pin", OSTREE_BUILTIN_FLAG_NO_REPO,
|
||||
ot_admin_builtin_pin,
|
||||
"Change the \"pinning\" state of a deployment" },
|
||||
{ "finalize-staged", OSTREE_BUILTIN_FLAG_NO_REPO | OSTREE_BUILTIN_FLAG_HIDDEN,
|
||||
ot_admin_builtin_finalize_staged,
|
||||
"Internal command to run at shutdown time" },
|
||||
{ "status", OSTREE_BUILTIN_FLAG_NO_REPO,
|
||||
ot_admin_builtin_status,
|
||||
"List deployments" },
|
||||
|
@ -16,6 +16,10 @@
|
||||
# Next copy all of the tests/ directory
|
||||
- name: Copy test data
|
||||
synchronize: src=../../ dest=/root/tests/ archive=yes
|
||||
|
||||
# First, the Ansible-based tests
|
||||
- import_tasks: destructive/staged-deploy.yml
|
||||
|
||||
- find:
|
||||
paths: /root/tests/installed/destructive
|
||||
patterns: "itest-*.sh"
|
||||
|
24
tests/installed/destructive/staged-deploy.yml
Normal file
24
tests/installed/destructive/staged-deploy.yml
Normal file
@ -0,0 +1,24 @@
|
||||
# Test the deploy --stage functionality
|
||||
|
||||
- name: Write staged-deploy commit
|
||||
shell: |
|
||||
ostree --repo=/ostree/repo commit --parent="${commit}" -b staged-deploy --tree=ref="${commit}" --no-bindings
|
||||
ostree admin deploy --stage --karg-proc-cmdline --karg=ostreetest=yes staged-deploy
|
||||
environment:
|
||||
commit: "{{ rpmostree_status['deployments'][0]['checksum'] }}"
|
||||
- include_tasks: ../tasks/reboot.yml
|
||||
- name: Check that deploy-staged service worked
|
||||
shell: |
|
||||
# Assert that the previous boot had a journal entry for it
|
||||
journalctl -b "-1" -u ostree-finalize-staged.service | grep -q -e 'Transaction complete'
|
||||
# And that we have the new kernel argument
|
||||
grep -q -e 'ostreetest=yes' /proc/cmdline
|
||||
- name: Rollback
|
||||
shell: rpm-ostree rollback
|
||||
- include_tasks: ../tasks/reboot.yml
|
||||
- shell: |
|
||||
ostree refs --delete staged-deploy
|
||||
rpm-ostree cleanup -rp
|
||||
# And now we shouldn't have the kernel commandline entry
|
||||
- name: Check we do not have new kernel cmdline entry
|
||||
shell: grep -qv -e 'ostreetest=yes' /proc/cmdline
|
71
tests/installed/tasks/reboot.yml
Normal file
71
tests/installed/tasks/reboot.yml
Normal file
@ -0,0 +1,71 @@
|
||||
# This file is copied from atomic-host-tests
|
||||
|
||||
# vim: set ft=ansible:
|
||||
# There is no clean way to restart hosts in ansible. The general issue is that
|
||||
# the shutdown command may close sshd before ansible has time to "return" from
|
||||
# the task, even with async & poll. This is due to the fact that asynchronous
|
||||
# tasks still require a small synchronous bootstrapping script which takes 1 sec
|
||||
# to complete, during which it is vulnerable to erroring out if sshd dies.
|
||||
# To mitigate this, we prefix a sleep command before the shutdown so
|
||||
# ansible has time to move on. For more info on this issue, see:
|
||||
# https://github.com/ansible/ansible/issues/10616
|
||||
#
|
||||
# The Ansible docs now recommend this combination of tasks to handle reboots
|
||||
# https://support.ansible.com/hc/en-us/articles/201958037-Reboot-a-server-and-wait-for-it-to-come-back
|
||||
|
||||
# remember the real ansible_host for following local actions
|
||||
# (otherwise ansible will target the localhost)
|
||||
- set_fact:
|
||||
real_ansible_host: "{{ ansible_host }}"
|
||||
timeout: "{{ cli_reboot_timeout | default('120') }}"
|
||||
|
||||
# Have to account for both because Fedora STR uses the old version of these
|
||||
# inventory values for some reason.
|
||||
- when: ansible_port is defined
|
||||
set_fact:
|
||||
real_ansible_port: "{{ ansible_port }}"
|
||||
|
||||
- when: ansible_ssh_port is defined
|
||||
set_fact:
|
||||
real_ansible_port: "{{ ansible_ssh_port }}"
|
||||
|
||||
- name: Get original bootid
|
||||
command: cat /proc/sys/kernel/random/boot_id
|
||||
register: orig_bootid
|
||||
|
||||
- name: restart hosts
|
||||
when: (not skip_shutdown is defined) or (not skip_shutdown)
|
||||
shell: sleep 3 && shutdown -r now
|
||||
async: 1
|
||||
poll: 0
|
||||
ignore_errors: true
|
||||
|
||||
# NB: The following tasks use local actions, so we need to explicitly ensure
|
||||
# that they don't use sudo, which may require a password, and is not necessary
|
||||
# anyway.
|
||||
|
||||
- name: wait for hosts to come back up
|
||||
local_action:
|
||||
wait_for host={{ real_ansible_host }}
|
||||
port={{ real_ansible_port | default('22') }}
|
||||
state=started
|
||||
delay=30
|
||||
timeout={{ timeout }}
|
||||
search_regex="OpenSSH"
|
||||
become: false
|
||||
|
||||
# I'm not sure the retries are even necessary, but I'm keeping them in
|
||||
- name: Wait until bootid changes
|
||||
command: cat /proc/sys/kernel/random/boot_id
|
||||
register: new_bootid
|
||||
until: new_bootid.stdout != orig_bootid.stdout
|
||||
retries: 6
|
||||
delay: 10
|
||||
|
||||
# provide an empty iterator when a list is not provided
|
||||
# http://docs.ansible.com/ansible/playbooks_conditionals.html#loops-and-conditionals
|
||||
- name: check services have started
|
||||
service:
|
||||
name: "{{ item }}"
|
||||
state: started
|
||||
with_items: "{{ wait_for_services|default([]) }}"
|
Loading…
x
Reference in New Issue
Block a user