Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support --workdir-root in the tmt clean images command #3426

Merged
merged 5 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/releases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
Releases
======================

tmt-1.43
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Add the ``--workdir-root`` option for the ``tmt clean images``
command so that users can specify the directory they want.


tmt-1.42.1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
1 change: 1 addition & 0 deletions tests/clean/images/main.fmf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
summary: Checks whether tmt clean images works correctly
23 changes: 23 additions & 0 deletions tests/clean/images/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/bash
# vim: dict+=/usr/share/beakerlib/dictionary.vim cpt=.,w,b,u,t,i,k
. /usr/share/beakerlib/beakerlib.sh || exit 1

rlJournalStart
rlPhaseStartSetup
rlRun "tmp=\$(mktemp -d)" 0 "Create tmp directory"
rlRun "pushd $tmp"
rlRun "set -o pipefail"
rlRun "tmt init"
rlRun "mkdir -p $tmp/testcloud/images"
rlRun "touch $tmp/testcloud/images/dummy"

rlPhaseStartTest "clean images "
rlRun "tmt clean images -v --workdir-root $tmp"
rlAssertNotExists "$tmp/testcloud/images/dummy"
rlPhaseEnd

rlPhaseStartCleanup
rlRun "popd"
rlRun "rm -r $tmp" 0 "Remove tmp directory"
rlPhaseEnd
rlJournalEnd
4 changes: 3 additions & 1 deletion tmt/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4318,7 +4318,9 @@ def images(self) -> bool:
successful = True
for method in tmt.steps.provision.ProvisionPlugin.methods():
# FIXME: ignore[union-attr]: https://github.com/teemtee/tmt/issues/1599
if not method.class_.clean_images(self, self.is_dry_run): # type: ignore[union-attr]
if not method.class_.clean_images( # type: ignore[union-attr]
self, self.is_dry_run, self.workdir_root
):
successful = False
return successful

Expand Down
26 changes: 14 additions & 12 deletions tmt/cli/_root.py
Original file line number Diff line number Diff line change
Expand Up @@ -1644,8 +1644,6 @@ def clean(

if last and id_:
raise tmt.utils.GeneralError("Options --last and --id cannot be used together.")
if workdir_root and not workdir_root.exists():
raise tmt.utils.GeneralError(f"Path '{workdir_root}' doesn't exist.")

context.obj.clean_logger = context.obj.logger.descend(
logger_name='clean', extra_shift=0
Expand All @@ -1662,6 +1660,10 @@ def clean(
if context.invoked_subcommand is None:
assert context.obj.clean_logger is not None # narrow type
workdir_root = effective_workdir_root(workdir_root)
if not workdir_root.exists():
raise tmt.utils.GeneralError(
f"Path '{workdir_root}' does not exist, skipping guest, run and image cleanup."
)
# Create another level to the hierarchy so that logging indent is
# consistent between the command and subcommands
clean_obj = tmt.Clean(
Expand All @@ -1672,15 +1674,10 @@ def clean(
cli_invocation=CliInvocation.from_context(context),
workdir_root=workdir_root,
)
if workdir_root.exists():
if 'guests' not in skip and not clean_obj.guests(id_, keep):
exit_code = 1
if 'runs' not in skip and not clean_obj.runs(id_, keep):
exit_code = 1
else:
clean_obj.warn(
f"Directory '{workdir_root}' does not exist, skipping guest and run cleanup."
)
if 'guests' not in skip and not clean_obj.guests(id_, keep):
exit_code = 1
if 'runs' not in skip and not clean_obj.runs(id_, keep):
exit_code = 1
if 'images' not in skip and not clean_obj.images():
exit_code = 1
raise SystemExit(exit_code)
Expand Down Expand Up @@ -1850,9 +1847,10 @@ def clean_guests(
# inference. See Context and ContextObjects above.
@clean.command(name='images') # type: ignore[arg-type]
@pass_context
@workdir_root_options
@verbosity_options
@dry_options
def clean_images(context: Context, **kwargs: Any) -> None:
def clean_images(context: Context, workdir_root: Optional[Path], **kwargs: Any) -> None:
"""
Remove images of supported provision methods.

Expand All @@ -1865,12 +1863,16 @@ def clean_images(context: Context, **kwargs: Any) -> None:
# cleaned, similarly to guests.
assert context.obj.clean_logger is not None # narrow type

if workdir_root and not workdir_root.exists():
raise tmt.utils.GeneralError(f"Path '{workdir_root}' doesn't exist.")

clean_obj = tmt.Clean(
logger=context.obj.clean_logger.descend(
logger_name='clean-images', extra_shift=0
).apply_verbosity_options(**kwargs),
parent=context.obj.clean,
cli_invocation=CliInvocation.from_context(context),
workdir_root=effective_workdir_root(workdir_root),
)
context.obj.clean_partials["images"].append(clean_obj.images)

Expand Down
2 changes: 1 addition & 1 deletion tmt/steps/provision/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2619,7 +2619,7 @@ def options(cls, how: Optional[str] = None) -> list[tmt.options.ClickOptionDecor
return super().options(how) + cls._guest_class.options(how)

@classmethod
def clean_images(cls, clean: 'tmt.base.Clean', dry: bool) -> bool:
def clean_images(cls, clean: 'tmt.base.Clean', dry: bool, workdir_root: Path) -> bool:
"""
Remove the images of one particular plugin
"""
Expand Down
9 changes: 5 additions & 4 deletions tmt/steps/provision/testcloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -1293,17 +1293,18 @@ def _print_local_images(self) -> None:
click.echo(f"{TESTCLOUD_IMAGES / filename}")

@classmethod
def clean_images(cls, clean: 'tmt.base.Clean', dry: bool) -> bool:
def clean_images(cls, clean: 'tmt.base.Clean', dry: bool, workdir_root: Path) -> bool:
"""
Remove the testcloud images
"""

testcloud_images = workdir_root / 'testcloud/images'
clean.info('testcloud', shift=1, color='green')
if not TESTCLOUD_IMAGES.exists():
clean.warn(f"Directory '{TESTCLOUD_IMAGES}' does not exist.", shift=2)
if not testcloud_images.exists():
clean.warn(f"Directory '{testcloud_images}' does not exist.", shift=2)
return True
successful = True
for image in TESTCLOUD_IMAGES.iterdir():
for image in testcloud_images.iterdir():
if dry:
clean.verbose(f"Would remove '{image}'.", shift=2)
else:
Expand Down