1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
|
#!/usr/bin/env python3
# Copyright (C) 2017 Canonical Ltd.
#
# This file is part of cloud-init. See LICENSE file for license information.
"""Define 'clean' utility and handler as part of cloud-init command line."""
import argparse
import glob
import os
import sys
from cloudinit import settings
from cloudinit.distros import uses_systemd
from cloudinit.log import log_util
from cloudinit.net.netplan import CLOUDINIT_NETPLAN_FILE
from cloudinit.stages import Init
from cloudinit.subp import ProcessExecutionError, runparts, subp
from cloudinit.util import (
del_dir,
del_file,
get_config_logfiles,
is_link,
write_file,
)
ETC_MACHINE_ID = "/etc/machine-id"
GEN_NET_CONFIG_FILES = [
CLOUDINIT_NETPLAN_FILE,
"/etc/NetworkManager/conf.d/99-cloud-init.conf",
"/etc/NetworkManager/conf.d/30-cloud-init-ip6-addr-gen-mode.conf",
"/etc/NetworkManager/system-connections/cloud-init-*.nmconnection",
"/etc/systemd/network/10-cloud-init-*.network",
"/etc/network/interfaces.d/50-cloud-init.cfg",
]
GEN_SSH_CONFIG_FILES = [
"/etc/ssh/sshd_config.d/50-cloud-init.conf",
]
def get_parser(parser=None):
"""Build or extend an arg parser for clean utility.
@param parser: Optional existing ArgumentParser instance representing the
clean subcommand which will be extended to support the args of
this utility.
@returns: ArgumentParser with proper argument configuration.
"""
if not parser:
parser = argparse.ArgumentParser(
prog="clean",
description=(
"Remove logs, configs and artifacts so cloud-init re-runs "
"on a clean system"
),
)
parser.add_argument(
"-l",
"--logs",
action="store_true",
default=False,
dest="remove_logs",
help="Remove cloud-init logs.",
)
parser.add_argument(
"--machine-id",
action="store_true",
default=False,
help=(
"Set /etc/machine-id to 'uninitialized\n' for golden image"
"creation. On next boot, systemd generates a new machine-id."
" Remove /etc/machine-id on non-systemd environments."
),
)
parser.add_argument(
"-r",
"--reboot",
action="store_true",
default=False,
help="Reboot system after logs are cleaned so cloud-init re-runs.",
)
parser.add_argument(
"-s",
"--seed",
action="store_true",
default=False,
dest="remove_seed",
help="Remove cloud-init seed directory /var/lib/cloud/seed.",
)
parser.add_argument(
"-c",
"--configs",
choices=[
"all",
"ssh_config",
"network",
],
default=[],
nargs="+",
dest="remove_config",
help="Remove cloud-init generated config files of a certain type."
" Config types: all, ssh_config, network",
)
return parser
def remove_artifacts(init, remove_logs, remove_seed=False, remove_config=None):
"""Helper which removes artifacts dir and optionally log files.
@param: init: Init object to use
@param: remove_logs: Boolean. Set True to delete the cloud_dir path. False
preserves them.
@param: remove_seed: Boolean. Set True to also delete seed subdir in
paths.cloud_dir.
@param: remove_config: List of strings.
Can be any of: all, network, ssh_config.
@returns: 0 on success, 1 otherwise.
"""
init.read_cfg()
if remove_logs:
for log_file in get_config_logfiles(init.cfg):
del_file(log_file)
if remove_config and set(remove_config).intersection(["all", "network"]):
for path in GEN_NET_CONFIG_FILES:
for conf in glob.glob(path):
del_file(conf)
if remove_config and set(remove_config).intersection(
["all", "ssh_config"]
):
for conf in GEN_SSH_CONFIG_FILES:
del_file(conf)
if not os.path.isdir(init.paths.cloud_dir):
return 0 # Artifacts dir already cleaned
seed_path = os.path.join(init.paths.cloud_dir, "seed")
for path in glob.glob("%s/*" % init.paths.cloud_dir):
if path == seed_path and not remove_seed:
continue
try:
if os.path.isdir(path) and not is_link(path):
del_dir(path)
else:
del_file(path)
except OSError as e:
log_util.error("Could not remove {0}: {1}".format(path, str(e)))
return 1
try:
runparts(settings.CLEAN_RUNPARTS_DIR)
except Exception as e:
log_util.error(
f"Failure during run-parts of {settings.CLEAN_RUNPARTS_DIR}: {e}"
)
return 1
return 0
def handle_clean_args(name, args):
"""Handle calls to 'cloud-init clean' as a subcommand."""
init = Init(ds_deps=[])
exit_code = remove_artifacts(
init, args.remove_logs, args.remove_seed, args.remove_config
)
if args.machine_id:
if uses_systemd():
# Systemd v237 and later will create a new machine-id on next boot
write_file(ETC_MACHINE_ID, "uninitialized\n", mode=0o444)
else:
# Non-systemd like FreeBSD regen machine-id when file is absent
del_file(ETC_MACHINE_ID)
if exit_code == 0 and args.reboot:
cmd = init.distro.shutdown_command(
mode="reboot", delay="now", message=None
)
try:
subp(cmd, capture=False)
except ProcessExecutionError as e:
log_util.error(
'Could not reboot this system using "{0}": {1}'.format(
cmd, str(e)
)
)
exit_code = 1
return exit_code
def main():
"""Tool to collect and tar all cloud-init related logs."""
parser = get_parser()
sys.exit(handle_clean_args("clean", parser.parse_args()))
if __name__ == "__main__":
main()
|