# Copyright (c) 2025 Kim Jarvis TPF Software Services S.A. kim.jarvis@tpfsystems.com # This software is licensed under the MIT License. See the LICENSE file for details.#importasyncsshfromreemote.commandimportCommandfromtypingimportUnion,List,Optional
[docs]classUpload:""" A class to encapsulate the functionality of uploading builtin via SCP. Attributes: srcpaths (Union[str, List[str]]): The local source builtin(s) or directory to upload. dstpath (str): The remote destination path. preserve (bool): Whether to preserve builtin attributes. recurse (bool): Whether to recursively copy directories. block_size (int): The block size for builtin transfers. port (int): SSH port to use for connections. **Examples:** .. code:: python yield Upload( srcpaths='/home/kim/inventory_alpine.py', dstpath='/home/user/', recurse=True ) Usage: This class is designed to be used in a generator-based workflow where commands are yielded for execution. Notes: Supports wildcard patterns and recursive directory copying. """def__init__(self,srcpaths:Union[str,List[str]],dstpath:str,preserve:bool=False,recurse:bool=False,block_size:int=16384,port:int=22):self.srcpaths=srcpathsself.dstpath=dstpathself.preserve=preserveself.recurse=recurseself.block_size=block_sizeself.port=portdef__repr__(self):returnf"Upload(srcpaths={self.srcpaths!r}, dstpath={self.dstpath!r}, preserve={self.preserve}, recurse={self.recurse})"@staticmethodasyncdef_upload_callback(host_info,global_info,command,cp,caller):"""Static callback method for builtin upload"""# Validate host_info (matching Write_file error handling)required_keys=['host','username','password']forkeyinrequired_keys:ifkeynotinhost_infoorhost_info[key]isNone:raiseValueError(f"Missing or invalid value for '{key}' in host_info.")# Validate caller attributes (matching Write_file error handling)ifcaller.srcpathsisNone:raiseValueError("The 'srcpaths' attribute of the caller cannot be None.")ifcaller.dstpathisNone:raiseValueError("The 'dstpath' attribute of the caller cannot be None.")try:# Create proper connection parametersconnect_kwargs={'host':host_info['host'],'username':host_info.get('username'),'password':host_info.get('password'),'client_keys':host_info.get('client_keys'),'known_hosts':host_info.get('known_hosts')}# Remove None valuesconnect_kwargs={k:vfork,vinconnect_kwargs.items()ifvisnotNone}# Set port if specified and different from defaultifcaller.port!=22:connect_kwargs['port']=caller.port# Connect to the SSH serverasyncwithasyncssh.connect(**connect_kwargs)asconn:# Handle destination path - remove any host prefix since we're already connectedif':'incaller.dstpath:# Extract path after host: partdstpath=caller.dstpath.split(':',1)[1]else:dstpath=caller.dstpath# Perform the SCP uploadawaitasyncssh.scp(caller.srcpaths,(conn,dstpath),# Use connection object instead of host stringpreserve=caller.preserve,recurse=caller.recurse,block_size=caller.block_size)except(OSError,asyncssh.Error)asexc:raisedefexecute(self):r=yieldCommand(f"{self}",local=True,callback=self._upload_callback,caller=self)r.executed=Truer.changed=Truereturnr