def get_ssh_client_command(self, ip_address=None, port_number=None): """ Generate an SSH_ client command line that connects to the container (assumed to be running). :param ip_address: This optional argument overrides the default IP address (which is otherwise automatically discovered). :param port_number: This optional argument overrides the default port number (which is otherwise automatically discovered). :returns: The SSH client command line as a list of strings containing the command and its arguments. """ command = ['ssh'] # Connect as the root user inside the container. command.extend(['-l', 'root']) # Connect using the generated SSH private key. command.extend(['-i', PRIVATE_SSH_KEY]) # Don't check or store the host key (it's pointless). command.extend(['-o', 'StrictHostKeyChecking=no']) command.extend(['-o', 'UserKnownHostsFile=/dev/null']) # Silence the message "Warning: Permanently added ... to the list of known hosts." command.append('-q') # Connect through a NAT port on the local system. if not (ip_address and port_number): ip_address, port_number = self.ssh_endpoint command.extend(['-p', str(port_number)]) # Make the SSH connection binary safe. command.extend(['-e', 'none']) # Finish the command by including the IP address. command.append(ip_address) self.logger.debug("Generated SSH command: %s", quote_command_line(command)) # Return the generated command. return command
def rsync(self, local_directory, remote_directory, cvs_exclude=True, delete=True): """ Copy a directory on the host to the container using rsync over SSH. Raises :py:exc:`ExternalCommandFailed` if the remote command ends with a nonzero exit code. :param local_directory: The pathname of the source directory on the host. :param remote_directory: The pathname of the target directory in the container. :param cvs_exclude: Exclude version control files (enabled by default). :param delete: Delete remote files that don't exist locally (enabled by default). """ rsync_timer = Timer() self.install_packages("rsync") def normalize(directory): """ Make sure a directory path ends with a trailing slash. """ return "%s/" % directory.rstrip("/") location = "%s:%s" % (self.ssh_alias, normalize(remote_directory)) self.logger.debug("Uploading %s to %s ..", local_directory, location) command = ["rsync", "-a"] command.extend(["--rsync-path", "mkdir -p %s && rsync" % pipes.quote(remote_directory)]) if cvs_exclude: command.append("--cvs-exclude") command.extend(["--exclude", ".hgignore"]) if delete: command.append("--delete") command.append(normalize(local_directory)) command.append(location) self.logger.debug("Generated rsync command: %s", quote_command_line(command)) exit_code = os.spawnvp(os.P_WAIT, command[0], command) if exit_code == 0: self.logger.debug("Finished upload using rsync in %s.", rsync_timer) self.logger.debug("rsync exited with status %d.", exit_code) if exit_code != 0: msg = "Failed to upload directory %s to %s, rsync exited with nonzero status %d! (command: %s)" raise ExternalCommandFailed, msg % (local_directory, location, exit_code, quote_command_line(command))