Merge pull request #26771 from YHNdnzj/machinectl-edit

machinectl: add verb edit and cat to operate on .nspawn files
This commit is contained in:
Yu Watanabe
2023-03-16 04:02:11 +09:00
committed by GitHub
4 changed files with 228 additions and 4 deletions

View File

@@ -376,6 +376,21 @@
formatted human-readable output.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>edit</command> <replaceable>NAME|FILE</replaceable></term>
<listitem><para>Edit the settings file of the specified machines. For the format of the settings file, refer to <citerefentry
project='man-pages'><refentrytitle>systemd.nspawn</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
If an existing settings file of the given machine can't be found, <command>edit</command> automatically
create a new settings file from scratch under <filename>/etc/</filename></para></listitem>
</varlistentry>
<varlistentry>
<term><command>cat</command> <replaceable>NAME|FILE</replaceable></term>
<listitem><para>Show the settings file of the specified machines.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>clone</command> <replaceable>NAME</replaceable> <replaceable>NAME</replaceable></term>

View File

@@ -27,6 +27,7 @@
#include "cgroup-util.h"
#include "constants.h"
#include "copy.h"
#include "edit-util.h"
#include "env-util.h"
#include "fd-util.h"
#include "format-table.h"
@@ -1412,6 +1413,163 @@ static int shell_machine(int argc, char *argv[], void *userdata) {
return process_forward(event, &forward, master, 0, machine);
}
static int normalize_nspawn_filename(const char *name, char **ret_file) {
_cleanup_free_ char *file = NULL;
assert(name);
assert(ret_file);
if (!endswith(name, ".nspawn"))
file = strjoin(name, ".nspawn");
else
file = strdup(name);
if (!file)
return log_oom();
if (!filename_is_valid(file))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid settings file name '%s'.", file);
*ret_file = TAKE_PTR(file);
return 0;
}
static int get_settings_path(const char *name, char **ret_path) {
assert(name);
assert(ret_path);
FOREACH_STRING(i, "/etc/systemd/nspawn", "/run/systemd/nspawn", "/var/lib/machines") {
_cleanup_free_ char *path = NULL;
path = path_join(i, name);
if (!path)
return -ENOMEM;
if (access(path, F_OK) >= 0) {
*ret_path = TAKE_PTR(path);
return 0;
}
}
return -ENOENT;
}
static int edit_settings(int argc, char *argv[], void *userdata) {
_cleanup_(edit_file_context_done) EditFileContext context = {
.remove_parent = false,
};
int r;
if (!on_tty())
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit machine settings if not on a tty.");
if (arg_transport != BUS_TRANSPORT_LOCAL)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Edit is only supported on the host machine.");
r = mac_selinux_init();
if (r < 0)
return r;
STRV_FOREACH(name, strv_skip(argv, 1)) {
_cleanup_free_ char *file = NULL, *path = NULL;
if (path_is_absolute(*name)) {
if (!path_is_safe(*name))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid settings file path '%s'.",
*name);
r = edit_files_add(&context, *name, NULL, NULL);
if (r < 0)
return r;
continue;
}
r = normalize_nspawn_filename(*name, &file);
if (r < 0)
return r;
r = get_settings_path(file, &path);
if (r == -ENOENT) {
log_debug("No existing settings file for machine '%s' found, creating a new file.", *name);
path = path_join("/etc/systemd/nspawn", file);
if (!path)
return log_oom();
r = edit_files_add(&context, path, NULL, NULL);
if (r < 0)
return r;
continue;
}
if (r < 0)
return log_error_errno(r, "Failed to get the path of the settings file: %m");
if (path_startswith(path, "/var/lib/machines")) {
_cleanup_free_ char *new_path = NULL;
new_path = path_join("/etc/systemd/nspawn", file);
if (!new_path)
return log_oom();
r = edit_files_add(&context, new_path, path, NULL);
} else
r = edit_files_add(&context, path, NULL, NULL);
if (r < 0)
return r;
}
return do_edit_files_and_install(&context);
}
static int cat_settings(int argc, char *argv[], void *userdata) {
int r = 0;
if (arg_transport != BUS_TRANSPORT_LOCAL)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Cat is only supported on the host machine.");
pager_open(arg_pager_flags);
STRV_FOREACH(name, strv_skip(argv, 1)) {
_cleanup_free_ char *file = NULL, *path = NULL;
int q;
if (path_is_absolute(*name)) {
if (!path_is_safe(*name))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid settings file path '%s'.",
*name);
q = cat_files(*name, /* dropins = */ NULL, /* flags = */ 0);
if (q < 0)
return r < 0 ? r : q;
continue;
}
q = normalize_nspawn_filename(*name, &file);
if (q < 0)
return r < 0 ? r : q;
q = get_settings_path(file, &path);
if (q == -ENOENT) {
log_error_errno(q, "No settings file found for machine '%s'.", *name);
r = r < 0 ? r : q;
continue;
}
if (q < 0) {
log_error_errno(q, "Failed to get the path of the settings file: %m");
return r < 0 ? r : q;
}
q = cat_files(path, /* dropins = */ NULL, /* flags = */ 0);
if (q < 0)
return r < 0 ? r : q;
}
return r;
}
static int remove_image(int argc, char *argv[], void *userdata) {
sd_bus *bus = ASSERT_PTR(userdata);
int r;
@@ -2443,18 +2601,20 @@ static int help(int argc, char *argv[], void *userdata) {
" kill NAME... Send signal to processes of a VM/container\n"
" copy-to NAME PATH [PATH] Copy files from the host to a container\n"
" copy-from NAME PATH [PATH] Copy files from a container to the host\n"
" bind NAME PATH [PATH] Bind mount a path from the host into a container\n\n"
"Image Commands:\n"
" bind NAME PATH [PATH] Bind mount a path from the host into a container\n"
"\nImage Commands:\n"
" list-images Show available container and VM images\n"
" image-status [NAME...] Show image details\n"
" show-image [NAME...] Show properties of image\n"
" edit NAME|FILE... Edit settings of one or more VMs/containers\n"
" cat NAME|FILE... Show settings of one or more VMs/containers\n"
" clone NAME NAME Clone an image\n"
" rename NAME NAME Rename an image\n"
" read-only NAME [BOOL] Mark or unmark image read-only\n"
" remove NAME... Remove an image\n"
" set-limit [NAME] BYTES Set image or pool size limit (disk quota)\n"
" clean Remove hidden (or all) images\n\n"
"Image Transfer Commands:\n"
" clean Remove hidden (or all) images\n"
"\nImage Transfer Commands:\n"
" pull-tar URL [NAME] Download a TAR container image\n"
" pull-raw URL [NAME] Download a RAW container or VM image\n"
" import-tar FILE [NAME] Import a local TAR container image\n"
@@ -2800,6 +2960,8 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
{ "login", VERB_ANY, 2, 0, login_machine },
{ "shell", VERB_ANY, VERB_ANY, 0, shell_machine },
{ "bind", 3, 4, 0, bind_mount },
{ "edit", 2, VERB_ANY, 0, edit_settings },
{ "cat", 2, VERB_ANY, 0, cat_settings },
{ "copy-to", 3, 4, 0, copy_files },
{ "copy-from", 3, 4, 0, copy_files },
{ "remove", 2, VERB_ANY, 0, remove_image },

View File

@@ -7,4 +7,8 @@ TEST_DESCRIPTION="Tests for auxiliary utilities"
# shellcheck source=test/test-functions
. "${TEST_BASE_DIR:?}/test-functions"
test_append_files() (
image_install script
)
do_test "$@"

View File

@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck disable=SC2016
set -eux
set -o pipefail
# shellcheck source=test/units/assert.sh
. "$(dirname "$0")"/assert.sh
: >/failed
at_exit() {
if [[ -v NSPAWN_NAME && -e "/var/lib/machines/$NSPAWN_NAME" ]]; then
rm -fvr "/var/lib/machines/$NSPAWN_NAME" "/etc/systemd/nspawn/$NSPAWN_NAME" "new"
fi
return 0
}
trap at_exit EXIT
export NSPAWN_NAME="machinectl-test-$RANDOM.nspawn"
cat >"/var/lib/machines/$NSPAWN_NAME" <<\EOF
[Exec]
Boot=true
EOF
EDITOR='true' script -ec 'machinectl edit "$NSPAWN_NAME"' /dev/null
[ -f "/etc/systemd/nspawn/$NSPAWN_NAME" ]
cmp "/var/lib/machines/$NSPAWN_NAME" "/etc/systemd/nspawn/$NSPAWN_NAME"
cat >new <<\EOF
[Exec]
Boot=false
EOF
script -ec 'machinectl cat "$PWD/new"' /dev/null
EDITOR='mv new' script -ec 'machinectl edit "$NSPAWN_NAME"' /dev/null
printf '%s\n' '[Exec]' 'Boot=false' | cmp - "/etc/systemd/nspawn/$NSPAWN_NAME"
touch /testok
rm /failed