Wed, 31 Dec 2014 07:16:47 +0100
Revert simplistic fix pending revisit of Mozilla integration attempt.
michael@0 | 1 | #!/usr/bin/python |
michael@0 | 2 | # |
michael@0 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 6 | # |
michael@0 | 7 | # When run directly, this script expects the following environment variables |
michael@0 | 8 | # to be set: |
michael@0 | 9 | # UPLOAD_HOST : host to upload files to |
michael@0 | 10 | # UPLOAD_USER : username on that host |
michael@0 | 11 | # UPLOAD_PATH : path on that host to put the files in |
michael@0 | 12 | # |
michael@0 | 13 | # And will use the following optional environment variables if set: |
michael@0 | 14 | # UPLOAD_SSH_KEY : path to a ssh private key to use |
michael@0 | 15 | # UPLOAD_PORT : port to use for ssh |
michael@0 | 16 | # POST_UPLOAD_CMD: a commandline to run on the remote host after uploading. |
michael@0 | 17 | # UPLOAD_PATH and the full paths of all files uploaded will |
michael@0 | 18 | # be appended to the commandline. |
michael@0 | 19 | # |
michael@0 | 20 | # All files to be uploaded should be passed as commandline arguments to this |
michael@0 | 21 | # script. The script takes one other parameter, --base-path, which you can use |
michael@0 | 22 | # to indicate that files should be uploaded including their paths relative |
michael@0 | 23 | # to the base path. |
michael@0 | 24 | |
michael@0 | 25 | import sys, os |
michael@0 | 26 | from optparse import OptionParser |
michael@0 | 27 | from subprocess import PIPE, Popen, check_call |
michael@0 | 28 | |
michael@0 | 29 | def RequireEnvironmentVariable(v): |
michael@0 | 30 | """Return the value of the environment variable named v, or print |
michael@0 | 31 | an error and exit if it's unset (or empty).""" |
michael@0 | 32 | if not v in os.environ or os.environ[v] == "": |
michael@0 | 33 | print "Error: required environment variable %s not set" % v |
michael@0 | 34 | sys.exit(1) |
michael@0 | 35 | return os.environ[v] |
michael@0 | 36 | |
michael@0 | 37 | def OptionalEnvironmentVariable(v): |
michael@0 | 38 | """Return the value of the environment variable named v, or None |
michael@0 | 39 | if it's unset (or empty).""" |
michael@0 | 40 | if v in os.environ and os.environ[v] != "": |
michael@0 | 41 | return os.environ[v] |
michael@0 | 42 | return None |
michael@0 | 43 | |
michael@0 | 44 | def FixupMsysPath(path): |
michael@0 | 45 | """MSYS helpfully translates absolute pathnames in environment variables |
michael@0 | 46 | and commandline arguments into Windows native paths. This sucks if you're |
michael@0 | 47 | trying to pass an absolute path on a remote server. This function attempts |
michael@0 | 48 | to un-mangle such paths.""" |
michael@0 | 49 | if 'OSTYPE' in os.environ and os.environ['OSTYPE'] == 'msys': |
michael@0 | 50 | # sort of awful, find out where our shell is (should be in msys/bin) |
michael@0 | 51 | # and strip the first part of that path out of the other path |
michael@0 | 52 | if 'SHELL' in os.environ: |
michael@0 | 53 | sh = os.environ['SHELL'] |
michael@0 | 54 | msys = sh[:sh.find('/bin')] |
michael@0 | 55 | if path.startswith(msys): |
michael@0 | 56 | path = path[len(msys):] |
michael@0 | 57 | return path |
michael@0 | 58 | |
michael@0 | 59 | def WindowsPathToMsysPath(path): |
michael@0 | 60 | """Translate a Windows pathname to an MSYS pathname. |
michael@0 | 61 | Necessary because we call out to ssh/scp, which are MSYS binaries |
michael@0 | 62 | and expect MSYS paths.""" |
michael@0 | 63 | if sys.platform != 'win32': |
michael@0 | 64 | return path |
michael@0 | 65 | (drive, path) = os.path.splitdrive(os.path.abspath(path)) |
michael@0 | 66 | return "/" + drive[0] + path.replace('\\','/') |
michael@0 | 67 | |
michael@0 | 68 | def AppendOptionalArgsToSSHCommandline(cmdline, port, ssh_key): |
michael@0 | 69 | """Given optional port and ssh key values, append valid OpenSSH |
michael@0 | 70 | commandline arguments to the list cmdline if the values are not None.""" |
michael@0 | 71 | if port is not None: |
michael@0 | 72 | cmdline.append("-P%d" % port) |
michael@0 | 73 | if ssh_key is not None: |
michael@0 | 74 | # Don't interpret ~ paths - ssh can handle that on its own |
michael@0 | 75 | if not ssh_key.startswith('~'): |
michael@0 | 76 | ssh_key = WindowsPathToMsysPath(ssh_key) |
michael@0 | 77 | cmdline.extend(["-o", "IdentityFile=%s" % ssh_key]) |
michael@0 | 78 | |
michael@0 | 79 | def DoSSHCommand(command, user, host, port=None, ssh_key=None): |
michael@0 | 80 | """Execute command on user@host using ssh. Optionally use |
michael@0 | 81 | port and ssh_key, if provided.""" |
michael@0 | 82 | cmdline = ["ssh"] |
michael@0 | 83 | AppendOptionalArgsToSSHCommandline(cmdline, port, ssh_key) |
michael@0 | 84 | cmdline.extend(["%s@%s" % (user, host), command]) |
michael@0 | 85 | cmd = Popen(cmdline, stdout=PIPE) |
michael@0 | 86 | retcode = cmd.wait() |
michael@0 | 87 | if retcode != 0: |
michael@0 | 88 | raise Exception("Command %s returned non-zero exit code: %i" % \ |
michael@0 | 89 | (cmdline, retcode)) |
michael@0 | 90 | return cmd.stdout.read().strip() |
michael@0 | 91 | |
michael@0 | 92 | def DoSCPFile(file, remote_path, user, host, port=None, ssh_key=None): |
michael@0 | 93 | """Upload file to user@host:remote_path using scp. Optionally use |
michael@0 | 94 | port and ssh_key, if provided.""" |
michael@0 | 95 | cmdline = ["scp"] |
michael@0 | 96 | AppendOptionalArgsToSSHCommandline(cmdline, port, ssh_key) |
michael@0 | 97 | cmdline.extend([WindowsPathToMsysPath(file), |
michael@0 | 98 | "%s@%s:%s" % (user, host, remote_path)]) |
michael@0 | 99 | check_call(cmdline) |
michael@0 | 100 | |
michael@0 | 101 | def GetRemotePath(path, local_file, base_path): |
michael@0 | 102 | """Given a remote path to upload to, a full path to a local file, and an |
michael@0 | 103 | optional full path that is a base path of the local file, construct the |
michael@0 | 104 | full remote path to place the file in. If base_path is not None, include |
michael@0 | 105 | the relative path from base_path to file.""" |
michael@0 | 106 | if base_path is None or not local_file.startswith(base_path): |
michael@0 | 107 | return path |
michael@0 | 108 | dir = os.path.dirname(local_file) |
michael@0 | 109 | # strip base_path + extra slash and make it unixy |
michael@0 | 110 | dir = dir[len(base_path)+1:].replace('\\','/') |
michael@0 | 111 | return path + dir |
michael@0 | 112 | |
michael@0 | 113 | def UploadFiles(user, host, path, files, verbose=False, port=None, ssh_key=None, base_path=None, upload_to_temp_dir=False, post_upload_command=None): |
michael@0 | 114 | """Upload each file in the list files to user@host:path. Optionally pass |
michael@0 | 115 | port and ssh_key to the ssh commands. If base_path is not None, upload |
michael@0 | 116 | files including their path relative to base_path. If upload_to_temp_dir is |
michael@0 | 117 | True files will be uploaded to a temporary directory on the remote server. |
michael@0 | 118 | Generally, you should have a post upload command specified in these cases |
michael@0 | 119 | that can move them around to their correct location(s). |
michael@0 | 120 | If post_upload_command is not None, execute that command on the remote host |
michael@0 | 121 | after uploading all files, passing it the upload path, and the full paths to |
michael@0 | 122 | all files uploaded. |
michael@0 | 123 | If verbose is True, print status updates while working.""" |
michael@0 | 124 | if upload_to_temp_dir: |
michael@0 | 125 | path = DoSSHCommand("mktemp -d", user, host, port=port, ssh_key=ssh_key) |
michael@0 | 126 | if not path.endswith("/"): |
michael@0 | 127 | path += "/" |
michael@0 | 128 | if base_path is not None: |
michael@0 | 129 | base_path = os.path.abspath(base_path) |
michael@0 | 130 | remote_files = [] |
michael@0 | 131 | try: |
michael@0 | 132 | for file in files: |
michael@0 | 133 | file = os.path.abspath(file) |
michael@0 | 134 | if not os.path.isfile(file): |
michael@0 | 135 | raise IOError("File not found: %s" % file) |
michael@0 | 136 | # first ensure that path exists remotely |
michael@0 | 137 | remote_path = GetRemotePath(path, file, base_path) |
michael@0 | 138 | DoSSHCommand("mkdir -p " + remote_path, user, host, port=port, ssh_key=ssh_key) |
michael@0 | 139 | if verbose: |
michael@0 | 140 | print "Uploading " + file |
michael@0 | 141 | DoSCPFile(file, remote_path, user, host, port=port, ssh_key=ssh_key) |
michael@0 | 142 | remote_files.append(remote_path + '/' + os.path.basename(file)) |
michael@0 | 143 | if post_upload_command is not None: |
michael@0 | 144 | if verbose: |
michael@0 | 145 | print "Running post-upload command: " + post_upload_command |
michael@0 | 146 | file_list = '"' + '" "'.join(remote_files) + '"' |
michael@0 | 147 | DoSSHCommand('%s "%s" %s' % (post_upload_command, path, file_list), user, host, port=port, ssh_key=ssh_key) |
michael@0 | 148 | finally: |
michael@0 | 149 | if upload_to_temp_dir: |
michael@0 | 150 | DoSSHCommand("rm -rf %s" % path, user, host, port=port, |
michael@0 | 151 | ssh_key=ssh_key) |
michael@0 | 152 | if verbose: |
michael@0 | 153 | print "Upload complete" |
michael@0 | 154 | |
michael@0 | 155 | if __name__ == '__main__': |
michael@0 | 156 | host = RequireEnvironmentVariable('UPLOAD_HOST') |
michael@0 | 157 | user = RequireEnvironmentVariable('UPLOAD_USER') |
michael@0 | 158 | path = OptionalEnvironmentVariable('UPLOAD_PATH') |
michael@0 | 159 | upload_to_temp_dir = OptionalEnvironmentVariable('UPLOAD_TO_TEMP') |
michael@0 | 160 | port = OptionalEnvironmentVariable('UPLOAD_PORT') |
michael@0 | 161 | if port is not None: |
michael@0 | 162 | port = int(port) |
michael@0 | 163 | key = OptionalEnvironmentVariable('UPLOAD_SSH_KEY') |
michael@0 | 164 | post_upload_command = OptionalEnvironmentVariable('POST_UPLOAD_CMD') |
michael@0 | 165 | if (not path and not upload_to_temp_dir) or (path and upload_to_temp_dir): |
michael@0 | 166 | print "One (and only one of UPLOAD_PATH or UPLOAD_TO_TEMP must be " + \ |
michael@0 | 167 | "defined." |
michael@0 | 168 | sys.exit(1) |
michael@0 | 169 | if sys.platform == 'win32': |
michael@0 | 170 | if path is not None: |
michael@0 | 171 | path = FixupMsysPath(path) |
michael@0 | 172 | if post_upload_command is not None: |
michael@0 | 173 | post_upload_command = FixupMsysPath(post_upload_command) |
michael@0 | 174 | |
michael@0 | 175 | parser = OptionParser(usage="usage: %prog [options] <files>") |
michael@0 | 176 | parser.add_option("-b", "--base-path", |
michael@0 | 177 | action="store", dest="base_path", |
michael@0 | 178 | help="Preserve file paths relative to this path when uploading. If unset, all files will be uploaded directly to UPLOAD_PATH.") |
michael@0 | 179 | (options, args) = parser.parse_args() |
michael@0 | 180 | if len(args) < 1: |
michael@0 | 181 | print "You must specify at least one file to upload" |
michael@0 | 182 | sys.exit(1) |
michael@0 | 183 | try: |
michael@0 | 184 | UploadFiles(user, host, path, args, base_path=options.base_path, |
michael@0 | 185 | port=port, ssh_key=key, upload_to_temp_dir=upload_to_temp_dir, |
michael@0 | 186 | post_upload_command=post_upload_command, |
michael@0 | 187 | verbose=True) |
michael@0 | 188 | except IOError, (strerror): |
michael@0 | 189 | print strerror |
michael@0 | 190 | sys.exit(1) |
michael@0 | 191 | except Exception, (err): |
michael@0 | 192 | print err |
michael@0 | 193 | sys.exit(2) |