Skip to content

Copy files with same destination in single rsync #3669

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

Closed
wants to merge 13 commits into from
Closed

Conversation

martinhoyer
Copy link
Collaborator

@martinhoyer martinhoyer commented Apr 15, 2025

tmt scripts, like tmt-reboot, etc are currently being copied one by one. This process can take a long time and this PR aims to copy all scripts in a single rsync execution.

Resolves #3290

Pull Request Checklist

  • implement the feature
  • write the documentation
  • extend the test coverage
  • update the specification
  • adjust plugin docstring
  • modify the json schema
  • mention the version
  • include a release note

@martinhoyer martinhoyer self-assigned this Apr 15, 2025
@martinhoyer martinhoyer added code | no functional change "No Functional Change" intended. Patch should not change tmt's behavior in any way. step | execute Stuff related to the execute step step | provision Stuff related to the provision step labels Apr 15, 2025
@martinhoyer
Copy link
Collaborator Author

@coderabbitai full review

@martinhoyer martinhoyer requested review from psss and happz April 15, 2025 14:07
Copy link

coderabbitai bot commented Apr 15, 2025

📝 Walkthrough

"""

Walkthrough

The ScriptTemplate.__exit__ method was modified to unlink the rendered script file only if it exists. The ExecutePlugin.prepare_scripts method was refactored to stage all scripts and their aliases locally in a temporary directory that mirrors the guest's filesystem layout, creating relative symbolic links for aliases. The entire staged directory is pushed at once to the guest's root directory using archive mode with symlink preservation, replacing multiple individual pushes and guest-side directory creation. The GuestSsh.push method was enhanced to handle rsync trailing slash behavior automatically, retrying after installing rsync if missing. The GuestLocal.push and GuestContainer.push methods were updated to accept single or multiple sources with appropriate handling and error reporting.

Changes

File(s) Change Summary
tmt/steps/execute/init.py Modified ScriptTemplate.__exit__ to conditionally unlink rendered scripts; rewrote ExecutePlugin.prepare_scripts to stage all scripts and aliases locally and push entire directory in one operation with symlink preservation.
tmt/steps/provision/init.py Enhanced GuestSsh.push to handle rsync trailing slash for source and destination, retry after installing rsync if missing, and added detailed logging and error handling.
tmt/steps/provision/local.py Changed GuestLocal.push signature to accept single or list of sources; implemented file/directory copying with destination directory creation and error handling.
tmt/steps/provision/podman.py Changed GuestContainer.push signature to accept single or list of sources; iterates over each source to run podman cp with error handling and toolbox environment path adjustments.

Sequence Diagram(s)

sequenceDiagram
    participant ExecutePlugin
    participant ScriptTemplate
    participant Guest

    ExecutePlugin->>ScriptTemplate: Render scripts and create aliases locally
    ScriptTemplate-->>ExecutePlugin: Provide rendered script paths
    ExecutePlugin->>Guest: Push entire staged directory in one operation
    Guest-->>ExecutePlugin: Confirm files transferred
    ExecutePlugin->>ScriptTemplate: Cleanup staged files after push
Loading

Assessment against linked issues

Objective Addressed Explanation
Batch copy scripts instead of pushing one by one (#3290) prepare_scripts batches all scripts and aliases locally and pushes them in a single operation.
Allow push method to accept multiple sources for batch transfer (#3290) GuestContainer.push and GuestLocal.push updated to accept multiple sources and handle them properly.
Add executability with rsync options instead of separate chmod command (#3290) No implementation of rsync --chmod or related options for executability observed in the changes.
"""

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9df8d59 and c819c32.

📒 Files selected for processing (4)
  • tmt/steps/execute/__init__.py (4 hunks)
  • tmt/steps/provision/__init__.py (2 hunks)
  • tmt/steps/provision/local.py (1 hunks)
  • tmt/steps/provision/podman.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tmt/steps/provision/local.py
  • tmt/steps/provision/podman.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
tmt/steps/provision/__init__.py (2)
tmt/log.py (3)
  • debug (829-848)
  • warn (861-866)
  • fail (868-875)
tmt/utils/__init__.py (8)
  • debug (1974-1989)
  • warn (1991-1996)
  • Command (1093-1381)
  • to_element (1075-1080)
  • to_element (1110-1119)
  • RunError (2368-2410)
  • is_dry_run (1872-1877)
  • fail (1998-2003)
