Skip to content

Commit

Permalink
clean: add a new option to clean generated config files
Browse files Browse the repository at this point in the history
cloud-init generates several config files today that are not cleaned as a part
of `cloud-init clean` operation. For example, cloud-init uses
`/etc/ssh/sshd_config.d/50-cloud-init.conf` to configure ssh daemon which is then
left uncleaned. This change adds a new option `cloud-init clean --configs` that
would clean up these config files as well. Additionally, the cli documentation
has also been updated to document this new option.

Reference:
#1618
Fixes: GH-3240

Signed-off-by: Ani Sinha <anisinha@redhat.com>
  • Loading branch information
ani-sinha committed Aug 24, 2023
1 parent f7a2c48 commit 7d52f68
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 10 deletions.
26 changes: 23 additions & 3 deletions cloudinit/cmd/clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
)

ETC_MACHINE_ID = "/etc/machine-id"
GEN_CONFIG_FILES = [
"/etc/ssh/sshd_config.d/50-cloud-init.conf",
"/etc/NetworkManager/conf.d/99-cloud-init.conf",
"/etc/NetworkManager/conf.d/30-cloud-init-ip6-addr-gen-mode.conf",
"/etc/network/interfaces.d/50-cloud-init.cfg",
]


def get_parser(parser=None):
Expand Down Expand Up @@ -77,24 +83,36 @@ def get_parser(parser=None):
dest="remove_seed",
help="Remove cloud-init seed directory /var/lib/cloud/seed.",
)
parser.add_argument(
"-c",
"--configs",
action="store_true",
default=False,
dest="remove_config",
help="Remove cloud-init generated config files.",
)
return parser


def remove_artifacts(remove_logs, remove_seed=False):
def remove_artifacts(remove_logs, remove_seed=False, remove_config=False):
"""Helper which removes artifacts dir and optionally log files.
@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: Boolean. Set True to also delete cloud-init
generated config file. False keeps them.
@returns: 0 on success, 1 otherwise.
"""
init = Init(ds_deps=[])
init.read_cfg()
if remove_logs:
for log_file in get_config_logfiles(init.cfg):
del_file(log_file)

if remove_config:
for conf in GEN_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")
Expand All @@ -121,7 +139,9 @@ def remove_artifacts(remove_logs, remove_seed=False):

def handle_clean_args(name, args):
"""Handle calls to 'cloud-init clean' as a subcommand."""
exit_code = remove_artifacts(args.remove_logs, args.remove_seed)
exit_code = remove_artifacts(
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
Expand Down
8 changes: 5 additions & 3 deletions doc/rtd/reference/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ Possible subcommands include:
:command:`clean`
================

Remove ``cloud-init`` artifacts from :file:`/var/lib/cloud` to simulate a clean
instance. On reboot, ``cloud-init`` will re-run all stages as it did on
first boot.
Remove ``cloud-init`` artifacts from :file:`/var/lib/cloud` and config files
(best effort) to simulate a clean instance. On reboot, ``cloud-init`` will
re-run all stages as it did on first boot.

* :command:`--logs`: Optionally remove all ``cloud-init`` log files in
:file:`/var/log/`.
Expand All @@ -78,6 +78,8 @@ first boot.
remove the file. Best practice when cloning a golden image, to ensure the
next boot of that image auto-generates a unique machine ID.
`More details on machine-id`_.
* :command:`--configs`: Optionally remove all ``cloud-init`` generated config
files.

.. _cli_collect_logs:

Expand Down
41 changes: 37 additions & 4 deletions tests/unittests/cmd/test_clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@

MyPaths = namedtuple("MyPaths", "cloud_dir")
CleanPaths = namedtuple(
"CleanPaths", ["tmpdir", "cloud_dir", "clean_dir", "log", "output_log"]
"CleanPaths",
["tmpdir", "cloud_dir", "clean_dir", "log", "output_log"],
)


Expand Down Expand Up @@ -69,6 +70,33 @@ def test_remove_artifacts_removes_logs(self, clean_paths, init_class):
), f"Unexpected file {clean_paths.output_log}"
assert 0 == retcode

def test_remove_conf(self, clean_paths, init_class):
"""remove_config removes config files when True."""

TEST_GEN_CONFIG_FILES = [
clean_paths.tmpdir.join(conf_file)
for conf_file in clean.GEN_CONFIG_FILES
]
for conf_path in TEST_GEN_CONFIG_FILES:
assert conf_path.exists() is False, f"Unexpected {conf_path}"
ensure_dir(os.path.dirname(conf_path))
conf_path.write(f"#generated by cloud-init\ntouch {conf_path}\n")

with mock.patch(
"cloudinit.cmd.clean.GEN_CONFIG_FILES", TEST_GEN_CONFIG_FILES
):
retcode = wrap_and_call(
"cloudinit.cmd.clean",
{"Init": {"side_effect": init_class}},
clean.remove_artifacts,
remove_logs=False,
remove_config=True,
)

for conf_path in TEST_GEN_CONFIG_FILES:
assert conf_path.exists() is False, f"Unexpected file {conf_path}"
assert 0 == retcode

@pytest.mark.allow_all_subp
def test_remove_artifacts_runparts_clean_d(self, clean_paths, init_class):
"""remove_artifacts performs runparts on CLEAN_RUNPARTS_DIR"""
Expand Down Expand Up @@ -229,10 +257,14 @@ def fake_subp(cmd, capture):
return "", ""

myargs = namedtuple(
"MyArgs", "remove_logs remove_seed reboot machine_id"
"MyArgs", "remove_logs remove_seed remove_config reboot machine_id"
)
cmdargs = myargs(
remove_logs=False, remove_seed=False, reboot=True, machine_id=False
remove_logs=False,
remove_seed=False,
remove_config=False,
reboot=True,
machine_id=False,
)
retcode = wrap_and_call(
"cloudinit.cmd.clean",
Expand Down Expand Up @@ -264,11 +296,12 @@ def test_handle_clean_args_removed_machine_id(
"""handle_clean_args removes /etc/machine-id when arg is True."""
uses_systemd.return_value = systemd_val
myargs = namedtuple(
"MyArgs", "remove_logs remove_seed reboot machine_id"
"MyArgs", "remove_logs remove_seed remove_config reboot machine_id"
)
cmdargs = myargs(
remove_logs=False,
remove_seed=False,
remove_config=False,
reboot=False,
machine_id=machine_id,
)
Expand Down

0 comments on commit 7d52f68

Please sign in to comment.