sysroot: Add concept of deployment "pinning" 📌

Example user story: Jane rebases her OS to a new major version N, and wants to
keep around N-1 even after a few upgrades for a while so she can easily roll
back. I plan to add `rpm-ostree rebase --pin` to opt-in to this for example.

Builds on the new `libostree-transient` group to store pinning state there.

Closes: https://github.com/ostreedev/ostree/issues/1460

Closes: #1464
Approved by: jlebon
This commit is contained in:
Colin Walters 2018-02-23 12:46:32 -05:00 committed by Atomic Bot
parent c40a47e965
commit 7f88fddcd4
15 changed files with 323 additions and 1 deletions

View File

@ -26,6 +26,7 @@ ostree-admin-config-diff.1 ostree-admin-deploy.1 \
ostree-admin-init-fs.1 ostree-admin-instutil.1 ostree-admin-os-init.1 \
ostree-admin-status.1 ostree-admin-set-origin.1 ostree-admin-switch.1 \
ostree-admin-undeploy.1 ostree-admin-upgrade.1 ostree-admin-unlock.1 \
ostree-admin-pin.1 \
ostree-admin.1 ostree-cat.1 ostree-checkout.1 ostree-checksum.1 \
ostree-commit.1 ostree-export.1 ostree-gpg-sign.1 ostree-config.1 \
ostree-diff.1 ostree-fsck.1 ostree-init.1 ostree-log.1 ostree-ls.1 \

View File

@ -74,6 +74,7 @@ ostree_SOURCES += \
src/ostree/ot-admin-builtin-set-origin.c \
src/ostree/ot-admin-builtin-status.c \
src/ostree/ot-admin-builtin-switch.c \
src/ostree/ot-admin-builtin-pin.c \
src/ostree/ot-admin-builtin-upgrade.c \
src/ostree/ot-admin-builtin-unlock.c \
src/ostree/ot-admin-builtins.h \

View File

@ -169,6 +169,7 @@ ostree_deployment_get_bootconfig
ostree_deployment_get_origin
ostree_deployment_get_origin_relpath
ostree_deployment_get_unlocked
ostree_deployment_is_pinned
ostree_deployment_set_index
ostree_deployment_set_bootserial
ostree_deployment_set_bootconfig
@ -509,6 +510,7 @@ ostree_sysroot_init_osname
ostree_sysroot_deployment_set_kargs
ostree_sysroot_deployment_set_mutable
ostree_sysroot_deployment_unlock
ostree_sysroot_deployment_set_pinned
ostree_sysroot_write_deployments
ostree_sysroot_write_deployments_with_options
ostree_sysroot_write_origin_file

View File