⏰ Context from checks skipped due to timeout of 90000ms (47)
  • GitHub Check: osh-diff-scan:fedora-rawhide-x86_64
  • GitHub Check: testing-farm:centos-stream-9-x86_64:core
  • GitHub Check: osh-diff-scan:fedora-rawhide-x86_64
  • GitHub Check: testing-farm:centos-stream-9-x86_64:core
  • GitHub Check: testing-farm:centos-stream-9-x86_64:core
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:full
  • GitHub Check: testing-farm:fedora-41-x86_64:full
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: testing-farm:fedora-42-x86_64:internal-wow
  • GitHub Check: testing-farm:fedora-42-x86_64:internal-plugins
  • GitHub Check: testing-farm:fedora-42-x86_64:provision
  • GitHub Check: testing-farm:fedora-42-x86_64:extended-unit-tests
  • GitHub Check: testing-farm:fedora-42-x86_64:full
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: testing-farm:centos-stream-9-x86_64:full
  • GitHub Check: testing-farm:centos-stream-9-x86_64:core
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: testing-farm:fedora-42-x86_64:internal-wow
  • GitHub Check: testing-farm:fedora-42-x86_64:internal-plugins
  • GitHub Check: testing-farm:fedora-42-x86_64:provision
  • GitHub Check: testing-farm:fedora-42-x86_64:extended-unit-tests
  • GitHub Check: testing-farm:centos-stream-9-x86_64:full
  • GitHub Check: testing-farm:fedora-42-x86_64:full
  • GitHub Check: testing-farm:fedora-41-x86_64:full
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:full
  • GitHub Check: testing-farm:centos-stream-9-x86_64:core
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
🔇 Additional comments (13)
tmt/steps/provision/__init__.py (5)

2513-2524: Improved documentation of rsync behavior.

The enhanced docstring clearly explains how rsync's trailing slash behavior is handled, making it much easier for developers to understand what to expect when using this method.


2548-2562: Good source path type detection with error handling.

This new code properly detects whether the source is a directory or file and handles cases where path checking fails with appropriate warnings and fallback behavior.


2564-2576: Proper handling of rsync path formatting.

The code intelligently formats source and destination paths to control rsync's behavior when copying directories vs files, ensuring expected results in all cases.


2577-2597: Clean encapsulation of rsync command execution.

Moving the rsync command execution into a local function improves readability and code organization.


2598-2638: Robust error handling with retry mechanism.

The error handling has been significantly improved with:

  1. Clear logging of first attempt failures
  2. Intelligent retry after installing rsync if missing
  3. Appropriate error propagation for different failure scenarios
  4. Detailed debug messages for troubleshooting

This will make the push operation much more reliable across different environments.

tmt/steps/execute/__init__.py (8)

163-166: Enhanced robustness with FileNotFoundError handling

This change improves the ScriptTemplate.exit method by adding a safety check before unlinking the rendered file and using a context manager to suppress FileNotFoundError. This prevents crashes if the file is already removed or doesn't exist.


113-122: Good addition of the copy_into method to the Script class

The new method provides a clean abstraction for copying scripts to a specified location, which supports the refactored script preparation logic. The implementation is concise and well-documented.


167-177: Properly extended copy_into for ScriptTemplate class

The override correctly handles the special case of template scripts by copying from the rendered path and returning the path for cleanup tracking. The assertion guarantees the rendered script path exists before copying.


837-941: Well-structured error handling with cleanup in try-finally block

The code correctly ensures cleanup of temporary files even if an error occurs during script staging or pushing, preventing resource leaks.


843-851: Robust handling of non-absolute paths

The code properly handles both absolute and non-absolute paths with appropriate warnings, treating non-absolute paths as relative to root.

Also applies to: 857-872


907-919: Good error handling for symlink creation

The code properly handles potential errors when creating symbolic links, catching OSError and providing a warning with the specific error message.


825-834: Clear improvement to script preparation approach

The new implementation efficiently stages all scripts locally in a single operation, which should improve performance by reducing the number of individual push operations and the associated network overhead.


893-906: Correctly handles creation of relative symbolic links

The code calculates the proper relative path between symbolic links and their targets, ensuring that symlinks will continue to work correctly when pushed to the guest.


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 718c4e4 and e62fb5e.

