@@ -133,8 +133,11 @@ class ScriptTemplate(Script):
133133 context : dict [str , str ]
134134
135135 _rendered_script_path : Optional [Path ] = None
136+ _delete_on_exit : bool = True # Flag to control cleanup
136137
137138 def __enter__ (self ) -> Path :
139+ # Ensure flag is reset for each use
140+ self ._delete_on_exit = True
138141 with NamedTemporaryFile (mode = 'w' , delete = False ) as rendered_script :
139142 rendered_script .write (
140143 render_template_file (
@@ -147,8 +150,19 @@ def __enter__(self) -> Path:
147150 return self ._rendered_script_path
148151
149152 def __exit__ (self , * args : object ) -> None :
150- assert self ._rendered_script_path
151- os .unlink (self ._rendered_script_path )
153+ # Only cleanup if the flag is set
154+ if self ._delete_on_exit :
155+ self .cleanup ()
156+
157+ def cleanup (self ) -> None :
158+ """Explicitly cleans up the rendered script file."""
159+ if self ._rendered_script_path and self ._rendered_script_path .exists ():
160+ self ._rendered_script_path .unlink ()
161+ self ._rendered_script_path = None
162+
163+ def keep_rendered_file (self ) -> None :
164+ """Prevents automatic deletion on __exit__."""
165+ self ._delete_on_exit = False
152166
153167
154168def effective_scripts_dest_dir (default : Path = DEFAULT_SCRIPTS_DEST_DIR ) -> Path :
@@ -794,28 +808,107 @@ def prepare_tests(self, guest: Guest, logger: tmt.log.Logger) -> list[TestInvoca
794808
795809 def prepare_scripts (self , guest : "tmt.steps.provision.Guest" ) -> None :
796810 """
797- Prepare additional scripts for testing
798- """
799-
800- # Make sure scripts directory exists
801- command = Command ("mkdir" , "-p" , f"{ guest .scripts_path } " )
811+ Prepare additional scripts for testing by copying them to the guest.
802812
813+ Scripts destined for the default guest scripts path are pushed in
814+ a single batch operation. Scripts with custom destination paths are
815+ handled individually. Aliases are created as symbolic links after
816+ the scripts are pushed.
817+ """
818+ # Ensure the main default scripts directory exists on the guest
819+ scripts_dest_dir = guest .scripts_path
820+ mkdir_command = Command ("mkdir" , "-p" , f"{ scripts_dest_dir } " )
803821 if not guest .facts .is_superuser :
804- command = Command ("sudo" ) + command
805-
806- guest .execute (command )
822+ mkdir_command = Command ("sudo" ) + mkdir_command
823+ guest .execute (mkdir_command )
824+
825+ # Group scripts by their target directory on the guest
826+ # Key: Target directory path on guest
827+ # Value: List of source paths (local paths) to copy into that directory
828+ scripts_by_target_dir : dict [Path , list [Path ]] = {}
829+ # Store aliases: key is the target path on guest, value is list of alias names
830+ # Aliases are created in the default scripts_dest_dir
831+ aliases_to_create : dict [Path , list [str ]] = {}
832+ # Keep track of ScriptTemplate instances for later cleanup
833+ templates_to_cleanup : list [ScriptTemplate ] = []
807834
808- # Install all scripts on guest
809835 for script in self .scripts :
836+ if not script .enabled (guest ):
837+ continue
838+
810839 with script as source :
811- for filename in [script .source_filename , * script .aliases ]:
812- if script .enabled (guest ):
813- guest .push (
814- source = source ,
815- destination = script .destination_path or guest .scripts_path / filename ,
816- options = ["-p" , "--chmod=755" ],
817- superuser = guest .facts .is_superuser is not True ,
818- )
840+ # If it's a template, prevent immediate cleanup and track it
841+ if isinstance (script , ScriptTemplate ):
842+ script .keep_rendered_file ()
843+ templates_to_cleanup .append (script )
844+ # Use the actual rendered path as the source
845+ source_path = script ._rendered_script_path
846+ assert source_path is not None # Should be set by __enter__
847+ else :
848+ source_path = source # Use the path from __enter__ directly
849+
850+ destination_path = script .destination_path
851+ target_dir : Path
852+ final_target_path_on_guest : Path
853+
854+ if destination_path is None :
855+ # Default destination directory
856+ target_dir = scripts_dest_dir
857+ final_target_path_on_guest = target_dir / script .source_filename
858+ # Store aliases associated with the final target path in the default dir
859+ if script .aliases :
860+ aliases_to_create [final_target_path_on_guest ] = script .aliases
861+ else :
862+ # Custom destination path - treat it as the final file path
863+ target_dir = destination_path .parent
864+ final_target_path_on_guest = destination_path
865+ # Ensure the custom parent directory exists
866+ mkdir_parent_cmd = Command ("mkdir" , "-p" , f"{ target_dir } " )
867+ if not guest .facts .is_superuser :
868+ mkdir_parent_cmd = Command ("sudo" ) + mkdir_parent_cmd
869+ guest .execute (mkdir_parent_cmd )
870+
871+ # Add the source path to the list for its target directory
872+ if target_dir not in scripts_by_target_dir :
873+ scripts_by_target_dir [target_dir ] = []
874+ scripts_by_target_dir [target_dir ].append (source_path )
875+
876+ # Push script batches grouped by target directory
877+ for target_dir , source_paths in scripts_by_target_dir .items ():
878+ if not source_paths :
879+ continue
880+
881+ self .debug (f"Pushing script batch ({ len (source_paths )} files) to { target_dir } " )
882+ guest .push (
883+ source = source_paths ,
884+ destination = target_dir ,
885+ options = ["-s" , "-p" , "--chmod=755" ],
886+ superuser = guest .facts .is_superuser is not True ,
887+ )
888+
889+ # Create aliases on the guest (only within the default scripts_dest_dir)
890+ full_alias_command = ShellScript ("" ) # Start with an empty script
891+ alias_count = 0
892+ for target_path , aliases in aliases_to_create .items ():
893+ for alias in aliases :
894+ link_path = scripts_dest_dir / alias
895+ # Use absolute path for symlink target for simplicity/robustness
896+ single_alias_cmd = ShellScript (
897+ f"ln -sf { target_path .as_posix ()} { link_path .as_posix ()} "
898+ )
899+ if alias_count == 0 :
900+ full_alias_command = single_alias_cmd
901+ else :
902+ full_alias_command &= single_alias_cmd # Use the '&' operator (__and__)
903+ alias_count += 1
904+
905+ if alias_count > 0 :
906+ self .debug ("Creating script aliases on guest." )
907+ guest .execute (full_alias_command , friendly_command = "Create script aliases" )
908+
909+ # Cleanup rendered templates now that they've been pushed
910+ for template_script in templates_to_cleanup :
911+ template_script .cleanup ()
819912
820913 def _tmt_report_results_filepath (self , invocation : TestInvocation ) -> Path :
821914 """
0 commit comments