@ -399,6 +399,37 @@ _ostree_admin_os_init() {
return 0
}
_ostree_admin_pin() {
local boolean_options="
$main_boolean_options
"
local options_with_args="
--sysroot
"
local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" )
case "$prev" in
--sysroot)
__ostree_compreply_dirs_only
return 0
;;
$options_with_args_glob )
return 0
;;
esac
case "$cur" in
-*)
local all_options="$boolean_options $options_with_args"
__ostree_compreply_all_options
;;
esac
return 0
}
_ostree_admin_set_origin() {
local boolean_options="
$main_boolean_options

82
man/ostree-admin-pin.xml Normal file
View File

@ -0,0 +1,82 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!--
Copyright 2018 Red Hat
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.
-->
<refentry id="ostree">
<refentryinfo>
<title>ostree admin pin</title>
<productname>OSTree</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Colin</firstname>
<surname>Walters</surname>
<email>walters@verbum.org</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>ostree admin pin</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>ostree-admin-pin</refname>
<refpurpose>Explicitly retain deployment at a given index</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>ostree admin pin</command> <arg choice="req">INDEX</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
Ensures the deployment at <literal>INDEX</literal>, will not be garbage
collected by default. This is termed "pinning". If the
<literal>-u</literal> option is provided, undoes a pinning operation.
</para>
</refsect1>
<refsect1>
<title>Options</title>
<variablelist>
<varlistentry>
<term><option>--unpin</option>,<option>-u</option></term>
<listitem><para>
Undoes a pinning operation.
</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
</refentry>

View File

@ -20,6 +20,8 @@
/* Add new symbols here. Release commits should copy this section into -released.sym. */
LIBOSTREE_2018.3 {
ostree_deployment_origin_remove_transient_state;
ostree_sysroot_deployment_set_pinned;
ostree_deployment_is_pinned;
} LIBOSTREE_2018.2;
/* Stub section for the stable release *after* this development one; don't

View File

@ -322,3 +322,20 @@ ostree_deployment_get_unlocked (OstreeDeployment *self)
{
return self->unlocked;
}
/**
* ostree_deployment_is_pinned:
* @self: Deployment
*
* See ostree_sysroot_deployment_set_pinned().
*
* Returns: `TRUE` if deployment will not be subject to GC
* Since: 2018.3
*/
gboolean
ostree_deployment_is_pinned (OstreeDeployment *self)
{
if (!self->origin)
return FALSE;
return g_key_file_get_boolean (self->origin, OSTREE_ORIGIN_TRANSIENT_GROUP, "pinned", NULL);
}

View File

@ -74,6 +74,9 @@ _OSTREE_PUBLIC
GKeyFile *ostree_deployment_get_origin (OstreeDeployment *self);
_OSTREE_PUBLIC
gboolean ostree_deployment_is_pinned (OstreeDeployment *self);
_OSTREE_PUBLIC
void ostree_deployment_set_index (OstreeDeployment *self, int index);
_OSTREE_PUBLIC

View File

@ -1572,12 +1572,14 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot,
/* Retain deployment if:
* - we're explicitly asked to, or
* - it's pinned
* - the deployment is for another osname, or
* - we're keeping pending deployments and this is a pending deployment, or
* - this is the merge or boot deployment, or
* - we're keeping rollback deployments and this is a rollback deployment
*/
if (retain
|| ostree_deployment_is_pinned (deployment)
|| !osname_matches
|| (retain_pending && !passed_crossover)
|| (is_booted || is_merge)
@ -1832,3 +1834,45 @@ ostree_sysroot_deployment_unlock (OstreeSysroot *self,
return TRUE;
}
/**
* ostree_sysroot_deployment_set_pinned:
* @self: Sysroot
* @deployment: A deployment
* @is_pinned: Whether or not deployment will be automatically GC'd
* @error: Error
*
* By default, deployments may be subject to garbage collection. Typical uses of
* libostree only retain at most 2 deployments. If @is_pinned is `TRUE`, a
* metadata bit will be set causing libostree to avoid automatic GC of the
* deployment. However, this is really an "advisory" note; it's still possible
* for e.g. older versions of libostree unaware of pinning to GC the deployment.
*
* This function does nothing and returns successfully if the deployment
* is already in the desired pinning state.
*
* Since: 2018.3
*/
gboolean
ostree_sysroot_deployment_set_pinned (OstreeSysroot *self,
OstreeDeployment *deployment,
gboolean is_pinned,
GError **error)
{
const gboolean current_pin = ostree_deployment_is_pinned (deployment);
if (is_pinned == current_pin)
return TRUE;
g_autoptr(OstreeDeployment) deployment_clone = ostree_deployment_clone (deployment);
GKeyFile *origin_clone = ostree_deployment_get_origin (deployment_clone);
if (is_pinned)
g_key_file_set_boolean (origin_clone, OSTREE_ORIGIN_TRANSIENT_GROUP, "pinned", TRUE);
else
g_key_file_remove_key (origin_clone, OSTREE_ORIGIN_TRANSIENT_GROUP, "pinned", NULL);
if (!ostree_sysroot_write_origin_file (self, deployment, origin_clone, NULL, error))
return FALSE;
return TRUE;
}

View File

@ -181,6 +181,12 @@ gboolean ostree_sysroot_deployment_set_mutable (OstreeSysroot *self,
GCancellable *cancellable,
GError **error);
_OSTREE_PUBLIC
gboolean ostree_sysroot_deployment_set_pinned (OstreeSysroot *self,
OstreeDeployment *deployment,
gboolean is_pinned,
GError **error);
_OSTREE_PUBLIC
gboolean ostree_sysroot_deployment_unlock (OstreeSysroot *self,
OstreeDeployment *deployment,

View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 2018 Colin Walters <walters@verbum.org>
*
* 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 <stdlib.h>
#include "ot-main.h"
#include "ot-admin-builtins.h"
#include "ot-admin-functions.h"
#include "ostree.h"
#include "otutil.h"
/* ATTENTION:
* Please remember to update the bash-completion script (bash/ostree) and
* man page (man/ostree-admin-pin.xml) when changing the option list.
*/
static gboolean opt_unpin;
static GOptionEntry options[] = {
{ "unpin", 'u', 0, G_OPTION_ARG_NONE, &opt_unpin, "Unset pin", NULL },
{ NULL }
};
gboolean
ot_admin_builtin_pin (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
{
g_autoptr(GOptionContext) context = g_option_context_new ("INDEX");
g_autoptr(OstreeSysroot) sysroot = NULL;
if (!ostree_admin_option_context_parse (context, options, &argc, &argv,
OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER,
invocation, &sysroot, cancellable, error))
return FALSE;
if (argc < 2)
{
ot_util_usage_error (context, "INDEX must be specified", error);
return FALSE;
}
const char *deploy_index_str = argv[1];
const int deploy_index = atoi (deploy_index_str);
g_autoptr(OstreeDeployment) target_deployment = ot_admin_get_indexed_deployment (sysroot, deploy_index, error);
if (!target_deployment)
return FALSE;
gboolean current_pin = ostree_deployment_is_pinned (target_deployment);
const gboolean desired_pin = !opt_unpin;
if (current_pin == desired_pin)
g_print ("Deployment is already %s\n", current_pin ? "pinned" : "unpinned");
else
{
if (!ostree_sysroot_deployment_set_pinned (sysroot, target_deployment, desired_pin, error))
return FALSE;
g_print ("Deployment is now %s\n", desired_pin ? "pinned" : "unpinned");
}
return TRUE;
}

View File

@ -149,6 +149,8 @@ ot_admin_builtin_status (int argc, char **argv, OstreeCommandInvocation *invocat
ostree_deployment_unlocked_state_to_string (unlocked),
red_bold_suffix);
}
if (ostree_deployment_is_pinned (deployment))
g_print (" Pinned: yes\n");
if (!origin)
g_print (" origin: none\n");
else

View File

@ -39,6 +39,7 @@ BUILTINPROTO(init_fs);
BUILTINPROTO(undeploy);
BUILTINPROTO(deploy);
BUILTINPROTO(cleanup);
BUILTINPROTO(pin);
BUILTINPROTO(unlock);
BUILTINPROTO(status);
BUILTINPROTO(set_origin);

View File

@ -54,6 +54,9 @@ static OstreeCommand admin_subcommands[] = {
{ "set-origin", OSTREE_BUILTIN_FLAG_NO_REPO,
ot_admin_builtin_set_origin,
"Set Origin and create a new origin file" },
{ "pin", OSTREE_BUILTIN_FLAG_NO_REPO,
ot_admin_builtin_pin,
"Change the \"pinning\" state of a deployment" },
{ "status", OSTREE_BUILTIN_FLAG_NO_REPO,
ot_admin_builtin_status,
"List deployments" },

View File

@ -26,7 +26,7 @@ set -euo pipefail
# Exports OSTREE_SYSROOT so --sysroot not needed.
setup_os_repository "archive" "syslinux"
echo "1..3"
echo "1..6"
${CMD_PREFIX} ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmaster/x86_64-runtime
rev=$(${CMD_PREFIX} ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime)
@ -63,3 +63,50 @@ assert_has_dir sysroot/boot/ostree/testos-${bootcsum}
assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/os-release 'NAME=TestOS'
echo "ok manual cleanup"
assert_n_pinned() {
local n=$1
${CMD_PREFIX} ostree admin status > status.txt
local n_pinned="$(grep -F -c -e 'Pinned: yes' < status.txt)"
if test "${n_pinned}" '!=' "${n}"; then
cat status.txt
fatal "${n_pinned} != ${n}"
fi
}
assert_n_deployments() {
local n=$1
${CMD_PREFIX} ostree admin status > status.txt
local n_deployments="$(grep -F -c -e 'Version: ' < status.txt)"
if test "${n_deployments}" '!=' "${n}"; then
cat status.txt
fatal "${n_deployments} != ${n}"
fi
}
assert_n_pinned 0
${CMD_PREFIX} ostree admin pin 0
assert_n_pinned 1
${CMD_PREFIX} ostree admin pin -u 0
assert_n_pinned 0
echo "ok pin unpin"
${CMD_PREFIX} ostree admin pin 0
${CMD_PREFIX} ostree admin pin 1
assert_n_pinned 2
assert_n_deployments 2
os_repository_new_commit
${CMD_PREFIX} ostree admin upgrade --os=testos
assert_n_pinned 2
assert_n_deployments 3
echo "ok pin across upgrades"
${CMD_PREFIX} ostree admin pin -u 1
os_repository_new_commit
${CMD_PREFIX} ostree admin upgrade --os=testos
assert_n_pinned 1
assert_n_deployments 3
os_repository_new_commit
${CMD_PREFIX} ostree admin upgrade --os=testos
assert_n_pinned 1
assert_n_deployments 3
echo "ok pinning"