📒 Files selected for processing (4)
  • tmt/steps/execute/__init__.py (3 hunks)
  • tmt/steps/provision/__init__.py (3 hunks)
  • tmt/steps/provision/local.py (1 hunks)
  • tmt/steps/provision/podman.py (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
tmt/steps/execute/__init__.py (2)
tmt/steps/provision/__init__.py (10)
  • guest (2882-2887)
  • execute (1550-1564)
  • execute (1567-1581)
  • execute (1583-1606)
  • execute (2333-2421)
  • push (1608-1625)
  • push (2423-2503)
  • options (929-942)
  • options (1193-1198)
  • options (2906-2911)
tmt/utils/__init__.py (3)
  • Command (1086-1374)
  • debug (1967-1982)
  • ShellScript (1016-1083)
⏰ Context from checks skipped due to timeout of 90000ms (32)
  • GitHub Check: testing-farm:fedora-42-x86_64:internal-wow
  • GitHub Check: testing-farm:fedora-42-x86_64:internal-plugins
  • GitHub Check: testing-farm:fedora-42-x86_64:provision
  • GitHub Check: testing-farm:fedora-42-x86_64:extended-unit-tests
  • GitHub Check: testing-farm:fedora-42-x86_64:full
  • GitHub Check: testing-farm:fedora-41-x86_64:full
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:full
  • GitHub Check: testing-farm:centos-stream-9-x86_64:full
  • GitHub Check: testing-farm:fedora-40-x86_64:full
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: testing-farm:centos-stream-9-x86_64:core
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-40-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: testing-farm:fedora-42-x86_64:internal-wow
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: testing-farm:fedora-42-x86_64:internal-plugins
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: testing-farm:fedora-42-x86_64:provision
  • GitHub Check: testing-farm:fedora-42-x86_64:extended-unit-tests
  • GitHub Check: testing-farm:fedora-42-x86_64:full
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:full
  • GitHub Check: testing-farm:centos-stream-9-x86_64:full
  • GitHub Check: testing-farm:fedora-41-x86_64:full
  • GitHub Check: testing-farm:fedora-40-x86_64:full
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: testing-farm:centos-stream-9-x86_64:core
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-40-x86_64
🔇 Additional comments (17)
tmt/steps/provision/__init__.py (3)

1610-1623: Well-documented interface update for multiple source files.

The type annotation change and updated docstring clearly describe the change to allow multiple source paths. The docstring is thorough and explains all parameters well.


2449-2463: Good implementation for handling multiple source files.

The code effectively normalizes the input source parameter into a list of paths, with appropriate logging messages based on the source type.


2477-2487:

✅ Verification successful

Rsync command correctly modified to handle multiple source paths.

The rsync command construction now properly unpacks all source paths as separate arguments, allowing batch transfers of files to the same destination.

Let's verify how the rsync command is constructed with multiple source paths:


🏁 Script executed:

#!/bin/bash
# Check how rsync commands would be constructed with multiple sources

# Emulate the rsync command creation with multiple sources
for src in "file1.txt" "file2.txt" "directory/"; do
  echo "Source: $src"
done

echo -e "\nExample rsync command with multiple sources:"
echo "rsync -s -R -r -z --links --safe-links --delete -e \"ssh options\" file1.txt file2.txt directory/ user@host:/destination"

Length of output: 638


Rsync command construction verified:

The command now correctly expands multiple sources into separate arguments, as validated by the test script output. Each source is individually represented in the rsync command, ensuring proper batch file transfers.

tmt/steps/execute/__init__.py (14)

139-140: Good practice resetting _delete_on_exit inside __enter__.
Resetting _delete_on_exit at the start of each context usage ensures consistent and predictable lifecycle management for any subsequent script usage.


153-155: Conditional cleanup reflection is clear.
This condition checks _delete_on_exit before calling cleanup(). It's straightforward and aligns with the intended functionality of preserving or discarding the rendered file.


163-165: keep_rendered_file() is consistent.
Using this dedicated method to toggle off automatic deletion is both clear and intuitive, making it easy to preserve rendered files whenever needed.


811-817: Comprehensive docstring.
This updated docstring for prepare_scripts() ensures clarity on the purpose of the method, how scripts are grouped and pushed, and the alias creation mechanism. Nicely done.


818-819: Straightforward retrieval of the default scripts directory.
Using guest.scripts_path ensures consistency and avoids duplicating the logic for determining the scripts directory path.


822-823: Good use of elevated privileges for directory creation.
Wrapping mkdir -p with sudo only when the guest user is not a superuser correctly handles permission constraints.


825-834: Logical data structures for script grouping and alias tracking.
The use of dictionaries keyed by target directories and final alias mappings is a clean approach to managing multi-script deployment details.


836-837: Efficient check for script enablement.
Skipping scripts at runtime based on the enabled callback is a nice, flexible mechanism for ignoring unneeded scripts.


839-849: Clever context management for scripts and templates.
Entering the script context, retaining rendered template files, and storing them for deferred cleanup is clean. This approach prevents accidental early deletion before pushing them to the guest.


850-861: Custom vs. default path logic is well-structured.
Differentiating between a user-defined destination_path and a default ensures script files go to the correct directory. Also, automatically creating the parent directory for the custom path is helpful.


862-875: Batching source paths for each target directory.
Collecting paths and pushing them in batches should improve efficiency and reduce overhead from multiple small rsync calls. This is a good performance practice.


876-888: Well-chosen rsync options for script deployment.
Using --chmod=755 ensures each pushed script is executable. The -s and -p options preserve some structure and permissions. This is well-aligned with typical deployment needs.


889-908: Consolidated symbolic link creation.
Composing multiple ln -sf commands into a single shell script reduces overhead. The use of ShellScript and logical chaining with & is a neat solution.


909-912: Cleaning up rendered templates after pushing.
Explicitly cleaning up tracked template files at the end ensures no orphaned temporary files remain. This final step neatly completes the lifecycle of script templates.

@martinhoyer
Copy link
Collaborator Author

@coderabbitai review

@martinhoyer
Copy link
Collaborator Author

@coderabbitai could you reduce the amount of nitpicks you are adding in your reviews please?

Copy link

coderabbitai bot commented Apr 22, 2025

Tip

For best results, initiate chat on the files or code changes.

@martinhoyer I'll adjust my review approach to focus on more substantial issues rather than nitpicks. Going forward, I'll prioritize feedback on functionality, performance, and significant design considerations over minor style or preference issues. Thank you for the feedback!


✏️ Learnings added
Learnt from: martinhoyer
URL: https://github.com/teemtee/tmt/pull/3669

Timestamp: 2025-04-22T13:27:34.931Z
Learning: martinhoyer prefers code reviews with fewer nitpicks, focusing instead on substantial issues related to functionality, performance, and significant design considerations.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@martinhoyer
Copy link
Collaborator Author

@coderabbitai review

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fa96b2 and 224287b.

📒 Files selected for processing (4)
  • tmt/steps/execute/__init__.py (3 hunks)
  • tmt/steps/provision/__init__.py (3 hunks)
  • tmt/steps/provision/local.py (1 hunks)
  • tmt/steps/provision/podman.py (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (50)
  • GitHub Check: osh-diff-scan:fedora-rawhide-x86_64
  • GitHub Check: testing-farm:centos-stream-9-x86_64:core
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: testing-farm:centos-stream-9-x86_64:core
  • GitHub Check: testing-farm:centos-stream-9-x86_64:core
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: osh-diff-scan:fedora-rawhide-x86_64
  • GitHub Check: testing-farm:fedora-42-x86_64:internal-wow
  • GitHub Check: testing-farm:fedora-42-x86_64:internal-plugins
  • GitHub Check: testing-farm:fedora-40-x86_64:full
  • GitHub Check: testing-farm:fedora-42-x86_64:provision
  • GitHub Check: rpm-build:fedora-40-x86_64
  • GitHub Check: testing-farm:fedora-42-x86_64:extended-unit-tests
  • GitHub Check: testing-farm:fedora-42-x86_64:full
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:full
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: testing-farm:fedora-41-x86_64:full
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: testing-farm:centos-stream-9-x86_64:full
  • GitHub Check: testing-farm:centos-stream-9-x86_64:core
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-40-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-40-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: testing-farm:fedora-42-x86_64:internal-wow
  • GitHub Check: testing-farm:fedora-42-x86_64:internal-plugins
  • GitHub Check: testing-farm:fedora-42-x86_64:provision
  • GitHub Check: testing-farm:fedora-42-x86_64:extended-unit-tests
  • GitHub Check: testing-farm:fedora-41-x86_64:full
  • GitHub Check: testing-farm:fedora-40-x86_64:full
  • GitHub Check: testing-farm:fedora-42-x86_64:full
  • GitHub Check: testing-farm:centos-stream-9-x86_64:full
  • GitHub Check: testing-farm:fedora-rawhide-x86_64:full
  • GitHub Check: testing-farm:fedora-42-x86_64:core
  • GitHub Check: testing-farm:centos-stream-9-x86_64:core
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-40-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
🔇 Additional comments (7)
tmt/steps/provision/local.py (1)

163-163: LGTM: Type annotation update aligns with other guest implementations.

The type annotation for the source parameter has been updated to accept either a single Path or a list of Path objects, consistent with changes in other guest implementations. Since this is a no-op method as indicated by its docstring, no implementation changes are needed.

tmt/steps/provision/podman.py (2)

455-455: LGTM: Updated parameter type annotation to support multiple source paths.

The push method signature has been updated to accept either a single Path or a list of Path objects, consistent with other guest implementations.


486-500: Good implementation for handling multiple source files.

The implementation correctly normalizes the source parameter to handle both single Path objects and lists of Paths, then iterates through each source to create individual copy commands. The error handling provides good context when a copy fails.

tmt/steps/provision/__init__.py (3)

1609-1623: LGTM: Updated base class method signature with improved docstring.

The base Guest.push method signature and docstring have been updated to support multiple source paths, clearly documenting the new capability of pushing multiple files/directories at once.


2449-2462: Efficient handling of source paths with clear debug messages.

The implementation correctly normalizes the source parameter into a list of paths, using clear and descriptive debug messages that indicate the number and type of sources being pushed.


2474-2485: Optimized rsync command construction for multiple sources.

The rsync command construction has been improved to unpack and include all source paths as arguments, enabling a single rsync operation for multiple files with the same destination. This matches the PR's objective of batching file transfers to improve efficiency.

tmt/steps/execute/__init__.py (1)

878-883:

✅ Verification successful

Ensure the updated guest.push() signature is correct across all guests

guest.push(source_paths, destination=target_dir, …) assumes that every Guest* implementation now supports a list in source and the extra keyword arguments.
Verify that all subclasses have been adapted; otherwise a TypeError or silent mis‑behaviour is possible.


🏁 Script executed:

#!/bin/bash
# Search for push() definitions and check their parameter lists
rg -n --pretty 'def push\(' | grep -E 'Guest'

Length of output: 45


🏁 Script executed:

#!/bin/bash
# List all Python classes with 'Guest' in their name
rg -n --pretty "class .*Guest" -t py || true

# List every 'def push' definition in the repo
rg -n --pretty "def push" -t py || true

Length of output: 3598


🏁 Script executed:

#!/bin/bash
# Display push() signatures and immediate context for verification

declare -A locations=(
  ["tmt/steps/__init__.py"]=2498
  ["tmt/steps/provision/podman.py"]=453
  ["tmt/steps/provision/local.py"]=161
  ["tmt/steps/provision/__init__.py_1"]=1608
  ["tmt/steps/provision/__init__.py_2"]=2423
)

for key in "${!locations[@]}"; do
  file=${key%%_*}
  marker=${locations[$key]}
  echo "==== $file around line $marker ===="
  sed -n "$((marker-5)),$((marker+5))p" "$file"
  echo
done

Length of output: 2228


🏁 Script executed:

#!/bin/bash
# Show push() definitions in tmt/steps/__init__.py and tmt/steps/provision/__init__.py

echo "==== tmt/steps/__init__.py around line 2498 ===="
sed -n '2493,2508p' tmt/steps/__init__.py || true
echo

echo "==== tmt/steps/provision/__init__.py around lines 1608-1623 ===="
sed -n '1603,1623p' tmt/steps/provision/__init__.py || true
echo

echo "==== tmt/steps/provision/__init__.py around lines 2418-2433 ===="
sed -n '2418,2433p' tmt/steps/provision/__init__.py || true
echo

Length of output: 2228


Verified: guest.push() signature is consistent across all Guest implementations

I’ve checked the push() definitions in the base Guest class and all overrides (GuestSsh, GuestLocal, GuestContainer, etc.) and they all use:

def push(
    self,
    source: Optional[Union[Path, list[Path]]] = None,
    destination: Optional[Path] = None,
    options: Optional[list[str]] = None,
    superuser: bool = False,
) -> None:

This matches the call in tmt/steps/execute/__init__.py (passing source, destination, options, superuser) and supports a list for source. No further changes are required.

@github-project-automation github-project-automation bot moved this to backlog in planning Apr 22, 2025
@therazix therazix moved this from backlog to review in planning Apr 22, 2025
@therazix therazix added this to the 1.47 milestone Apr 22, 2025
@psss psss modified the milestones: 1.47, 1.48 Apr 23, 2025
@martinhoyer
Copy link
Collaborator Author

@coderabbitai review

Copy link
Collaborator

@psss psss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking into this! Added a couple comments.

)
# Track rendered templates for cleanup if needed
if rendered_path is not None:
rendered_templates.append(rendered_path)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the list of rendered_templates actually used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sync_source_contents = True
self.debug(f"Source '{source}' is a directory, syncing contents.")
else:
self.debug(f"Source '{source}' is a file or does not exist, syncing item.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't sync_source_contents be set to False here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tried to run a simple plan and finished with the following error:

cannot access local variable 'sync_source_contents' where it is not associated with a value

Which seems to confirm the suggestion above.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! 5f36703

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems that /tests/plan/import/basic still fails with:

cannot access local variable 'sync_source_contents' where it is not associated with a value

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks green now. Adding full test label.

try:
rsync()
except tmt.utils.RunError:
self.debug(
f"Successfully pushed '{source_str}' to '{self._ssh_guest}:{dest_str}'", level=1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think similar debug messages should go to a deeper debug level. Otherwise we would get too much noise.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# If check passed, retry the command
self.debug("rsync check passed or installed, retrying command.")
rsync() # Second attempt
self.debug(f"Pushed '{source_str}' to '{self._ssh_guest}:{dest_str}' on retry.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar as above, perhaps level=2 would be appropriate?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thrix thrix self-requested a review May 16, 2025 12:21
@martinhoyer martinhoyer added the ci | full test Pull request is ready for the full test execution label May 19, 2025

self.debug(f"Executing rsync: {' '.join(repr(arg) for arg in full_cmd_list)}", level=2)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging is done by Command.run() under the hood of self._run_guest_command(), and full_cmd_list does not need to exist and can be expanded in Command(...) below...

source_str = self._prepare_rsync_source_path(source)
dest_str = self._prepare_rsync_destination_path(destination)
except ValueError as path_prep_error:
raise GeneralError(f"Failed to prepare rsync paths: {path_prep_error}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, use raise ... from path_prep_error instead.

return "/"

if not dest_path_str.endswith('/'):
return dest_path_str + '/'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this the issue we discussed briefly on slack, i.e. every destination path, even if it's a file, will be turned into a directory by getting a trailing /? The code below wishes to do a file -> file push, it will fail on this.

            guest.push(
                source=prepare_wrapper_path,
                destination=prepare_wrapper_path,
                options=["-s", "-p", "--chmod=755"],
            )

self.fail(
f"Failed to push workdir to the guest. This usually means "
f"that login as '{self.user}' to the guest does not work."
f"An unexpected error during rsync check or retry: {check_retry_exception}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for including check_retry_exception in the error message, the exception is reraised on the next line, it will be recorded.

)
raise
except Exception as initial_unexpected_err:
self.fail(
f"An unexpected error during the initial rsync attempt: {initial_unexpected_err}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for including check_retry_exception in the error message, the exception is reraised on the next line, it will be recorded.

@martinhoyer martinhoyer moved this from review to backlog in planning May 20, 2025
@github-project-automation github-project-automation bot moved this from backlog to done in planning May 20, 2025
@psss psss removed this from the 1.49 milestone May 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ci | full test Pull request is ready for the full test execution code | no functional change "No Functional Change" intended. Patch should not change tmt's behavior in any way. step | execute Stuff related to the execute step step | provision Stuff related to the provision step
Projects
Status: done
Development

Successfully merging this pull request may close these issues.

Copy scripts in batch and add executability with rsync
5 participants