def normalizeStaticIp(self, ipaddress, netmask="255.255.255.0", gateway=None, nameservers=None): """Normalize static IP options for network configuration. As implemented only supports IPv4. ipaddress IP address. netmask netmask. Defaults to 255.255.255.0. gateway gateway. If None then default to ip.1. nameservers one nameserver or a list of nameservers. If None then default to gateway. If empty list then remove option. return a NetworkConfigurationStaticParameters instance.""" # also see http://docs.redhat.com/docs/en-US/Red_Hat_Enterprise_Linux/6/html/Installation_Guide/s1-kickstart2-options.html # sanity check ipaddress = IPAddress.asString(ipaddress) if not re.match(r"^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$", ipaddress): raise Exception( "won't accept apparently impossible IP address {0}".format( ipaddress)) netmask = IPAddress.asString(netmask) if gateway is None: # default to ip.1 gateway = IPAddress.bitOr(IPAddress.bitAnd(ipaddress, netmask), "0.0.0.1") gateway = IPAddress.asString(gateway) if nameservers is None: # default to gateway nameservers = [gateway] elif not isinstance(nameservers, list): # process one as a list of one nameservers = [nameservers] else: # given a list already nameservers = nameservers nameserversStrings = [ IPAddress.asString(oneNameserver) for oneNameserver in nameservers ] normalized = NetworkConfigurationStaticParameters( ipaddress=ipaddress, netmask=netmask, gateway=gateway, nameservers=nameserversStrings) return normalized
def _changeIPAddress(cls, portsFileContent, oldIpAddress, newIpAddress): """Change all occurences of a specific IP address.""" # method made to be portsFileContentModifyingMethod parameter for method modify() oldIpAddress = IPAddress.asString(oldIpAddress) newIpAddress = IPAddress.asString(newIpAddress) # feel the misery of not yet having better XPath from Python 2.7 and ElementTree 1.3 sshElements = portsFileContent.findall("ssh") for sshElement in sshElements: if oldIpAddress == sshElement.findtext("ipaddress"): # found oldIpAddress ipaddressElement = sshElement.find("ipaddress") ipaddressElement.text = newIpAddress
def normalizeStaticIp(self, ipaddress, netmask="255.255.255.0", gateway=None, nameservers=None): """Normalize static IP options for network configuration. As implemented only supports IPv4. ipaddress IP address. netmask netmask. Defaults to 255.255.255.0. gateway gateway. If None then default to ip.1. nameservers one nameserver or a list of nameservers. If None then default to gateway. If empty list then remove option. return a NetworkConfigurationStaticParameters instance.""" # also see http://docs.redhat.com/docs/en-US/Red_Hat_Enterprise_Linux/6/html/Installation_Guide/s1-kickstart2-options.html # sanity check ipaddress = IPAddress.asString(ipaddress) if not re.match(r"^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$", ipaddress): raise Exception("won't accept apparently impossible IP address {0}".format(ipaddress)) netmask = IPAddress.asString(netmask) if gateway is None: # default to ip.1 gateway = IPAddress.bitOr(IPAddress.bitAnd(ipaddress, netmask), "0.0.0.1") gateway = IPAddress.asString(gateway) if nameservers is None: # default to gateway nameservers = [gateway] elif not isinstance(nameservers, list): # process one as a list of one nameservers = [nameservers] else: # given a list already nameservers = nameservers nameserversStrings = [IPAddress.asString(oneNameserver) for oneNameserver in nameservers] normalized = NetworkConfigurationStaticParameters(ipaddress=ipaddress, netmask=netmask, gateway=gateway, nameservers=nameserversStrings) return normalized
def removeKnownHostKey(cls, ipaddress): """Remove line from ~/.ssh/known_hosts file.""" knownHostsFile = SshCommand._knownHostFilePath ipaddress = IPAddress.asString(ipaddress) if not os.path.exists(knownHostsFile): # maybe file hasn't been created yet, nothing to do return with open (knownHostsFile, "r") as inputFile: knownHostLines = inputFile.readlines() ipaddressRegex = re.compile(r"^[ \t]*" + re.escape(ipaddress) + r"\s") anyMatch = False newKnownHostLines = [] for knownHostLine in knownHostLines: if ipaddressRegex.search(knownHostLine): # a match, don't copy it over anyMatch = True else: # all others copy over newKnownHostLines.append(knownHostLine) if anyMatch: with open (knownHostsFile, "w") as outputFile: outputFile.writelines(newKnownHostLines) if not anyMatch: # possibly not found as plain text because hashed sshKeygen = CommandCapture(["ssh-keygen", "-f", knownHostsFile, "-R", ipaddress], copyToStdio=False, exceptionIfNotZero=False, exceptionIfAnyStderr=False)
def sleepUntilIsAvailable(cls, sshParameters, checkIntervalSeconds=5.0, ticker=False, probingCommand="hostname"): """If available return, else loop sleeping for checkIntervalSeconds.""" printed = False ticked = False # check the essential condition, initially and then repeatedly while not SshCommand.isAvailable(sshParameters, probingCommand=probingCommand): if not printed: # first time only printing print "waiting for ssh to be available to connect to " + IPAddress.asString(sshParameters.ipaddress) sys.stdout.flush() printed = True if ticker: if not ticked: # first time only printing sys.stdout.write("[") sys.stdout.write(".") sys.stdout.flush() ticked = True time.sleep(checkIntervalSeconds) if ticked: # final printing sys.stdout.write("]\n") sys.stdout.flush()
def sleepUntilHasAcceptedKnownHostKey(cls, sshParameters, checkIntervalSeconds=3.0, ticker=False, extraSleepSeconds=5.0): """If available return, else loop sleeping for checkIntervalSeconds. sshParameters an SshParameters instance to use in the attempts.""" printed = False ticked = False # check the essential condition, initially and then repeatedly while not SshCommand.hasAcceptedKnownHostKey(sshParameters=sshParameters): if not printed: # first time only printing print "waiting for ssh to be available to get host key from " + IPAddress.asString(sshParameters.ipaddress) sys.stdout.flush() printed = True if ticker: if not ticked: # first time only printing sys.stdout.write("[") sys.stdout.write(".") sys.stdout.flush() ticked = True time.sleep(checkIntervalSeconds) if ticked: # final printing sys.stdout.write("]\n") sys.stdout.flush() if extraSleepSeconds: time.sleep(extraSleepSeconds)
def sleepUntilHasAcceptedKnownHostKey(cls, sshParameters, checkIntervalSeconds=3.0, ticker=False, extraSleepSeconds=5.0): """If available return, else loop sleeping for checkIntervalSeconds. sshParameters an SshParameters instance to use in the attempts.""" printed = False ticked = False # check the essential condition, initially and then repeatedly while not SshCommand.hasAcceptedKnownHostKey( sshParameters=sshParameters): if not printed: # first time only printing print "waiting for ssh to be available to get host key from " + IPAddress.asString( sshParameters.ipaddress) sys.stdout.flush() printed = True if ticker: if not ticked: # first time only printing sys.stdout.write("[") sys.stdout.write(".") sys.stdout.flush() ticked = True time.sleep(checkIntervalSeconds) if ticked: # final printing sys.stdout.write("]\n") sys.stdout.flush() if extraSleepSeconds: time.sleep(extraSleepSeconds)
def sleepUntilIsAvailable(cls, sshParameters, checkIntervalSeconds=5.0, ticker=False, probingCommand="hostname"): """If available return, else loop sleeping for checkIntervalSeconds.""" printed = False ticked = False # check the essential condition, initially and then repeatedly while not SshCommand.isAvailable(sshParameters, probingCommand=probingCommand): if not printed: # first time only printing print "waiting for ssh to be available to connect to " + IPAddress.asString( sshParameters.ipaddress) sys.stdout.flush() printed = True if ticker: if not ticked: # first time only printing sys.stdout.write("[") sys.stdout.write(".") sys.stdout.flush() ticked = True time.sleep(checkIntervalSeconds) if ticked: # final printing sys.stdout.write("]\n") sys.stdout.flush()
def removeKnownHostKey(cls, ipaddress): """Remove line from ~/.ssh/known_hosts file.""" knownHostsFile = SshCommand._knownHostFilePath ipaddress = IPAddress.asString(ipaddress) if not os.path.exists(knownHostsFile): # maybe file hasn't been created yet, nothing to do return with open(knownHostsFile, "r") as inputFile: knownHostLines = inputFile.readlines() ipaddressRegex = re.compile(r"^[ \t]*" + re.escape(ipaddress) + r"\s") anyMatch = False newKnownHostLines = [] for knownHostLine in knownHostLines: if ipaddressRegex.search(knownHostLine): # a match, don't copy it over anyMatch = True else: # all others copy over newKnownHostLines.append(knownHostLine) if anyMatch: with open(knownHostsFile, "w") as outputFile: outputFile.writelines(newKnownHostLines) if not anyMatch: # possibly not found as plain text because hashed sshKeygen = CommandCapture( ["ssh-keygen", "-f", knownHostsFile, "-R", ipaddress], copyToStdio=False, exceptionIfNotZero=False, exceptionIfAnyStderr=False)
def sleepUntilIsGuiAvailable(cls, sshParameters, checkIntervalSeconds=3.0, ticker=False, extraSleepSeconds=5.0): """If GUI available return, else loop sleeping for checkIntervalSeconds. Should be user to be meaningful. As implemented first calls SshCommand.sleepUntilIsAvailable(sshParameters). sshParameters an SshParameters instance.""" cls.sleepUntilIsAvailable(sshParameters, checkIntervalSeconds=checkIntervalSeconds, ticker=ticker) printed = False ticked = False # check the essential condition, initially and then repeatedly while not cls.isGuiAvailable(sshParameters): if not printed: # first time only printing print "waiting for GUI to be available to connect to " + IPAddress.asString(sshParameters.ipaddress) sys.stdout.flush() printed = True if ticker: if not ticked: # first time only printing sys.stdout.write("[") sys.stdout.write(".") sys.stdout.flush() ticked = True time.sleep(checkIntervalSeconds) if ticked: # final printing sys.stdout.write("]\n") if extraSleepSeconds: time.sleep(extraSleepSeconds)
def commandToChangeStaticIPAddress(cls, oldIpAddress, newIpAddress, interface=None): """Build command to change static IP address. Must be root to succeed. As implemented works in Enterprise Linux versions 6.x. Example use: vm = VMwareMachine("~/vmware/examples/example68/example68.vmx") VMwareHypervisor.local.start(vm.vmxFilePath) vm.sleepUntilSshIsAvailable(ticker=True) vm.sshCommand([ElClone.commandToChangeStaticIPAddress("10.123.45.67", "10.123.45.68")]) vm.portsFile.changeIPAddress("10.123.45.67", "10.123.45.68") vm.sleepUntilSshIsAvailable(ticker=True) vm.acceptKnownHostKey() vm.sshCommand([ElClone.commandToChangeHostname("example67", "example68")]) interface a string, e.g. "eth0". Return command to change static IP address.""" oldIpAddress = IPAddress.asString(oldIpAddress) newIpAddress = IPAddress.asString(newIpAddress) if re.search(r"\s", oldIpAddress): raise Exception( "not accepting whitespace in IP address ({0})".format( oldIpAddress)) if re.search(r"\s", newIpAddress): raise Exception( "not accepting whitespace in IP address ({0})".format( newIpAddress)) # quite sensitive to quoting and not quoting command = r"sed -i -e 's/=\"\?" + re.escape( oldIpAddress) + r"\"\?/=\"" + re.escape(newIpAddress) + r"\"/'" if interface: command += r" '/etc/sysconfig/network-scripts/ifcfg-" + re.escape( interface) + r"'" else: # quite sensitive to quoting and not quoting command = r"for f in /etc/sysconfig/network-scripts/ifcfg-* ; do " + command + r" $f ; done" # oddly has been observed to require two times service network restart command += r" ; ( nohup sh -c 'service network restart ; service network restart' &> /dev/null & )" return command
def _removeSsh(cls, portsFileContent, ipaddress, user): """Remove .ports file entry for ssh access for a user.""" # method made to be portsFileContentModifyingMethod parameter for method modify() ipaddress = IPAddress.asString(ipaddress) # feel the misery of not yet having better XPath from Python 2.7 and ElementTree 1.3 ports = portsFileContent.getroot() sshElements = portsFileContent.findall("ssh") for sshElement in sshElements: if user == sshElement.findtext("user") and ipaddress == sshElement.findtext("ipaddress"): # found user at ipaddress ports.remove(sshElement)
def commandToChangeStaticIPAddress(cls, oldIpAddress, newIpAddress, interface=None): """Build command to change static IP address. Must be root to succeed. As implemented works in Enterprise Linux versions 6.x. Example use: vm = VMwareMachine("~/vmware/examples/example68/example68.vmx") VMwareHypervisor.local.start(vm.vmxFilePath) vm.sleepUntilSshIsAvailable(ticker=True) vm.sshCommand([ElClone.commandToChangeStaticIPAddress("10.123.45.67", "10.123.45.68")]) vm.portsFile.changeIPAddress("10.123.45.67", "10.123.45.68") vm.sleepUntilSshIsAvailable(ticker=True) vm.acceptKnownHostKey() vm.sshCommand([ElClone.commandToChangeHostname("example67", "example68")]) interface a string, e.g. "eth0". Return command to change static IP address.""" oldIpAddress = IPAddress.asString(oldIpAddress) newIpAddress = IPAddress.asString(newIpAddress) if re.search(r"\s", oldIpAddress): raise Exception("not accepting whitespace in IP address ({0})".format(oldIpAddress)) if re.search(r"\s", newIpAddress): raise Exception("not accepting whitespace in IP address ({0})".format(newIpAddress)) # quite sensitive to quoting and not quoting command = r"sed -i -e 's/=\"\?" + re.escape(oldIpAddress) + r"\"\?/=\"" + re.escape(newIpAddress) + r"\"/'" if interface: command += r" '/etc/sysconfig/network-scripts/ifcfg-" + re.escape(interface) + r"'" else: # quite sensitive to quoting and not quoting command = r"for f in /etc/sysconfig/network-scripts/ifcfg-* ; do " + command + r" $f ; done" # oddly has been observed to require two times service network restart command += r" ; ( nohup sh -c 'service network restart ; service network restart' &> /dev/null & )" return command
def commandToAppendAddressNameLineToEtcHosts(cls, ipaddress, name): """Build command to append an ipaddress hostname line to /etc/hosts. Only if no line yet. As implemented any fgrep match of name in /etc/hosts prevents addition. Must be root to succeed. Return command to append an ipaddress hostname line to /etc/hosts.""" name = re.escape(name) # precaution ipaddress = IPAddress.asString(ipaddress) command = "fgrep -q -e '" + name + "' /etc/hosts || " \ + "echo '" + ipaddress + " " + name + "' >> /etc/hosts" return command
def __init__(self, ipaddress, user, pwd): """Create new SshParameters instance. Example use:: exampleSshParameters = SshParameters("10.123.45.67", "joe", "redwood") ipaddress IP address or domain name. user a string. pwd a string or None.""" self.ipaddress = IPAddress.asString(ipaddress) self.user = user self.pwd = pwd
def _setSsh(cls, portsFileContent, ipaddress, user, pwd): """Set .ports file entry for ssh access for a user.""" # method made to be portsFileContentModifyingMethod parameter for method modify() ipaddress = IPAddress.asString(ipaddress) # feel the misery of not yet having better XPath from Python 2.7 and ElementTree 1.3 sshElements = portsFileContent.findall("ssh") for sshElement in sshElements: if user == sshElement.findtext("user") and ipaddress == sshElement.findtext("ipaddress"): # found user at ipaddress pwdElement = sshElement.find("pwd") if pwdElement is None: # odd case pwdElement = SubElement(sshElement, "pwd") pwdElement.text = pwd return # done # no entry yet for user at ipaddress sshElement = SubElement(portsFileContent.getroot(), "ssh") ipaddressElement = SubElement(sshElement, "ipaddress") ipaddressElement.text = ipaddress userElement = SubElement(sshElement, "user") userElement.text = user pwdElement = SubElement(sshElement, "pwd") pwdElement.text = pwd
def addNetworkConfigurationStatic(self, mac, ipaddress, netmask="255.255.255.0", gateway=None, nameservers=None, limitRoutingToLocalByNetmask=False): """Add an additional network device with static IP. As implemented only supports IPv4. mac the MAC, e.g. "01:23:45:67:89:ab" or "01-23-45-67-89-AB". ipaddress IP address. netmask netmask. Defaults to 255.255.255.0. gateway gateway. If None then default to ip.1. nameservers one nameserver or a list of nameservers. If None then default to gateway. If empty list then do not add any. return self, for daisychaining.""" # sanity check normalizedStaticIp = NetworkConfigurationStaticParameters.normalizeStaticIp(ipaddress, netmask, gateway, nameservers) # see http://technet.microsoft.com/en-us/library/ff716288.aspx mac = mac.replace(":","-").upper() ipaddressSlashRoutingPrefixLength = normalizedStaticIp.ipaddress + "/" + str(normalizedStaticIp.routingprefixlength) gatewaySlashRoutingPrefixLength = normalizedStaticIp.gateway + "/" + str(normalizedStaticIp.routingprefixlength) if not limitRoutingToLocalByNetmask: routePrefix = "0.0.0.0/0" else: routePrefix = IPAddress.asString(normalizedStaticIp.localprefix) + "/" + str(normalizedStaticIp.routingprefixlength) nameservers = normalizedStaticIp.nameservers additionalContent = r""" <component name="Microsoft-Windows-TCPIP" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Interfaces> <Interface wcm:action="add"> <Identifier>""" + mac + r"""</Identifier> <Ipv4Settings> <DhcpEnabled>false</DhcpEnabled> <RouterDiscoveryEnabled>false</RouterDiscoveryEnabled> </Ipv4Settings> <UnicastIpAddresses> <IpAddress wcm:action="add" wcm:keyValue="1">""" + ipaddressSlashRoutingPrefixLength + r"""</IpAddress> </UnicastIpAddresses> <Routes> <Route wcm:action="add"> <Identifier>1</Identifier> <NextHopAddress>""" + gatewaySlashRoutingPrefixLength + r"""</NextHopAddress> <Prefix>""" + routePrefix + r"""</Prefix> </Route> </Routes> </Interface> </Interfaces> </component>""" if nameservers: additionalContent += r""" <component name="Microsoft-Windows-DNS-Client" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Interfaces> <Interface wcm:action="add"> <Identifier>""" + mac + r"""</Identifier> <DNSServerSearchOrder> """ + "\n".join(map(lambda nameserver, i: r"""<IpAddress wcm:action="add" wcm:keyValue=""" r'"' + str(i+1) + r'"' r""">""" + nameserver + r"""</IpAddress>""", nameservers, range(0,len(nameservers)))) + r""" </DNSServerSearchOrder> <EnableAdapterDomainNameRegistration>false</EnableAdapterDomainNameRegistration> <DisableDynamicUpdate>true</DisableDynamicUpdate> </Interface> </Interfaces> <DNSDomain>example.com</DNSDomain> </component>""" self._appendToChildren("settings", "pass", "specialize", additionalContent, prepend=True) return self
def addNetworkConfigurationStatic(self, mac, ipaddress, netmask="255.255.255.0", gateway=None, nameservers=None, limitRoutingToLocalByNetmask=False): """Add an additional network device with static IP. As implemented only supports IPv4. mac the MAC, e.g. "01:23:45:67:89:ab" or "01-23-45-67-89-AB". ipaddress IP address. netmask netmask. Defaults to 255.255.255.0. gateway gateway. If None then default to ip.1. nameservers one nameserver or a list of nameservers. If None then default to gateway. If empty list then do not add any. return self, for daisychaining.""" # sanity check normalizedStaticIp = NetworkConfigurationStaticParameters.normalizeStaticIp( ipaddress, netmask, gateway, nameservers) # see http://technet.microsoft.com/en-us/library/ff716288.aspx mac = mac.replace(":", "-").upper() ipaddressSlashRoutingPrefixLength = normalizedStaticIp.ipaddress + "/" + str( normalizedStaticIp.routingprefixlength) gatewaySlashRoutingPrefixLength = normalizedStaticIp.gateway + "/" + str( normalizedStaticIp.routingprefixlength) if not limitRoutingToLocalByNetmask: routePrefix = "0.0.0.0/0" else: routePrefix = IPAddress.asString( normalizedStaticIp.localprefix) + "/" + str( normalizedStaticIp.routingprefixlength) nameservers = normalizedStaticIp.nameservers additionalContent = r""" <component name="Microsoft-Windows-TCPIP" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Interfaces> <Interface wcm:action="add"> <Identifier>""" + mac + r"""</Identifier> <Ipv4Settings> <DhcpEnabled>false</DhcpEnabled> <RouterDiscoveryEnabled>false</RouterDiscoveryEnabled> </Ipv4Settings> <UnicastIpAddresses> <IpAddress wcm:action="add" wcm:keyValue="1">""" + ipaddressSlashRoutingPrefixLength + r"""</IpAddress> </UnicastIpAddresses> <Routes> <Route wcm:action="add"> <Identifier>1</Identifier> <NextHopAddress>""" + gatewaySlashRoutingPrefixLength + r"""</NextHopAddress> <Prefix>""" + routePrefix + r"""</Prefix> </Route> </Routes> </Interface> </Interfaces> </component>""" if nameservers: additionalContent += r""" <component name="Microsoft-Windows-DNS-Client" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Interfaces> <Interface wcm:action="add"> <Identifier>""" + mac + r"""</Identifier> <DNSServerSearchOrder> """ + "\n".join( map( lambda nameserver, i: r"""<IpAddress wcm:action="add" wcm:keyValue=""" r'"' + str(i + 1) + r'"' r""">""" + nameserver + r"""</IpAddress>""", nameservers, range(0, len(nameservers)))) + r""" </DNSServerSearchOrder> <EnableAdapterDomainNameRegistration>false</EnableAdapterDomainNameRegistration> <DisableDynamicUpdate>true</DisableDynamicUpdate> </Interface> </Interfaces> <DNSDomain>example.com</DNSDomain> </component>""" self._appendToChildren("settings", "pass", "specialize", additionalContent, prepend=True) return self
def __init__(self, fromPath, toPath, fromSshParameters=None, toSshParameters=None, recurseDirectories=False, preserveTimes=True): """Create new ScpCommand instance. Will wait until completed. Either fromPath or toPath is expected to be local, i.e. without user and without IP address. Correspondingly either fromSshParameters or toSshParameters must NOT be assigned an SshParameters instance and remain default None. fromPath one path or a list of paths. Absolute paths strongly recommended. toPath one path. Absolute path strongly recommended. Must be directory if more than one fromPath. fromSshParameters an SshParameters instance. toSshParameters an SshParameters instance. recurseDirectories a hint for when fromSshParameters.""" if not _gotPty: # cannot use scp if no pty raise Exception("must have module pty available to use scp command" ", which is known to be available in Python 2.6 on Linux, but not on Windows") # if fromSshParameters and toSshParameters: raise Exception("cannot copy if both fromSshParameters and toSshParameters, only one or other") if not fromSshParameters and not toSshParameters: raise Exception("cannot copy if neither fromSshParameters nor toSshParameters, requires one or other") # if not isinstance(fromPath, (list, tuple)): # should be one string for one path to copy from fromPaths = [fromPath] else: # should be a list of strings for multiple paths to copy from fromPaths = fromPath if len(fromPaths) == 0: raise Exception("cannot copy zero files, requires at least one") if fromSshParameters: # get files from remote if len(fromPaths) > 1 or recurseDirectories: if not os.path.isdir(toPath): raise Exception("cannot copy multiple files into a file, must copy into a directory, not into %s" % toPath) self._fromSpecification = \ [fromSshParameters.user + "@" + IPAddress.asString(fromSshParameters.ipaddress) + ":" + " ".join(fromPaths)] self._toSpecification = toPath self._pwd = fromSshParameters.pwd else: # put files to remote anyFromDirectory = False for path in fromPaths: if os.path.isdir(path): anyFromDirectory = True break if anyFromDirectory: recurseDirectories = True # mandatory in this case self._fromSpecification = fromPaths self._toSpecification = \ toSshParameters.user + "@" + IPAddress.asString(toSshParameters.ipaddress) + ":" + toPath self._pwd = toSshParameters.pwd self._args = ["scp"] if preserveTimes: self._args.append("-p") if recurseDirectories: self._args.append("-r") self._args.extend(self._fromSpecification) # a list because possibly more than one self._args.append(self._toSpecification) # self._output = "" self._returncode = None # # fork and connect child to a pseudo-terminal self._pid, self._fd = pty.fork() if self._pid == 0: # in child process os.execvp("scp", self._args) else: # in parent process if self._pwd: # if given a password then apply promptedForPassword = False outputTillPrompt = "" # look for password prompt while not promptedForPassword: try: newOutput = os.read(self._fd, 1024) if not len(newOutput): # end has been reached # was raise Exception("unexpected end of output from scp") raise Exception("failing to connect for scp\n" + outputTillPrompt) # ssh has been observed returning "\r\n" for newline, but we want "\n" newOutput = SshCommand._crLfRegex.sub("\n", newOutput) outputTillPrompt += newOutput if SshCommand._acceptPromptRegex.search(outputTillPrompt): # e.g. "Are you sure you want to continue connecting (yes/no)? " raise Exception("cannot proceed unless having accepted host key\n" + outputTillPrompt + '\nE.g. invoke SshCommand.acceptKnownHostKey(SshParameters("{0}",user,pwd)).'.format(self._ipaddress)) if SshCommand._pwdPromptRegex.search(outputTillPrompt): # e.g. "10.123.45.67's password: "******"@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @" and closing raise Exception("failing to connect for scp\n" + outputTillPrompt) os.write(self._fd, self._pwd + "\n") # look for output endOfOutput = False outputSincePrompt = "" try: while not endOfOutput: try: newOutput = os.read(self._fd, 1024) if len(newOutput): outputSincePrompt += newOutput else: # end has been reached endOfOutput = True except EnvironmentError as e: # some ideas maybe at http://bugs.python.org/issue5380 if e.errno == 5: # errno.EIO: # seen when pty closes OSError: [Errno 5] Input/output error endOfOutput = True else: # we accept what we got so far, for now endOfOutput = True finally: # remove any leading space (maybe there after "password:"******"\n") self._output = re.sub(r"^\s*?\n(.*)$", r"\1", outputSincePrompt) # # get returncode try: ignorePidAgain, waitEncodedStatusIndication = os.waitpid(self._pid, 0) if os.WIFEXITED(waitEncodedStatusIndication): # normal exit(status) call self._returncode = os.WEXITSTATUS(waitEncodedStatusIndication) # raise an exception if there is a reason exceptionMessage = "" if self._returncode: exceptionMessage += "returncode: " + str(self._returncode) if exceptionMessage: commandDescription = "scp from:\n\t" + str(self._fromSpecification) commandDescription += "\nto:\n\t" + self._toSpecification commandDescription += "\nargs:\n\t" + str(self._args) exceptionMessage = commandDescription + "\n" + exceptionMessage exceptionMessage += "\noutput:\n" + self._output raise ScpCommandException(exceptionMessage) else: # e.g. os.WIFSIGNALED or os.WIFSTOPPED self._returncode = -1 raise ScpCommandException("scp did not exit normally") except OSError: # supposedly can occur self._returncode = -1 raise ScpCommandException("scp did not exit normally")
def __init__(self, fromPath, toPath, fromSshParameters=None, toSshParameters=None, recurseDirectories=False, preserveTimes=True): """Create new ScpCommand instance. Will wait until completed. Either fromPath or toPath is expected to be local, i.e. without user and without IP address. Correspondingly either fromSshParameters or toSshParameters must NOT be assigned an SshParameters instance and remain default None. fromPath one path or a list of paths. Absolute paths strongly recommended. toPath one path. Absolute path strongly recommended. Must be directory if more than one fromPath. fromSshParameters an SshParameters instance. toSshParameters an SshParameters instance. recurseDirectories a hint for when fromSshParameters.""" if not _gotPty: # cannot use scp if no pty raise Exception( "must have module pty available to use scp command" ", which is known to be available in Python 2.6 on Linux, but not on Windows" ) # if fromSshParameters and toSshParameters: raise Exception( "cannot copy if both fromSshParameters and toSshParameters, only one or other" ) if not fromSshParameters and not toSshParameters: raise Exception( "cannot copy if neither fromSshParameters nor toSshParameters, requires one or other" ) # if not isinstance( fromPath, (list, tuple)): # should be one string for one path to copy from fromPaths = [fromPath] else: # should be a list of strings for multiple paths to copy from fromPaths = fromPath if len(fromPaths) == 0: raise Exception("cannot copy zero files, requires at least one") if fromSshParameters: # get files from remote if len(fromPaths) > 1 or recurseDirectories: if not os.path.isdir(toPath): raise Exception( "cannot copy multiple files into a file, must copy into a directory, not into %s" % toPath) self._fromSpecification = \ [fromSshParameters.user + "@" + IPAddress.asString(fromSshParameters.ipaddress) + ":" + " ".join(fromPaths)] self._toSpecification = toPath self._pwd = fromSshParameters.pwd else: # put files to remote anyFromDirectory = False for path in fromPaths: if os.path.isdir(path): anyFromDirectory = True break if anyFromDirectory: recurseDirectories = True # mandatory in this case self._fromSpecification = fromPaths self._toSpecification = \ toSshParameters.user + "@" + IPAddress.asString(toSshParameters.ipaddress) + ":" + toPath self._pwd = toSshParameters.pwd self._args = ["scp"] if preserveTimes: self._args.append("-p") if recurseDirectories: self._args.append("-r") self._args.extend( self._fromSpecification) # a list because possibly more than one self._args.append(self._toSpecification) # self._output = "" self._returncode = None # # fork and connect child to a pseudo-terminal self._pid, self._fd = pty.fork() if self._pid == 0: # in child process os.execvp("scp", self._args) else: # in parent process if self._pwd: # if given a password then apply promptedForPassword = False outputTillPrompt = "" # look for password prompt while not promptedForPassword: try: newOutput = os.read(self._fd, 1024) if not len(newOutput): # end has been reached # was raise Exception("unexpected end of output from scp") raise Exception("failing to connect for scp\n" + outputTillPrompt) # ssh has been observed returning "\r\n" for newline, but we want "\n" newOutput = SshCommand._crLfRegex.sub("\n", newOutput) outputTillPrompt += newOutput if SshCommand._acceptPromptRegex.search( outputTillPrompt): # e.g. "Are you sure you want to continue connecting (yes/no)? " raise Exception( "cannot proceed unless having accepted host key\n" + outputTillPrompt + '\nE.g. invoke SshCommand.acceptKnownHostKey(SshParameters("{0}",user,pwd)).' .format(self._ipaddress)) if SshCommand._pwdPromptRegex.search(outputTillPrompt): # e.g. "10.123.45.67's password: "******"@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @" and closing raise Exception("failing to connect for scp\n" + outputTillPrompt) os.write(self._fd, self._pwd + "\n") # look for output endOfOutput = False outputSincePrompt = "" try: while not endOfOutput: try: newOutput = os.read(self._fd, 1024) if len(newOutput): outputSincePrompt += newOutput else: # end has been reached endOfOutput = True except EnvironmentError as e: # some ideas maybe at http://bugs.python.org/issue5380 if e.errno == 5: # errno.EIO: # seen when pty closes OSError: [Errno 5] Input/output error endOfOutput = True else: # we accept what we got so far, for now endOfOutput = True finally: # remove any leading space (maybe there after "password:"******"\n") self._output = re.sub(r"^\s*?\n(.*)$", r"\1", outputSincePrompt) # # get returncode try: ignorePidAgain, waitEncodedStatusIndication = os.waitpid( self._pid, 0) if os.WIFEXITED(waitEncodedStatusIndication): # normal exit(status) call self._returncode = os.WEXITSTATUS( waitEncodedStatusIndication) # raise an exception if there is a reason exceptionMessage = "" if self._returncode: exceptionMessage += "returncode: " + str( self._returncode) if exceptionMessage: commandDescription = "scp from:\n\t" + str( self._fromSpecification) commandDescription += "\nto:\n\t" + self._toSpecification commandDescription += "\nargs:\n\t" + str( self._args) exceptionMessage = commandDescription + "\n" + exceptionMessage exceptionMessage += "\noutput:\n" + self._output raise ScpCommandException(exceptionMessage) else: # e.g. os.WIFSIGNALED or os.WIFSTOPPED self._returncode = -1 raise ScpCommandException("scp did not exit normally") except OSError: # supposedly can occur self._returncode = -1 raise ScpCommandException("scp did not exit normally")