File: clean.py

package info (click to toggle)
cloud-init 25.1.4-1%2Bdeb13u1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 12,100 kB
  • sloc: python: 134,496; sh: 3,879; makefile: 128; javascript: 30; xml: 22
file content (195 lines) | stat: -rwxr-xr-x 6,024 bytes parent folder | download
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()