2 '''Script that uploads to the new CMS conditions uploader.
3 Adapted to the new infrastructure from v6 of the upload.py script for the DropBox from Miguel Ojeda.
5 from __future__
import print_function
7 __author__ =
'Andreas Pfeiffer'
8 __copyright__ =
'Copyright 2015, CERN CMS'
9 __credits__ = [
'Giacomo Govi',
'Salvatore Di Guida',
'Miguel Ojeda',
'Andreas Pfeiffer']
10 __license__ =
'Unknown'
11 __maintainer__ =
'Giacomo Govi'
12 __email__ =
'giacomo.govi@cern.ch'
28 from datetime
import datetime
30 defaultBackend =
'online'
31 defaultHostname =
'cms-conddb-prod.cern.ch'
32 defaultDevHostname =
'cms-conddb-dev.cern.ch'
33 defaultUrlTemplate =
'https://%s/cmsDbUpload/'
34 defaultTemporaryFile =
'upload.tar.bz2'
35 defaultNetrcHost =
'ConditionUploader'
36 defaultWorkflow =
'offline'
37 prodLogDbSrv =
'cms_orcoff_prod'
38 devLogDbSrv =
'cms_orcoff_prep'
39 logDbSchema =
'CMS_COND_DROPBOX'
40 authPathEnvVar =
'COND_AUTH_PATH'
53 '''Like input() but with a default and automatic strip().
56 answer =
input(prompt)
60 return default.strip()
64 '''Like getInput() but tailored to get target workflows (synchronization options).
68 workflow =
getInput(defaultWorkflow, prompt)
70 if workflow
in frozenset([
'offline',
'hlt',
'express',
'prompt',
'pcl']):
73 logging.error(
'Please specify one of the allowed workflows. See above for the explanation on each of them.')
77 '''Makes the user choose from a list of options.
84 return optionsList[
int(index)]
86 logging.error(
'Please specify an index of the list (i.e. integer).')
88 logging.error(
'The index you provided is not in the given list.')
92 '''Like input() but repeats if nothing is provided and automatic strip().
96 answer =
input(prompt)
100 logging.error(
'You need to provide a value.')
103 def runWizard(basename, dataFilename, metadataFilename):
105 print(
'''\nWizard for metadata for %s
107 I will ask you some questions to fill the metadata file. For some of the questions there are defaults between square brackets (i.e. []), leave empty (i.e. hit Enter) to use them.''' % basename)
110 dataConnection = sqlite3.connect(dataFilename)
111 dataCursor = dataConnection.cursor()
113 dataCursor.execute(
'select NAME from TAG')
114 records = dataCursor.fetchall()
117 inputTags.append(rec[0])
119 if len(inputTags) == 0:
120 raise Exception(
"Could not find any input tag in the data file.")
123 print(
'\nI found the following input tags in your SQLite data file:')
124 for (index, inputTag)
in enumerate(inputTags):
125 print(
' %s) %s' % (index, inputTag))
128 '\nWhich is the input tag (i.e. the tag to be read from the SQLite data file)?\ne.g. 0 (you select the first in the list)\ninputTag [0]: ')
130 destinationDatabase =
''
132 while ( destinationDatabase !=
'oracle://cms_orcon_prod/CMS_CONDITIONS' and destinationDatabase !=
'oracle://cms_orcoff_prep/CMS_CONDITIONS' ):
135 '\nWhich is the destination database where the tags should be exported? \nPossible choices: oracle://cms_orcon_prod/CMS_CONDITIONS (or prod); oracle://cms_orcoff_prep/CMS_CONDITIONS (or prep) \ndestinationDatabase: '
138 '\nPlease choose one of the two valid destinations: \noracle://cms_orcon_prod/CMS_CONDITIONS (for prod) or oracle://cms_orcoff_prep/CMS_CONDITIONS (for prep) \
139 \ndestinationDatabase: '
141 raise Exception(
'No valid destination chosen. Bailing out...')
143 if destinationDatabase ==
'prod':
144 destinationDatabase =
'oracle://cms_orcon_prod/CMS_CONDITIONS'
145 if destinationDatabase ==
'prep':
146 destinationDatabase =
'oracle://cms_orcoff_prep/CMS_CONDITIONS'
151 '\nWhich is the given since? (if not specified, the one from the SQLite data file will be taken -- note that even if specified, still this may not be the final since, depending on the synchronization options you select later: if the synchronization target is not offline, and the since you give is smaller than the next possible one (i.e. you give a run number earlier than the one which will be started/processed next in prompt/hlt/express), the DropBox will move the since ahead to go to the first safe run instead of the value you gave)\ne.g. 1234\nsince []: ')
160 logging.error(
'The since value has to be an integer or empty (null).')
163 '\nWrite any comments/text you may want to describe your request\ne.g. Muon alignment scenario for...\nuserText []: ')
168 '\nWhich is the next destination tag to be added (leave empty to stop)?\ne.g. BeamSpotObjects_PCL_byRun_v0_offline\ndestinationTag []: ')
169 if not destinationTag:
170 if len(destinationTags) == 0:
171 logging.error(
'There must be at least one destination tag.')
175 if destinationTag
in destinationTags:
177 'You already added this destination tag. Overwriting the previous one with this new one.')
179 destinationTags[destinationTag] = {
183 'destinationDatabase': destinationDatabase,
184 'destinationTags': destinationTags,
185 'inputTag': inputTag,
187 'userText': userText,
190 metadata = json.dumps(metadata, sort_keys=
True, indent=4)
191 print(
'\nThis is the generated metadata:\n%s' % metadata)
194 '\nIs it fine (i.e. save in %s and *upload* the conditions if this is the latest file)?\nAnswer [n]: ' % metadataFilename).lower() ==
'y':
196 logging.info(
'Saving generated metadata in %s...', metadataFilename)
197 with open(metadataFilename,
'w')
as metadataFile:
198 metadataFile.write(metadata)
201 '''A common HTTP exception.
203 self.code is the response HTTP code as an integer.
204 self.response is the response body (i.e. page).
213 self.
args = (response.split(
'<p>')[1].
split(
'</p>')[0], )
218 CERN_SSO_CURL_CAPATH =
'/etc/pki/tls/certs'
221 '''Class used for querying URLs using the HTTP protocol.
224 retryCodes = frozenset([502, 503])
231 self.
curl.setopt(self.
curl.COOKIEFILE,
'')
236 self.
curl.setopt(self.
curl.SSL_VERIFYPEER, 0)
237 self.
curl.setopt(self.
curl.SSL_VERIFYHOST, 2)
244 '''Returns the list of cookies.
246 return self.
curl.getinfo(self.
curl.INFO_COOKIELIST)
251 self.
curl.setopt(self.
curl.COOKIELIST,
'ALL')
255 '''Allows to set a base URL which will be prefixed to all the URLs
256 that will be queried later.
262 '''Allows to set a proxy.
264 self.
curl.setopt(self.
curl.PROXY, proxy)
268 '''Allows to set a timeout.
270 self.
curl.setopt(self.
curl.TIMEOUT, timeout)
274 '''Allows to set retries.
276 The retries are a sequence of the seconds to wait per retry.
278 The retries are done on:
279 * PyCurl errors (includes network problems, e.g. not being able
280 to connect to the host).
281 * 502 Bad Gateway (for the moment, to avoid temporary
282 Apache-CherryPy issues).
283 * 503 Service Temporarily Unavailable (for when we update
292 self.
curl.setopt(pycurl.URL, url)
293 self.
curl.setopt(pycurl.VERBOSE, 0)
302 self.
curl.setopt(pycurl.HTTPHEADER, [
'Accept: application/json'])
304 self.
curl.setopt(self.
curl.HTTPGET, 0)
306 response = io.BytesIO()
307 self.
curl.setopt(pycurl.WRITEFUNCTION, response.write)
308 self.
curl.setopt(pycurl.USERPWD,
'%s:%s' % (username, password) )
309 logging.debug(
'going to connect to server at: %s' % url )
312 code = self.
curl.getinfo(pycurl.RESPONSE_CODE)
313 logging.debug(
'got: %s ',
str(code))
314 if code
in ( 502,503,504 ):
315 logging.debug(
'Trying again after %d seconds...', waitForRetry)
316 time.sleep( waitForRetry )
317 response = io.StringIO()
318 self.
curl.setopt(pycurl.WRITEFUNCTION, response.write)
319 self.
curl.setopt(pycurl.USERPWD,
'%s:%s' % (username, password) )
321 code = self.
curl.getinfo(pycurl.RESPONSE_CODE)
322 resp = response.getvalue().
decode(
'UTF-8')
324 if code==500
and not resp.find(
"INVALID_CREDENTIALS")==-1:
325 logging.error(
"Invalid credentials provided.")
327 if code==403
and not resp.find(
"Unauthorized access")==-1:
328 logging.error(
"Unauthorized access. Please check the membership of group 'cms-cond-dropbox'")
332 self.
token = json.loads( resp )[
'token']
333 except Exception
as e:
334 errorMsg =
'Error while decoding returned json string'
335 logging.debug(
'http::getToken> error while decoding json: %s ',
str(resp) )
336 logging.debug(
"error getting token: %s",
str(e))
339 errorMsg =
'HTTP Error code %s ' %code
340 logging.debug(
'got: %s ',
str(code))
341 logging.debug(
'http::getToken> got error from server: %s ',
str(resp) )
346 logging.debug(
'token: %s', self.
token)
347 logging.debug(
'returning: %s', response.getvalue().
decode(
'UTF-8'))
349 return response.getvalue()
351 def query(self, url, data = None, files = None, keepCookies = True):
352 '''Queries a URL, optionally with some data (dictionary).
354 If no data is specified, a GET request will be used.
355 If some data is specified, a POST request will be used.
357 If files is specified, it must be a dictionary like data but
358 the values are filenames.
360 By default, cookies are kept in-between requests.
362 A HTTPError exception is raised if the response's HTTP code is not 200.
371 data4log = copy.copy(data)
373 if 'password' in data4log.keys():
374 data4log[
'password'] =
'*'
376 retries = [0] + list(self.
retries)
379 logging.debug(
'Querying %s with data %s and files %s (retries left: %s, current sleep: %s)...', url, data4log, files, len(retries), retries[0])
381 time.sleep(retries.pop(0))
384 self.
curl.setopt(self.
curl.URL, url)
385 self.
curl.setopt(self.
curl.HTTPGET, 1)
388 self.
curl.setopt(pycurl.USERPWD,
'%s:""' % (
str(self.
token), ) )
389 self.
curl.setopt(pycurl.HTTPHEADER, [
'Accept: application/json'])
391 if data
is not None or files
is not None:
397 finalData.update(data)
399 if files
is not None:
400 for (key, fileName)
in files.items():
401 finalData[key] = (self.
curl.FORM_FILE, fileName)
402 self.
curl.setopt( self.
curl.HTTPPOST, list(finalData.items()) )
404 self.
curl.setopt(pycurl.VERBOSE, 0)
406 response = io.BytesIO()
407 self.
curl.setopt(self.
curl.WRITEFUNCTION, response.write)
410 code = self.
curl.getinfo(self.
curl.RESPONSE_CODE)
412 if code
in self.retryCodes
and len(retries) > 0:
413 logging.debug(
'Retrying since we got the %s error code...', code)
417 raise HTTPError(code, response.getvalue())
419 return response.getvalue()
421 except pycurl.error
as e:
422 if len(retries) == 0:
424 logging.debug(
'Retrying since we got the %s pycurl exception...',
str(e))
429 tarInfo = tarFile.gettarinfo(fileobj = fileobj, arcname = arcname)
431 tarInfo.uid = tarInfo.gid = tarInfo.mtime = 0
432 tarInfo.uname = tarInfo.gname =
'root'
433 tarFile.addfile(tarInfo, fileobj)
436 '''Upload conditions to the CMS conditions uploader service.
439 def __init__(self, hostname = defaultHostname, urlTemplate = defaultUrlTemplate):
453 if self.
token is None:
454 logging.debug(
"Initializing connection with server %s",self.
hostname)
458 if socket.getfqdn().
strip().endswith(
'.cms'):
459 self.
http.setProxy(
'https://cmsproxy.cms:3128/')
461 '''Signs in the server.
463 logging.info(
'%s: Signing in user %s ...', self.
hostname, username)
466 except Exception
as e:
472 logging.error(
"Caught exception when trying to connect to %s: %s" % (self.
hostname,
str(e)) )
476 logging.error(
"could not get token for user %s from %s" % (username, self.
hostname) )
478 logging.debug(
"got: '%s'",
str(self.
token) )
482 logging.debug(
"User %s has been already authenticated." %username)
486 '''Signs out the server.
489 logging.info(
'%s: Signing out...', self.
hostname)
495 '''Updates this script, if a new version is found.
498 logging.debug(
'%s: Checking if a newer version of this script is available ...', self.
hostname)
499 version =
int(self.
http.
query(
'getUploadScriptVersion'))
501 if version <= __version__:
502 logging.debug(
'%s: Script is up-to-date.', self.
hostname)
505 logging.info(
'%s: Updating to a newer version (%s) than the current one (%s): downloading ...', self.
hostname, version, __version__)
507 uploadScript = self.
http.
query(
'getUploadScript')
511 logging.info(
'%s: ... saving the new version ...', self.
hostname)
512 with open(sys.argv[0],
'wb')
as f:
513 f.write(uploadScript)
515 logging.info(
'%s: ... executing the new version...', self.
hostname)
516 os.execl(sys.executable, *([sys.executable] + sys.argv))
519 def uploadFile(self, filename, backend = defaultBackend, temporaryFile = defaultTemporaryFile):
520 '''Uploads a file to the dropBox.
522 The filename can be without extension, with .db or with .txt extension.
523 It will be stripped and then both .db and .txt files are used.
526 basepath = filename.rsplit(
'.db', 1)[0].rsplit(
'.txt', 1)[0]
527 basename = os.path.basename(basepath)
529 logging.debug(
'%s: %s: Creating tar file for upload ...', self.
hostname, basename)
532 tarFile = tarfile.open(temporaryFile,
'w:bz2')
534 with open(
'%s.db' % basepath,
'rb')
as data:
536 except Exception
as e:
537 msg =
'Error when creating tar file. \n'
538 msg +=
'Please check that you have write access to the directory you are running,\n'
539 msg +=
'and that you have enough space on this disk (df -h .)\n'
543 with tempfile.NamedTemporaryFile(mode=
'rb+')
as metadata:
544 with open(
'%s.txt' % basepath,
'r')
as originalMetadata:
545 metadata.write(json.dumps(json.load(originalMetadata), sort_keys =
True, indent = 4).
encode())
552 logging.debug(
'%s: %s: Calculating hash...', self.
hostname, basename)
554 fileHash = hashlib.sha1()
555 with open(temporaryFile,
'rb')
as f:
557 data = f.read(4 * 1024 * 1024)
560 fileHash.update(data)
562 fileHash = fileHash.hexdigest()
563 fileInfo = os.stat(temporaryFile)
564 fileSize = fileInfo.st_size
566 logging.debug(
'%s: %s: Hash: %s', self.
hostname, basename, fileHash)
568 logging.info(
'%s: %s: Uploading file (%s, size %s) to the %s backend...', self.
hostname, basename, fileHash, fileSize, backend)
569 os.rename(temporaryFile, fileHash)
574 'fileName': basename,
578 'uploadedFile': fileHash,
581 except Exception
as e:
582 logging.error(
'Error from uploading: %s' %
str(e))
583 ret = json.dumps( {
"status": -1,
"upload" : {
'itemStatus' : { basename : {
'status':
'failed',
'info':
str(e)}}},
"error" :
str(e)} )
587 statusInfo = json.loads(ret)[
'upload']
588 logging.debug(
'upload returned: %s', statusInfo )
593 for tag, info
in statusInfo[
'itemStatus'].
items():
594 logging.debug(
'checking tag %s, info %s', tag,
str(json.dumps(info, indent=4,sort_keys=
True)) )
595 if 'ok' in info[
'status'].lower() :
597 logging.info(
'tag %s successfully uploaded', tag)
598 if 'skip' in info[
'status'].lower() :
599 skippedTags.append( tag )
600 logging.warning(
'found tag %s to be skipped. reason: \n ... \t%s ', tag, info[
'info'])
601 if 'fail' in info[
'status'].lower() :
602 failedTags.append( tag )
603 logging.error(
'found tag %s failed to upload. reason: \n ... \t%s ', tag, info[
'info'])
605 if len(okTags) > 0: logging.info (
"tags sucessfully uploaded: %s ",
str(okTags) )
606 if len(skippedTags) > 0: logging.warning(
"tags SKIPped to upload : %s ",
str(skippedTags) )
607 if len(failedTags) > 0: logging.error (
"tags FAILed to upload : %s ",
str(failedTags) )
609 fileLogURL =
'https://cms-conddb.cern.ch/cmsDbBrowser/logs/show_cond_uploader_log/%s/%s'
611 if self.
hostname==
'cms-conddb-dev.cern.ch':
613 logging.info(
'file log at: %s', fileLogURL % (backend,fileHash))
622 if authPathEnvVar
in os.environ:
623 authPath = os.environ[authPathEnvVar]
624 netrcPath = os.path.join(authPath,
'.netrc')
625 if options.authPath
is not None:
626 netrcPath = os.path.join( options.authPath,
'.netrc' )
629 (username, account, password) = netrc.netrc( netrcPath ).authenticators(options.netrcHost)
633 'netrc entry "%s" not found: if you wish not to have to retype your password, you can add an entry in your .netrc file. However, beware of the risks of having your password stored as plaintext. Instead.',
637 defaultUsername = getpass.getuser()
638 if defaultUsername
is None:
639 defaultUsername =
'(not found)'
641 username =
getInput(defaultUsername,
'\nUsername [%s]: ' % defaultUsername)
642 password = getpass.getpass(
'Password: ')
644 return username, password
654 for filename
in arguments:
655 basepath = filename.rsplit(
'.db', 1)[0].rsplit(
'.txt', 1)[0]
656 basename = os.path.basename(basepath)
657 dataFilename =
'%s.db' % basepath
658 metadataFilename =
'%s.txt' % basepath
660 logging.info(
'Checking %s...', basename)
664 with open(dataFilename,
'rb')
as dataFile:
667 errMsg =
'Impossible to open SQLite data file %s' %dataFilename
668 logging.error( errMsg )
670 ret[
'error'] = errMsg
676 dbcon = sqlite3.connect( dataFilename )
677 dbcur = dbcon.cursor()
678 dbcur.execute(
'SELECT * FROM IOV')
679 rows = dbcur.fetchall()
684 errMsg =
'The input SQLite data file %s contains no data.' %dataFilename
685 logging.error( errMsg )
687 ret[
'error'] = errMsg
689 except Exception
as e:
690 errMsg =
'Check on input SQLite data file %s failed: %s' %(dataFilename,
str(e))
691 logging.error( errMsg )
693 ret[
'error'] = errMsg
698 with open(metadataFilename,
'rb')
as metadataFile:
701 if e.errno != errno.ENOENT:
702 errMsg =
'Impossible to open file %s (for other reason than not existing)' %metadataFilename
703 logging.error( errMsg )
705 ret[
'error'] = errMsg
708 if getInput(
'y',
'\nIt looks like the metadata file %s does not exist. Do you want me to create it and help you fill it?\nAnswer [y]: ' % metadataFilename).lower() !=
'y':
709 errMsg =
'Metadata file %s does not exist' %metadataFilename
710 logging.error( errMsg )
712 ret[
'error'] = errMsg
715 runWizard(basename, dataFilename, metadataFilename)
725 for filename
in arguments:
726 backend = options.backend
727 basepath = filename.rsplit(
'.db', 1)[0].rsplit(
'.txt', 1)[0]
728 metadataFilename =
'%s.txt' % basepath
729 with open(metadataFilename,
'rb')
as metadataFile:
730 metadata = json.load( metadataFile )
733 destDb = metadata[
'destinationDatabase']
734 if destDb.startswith(
'oracle://cms_orcon_prod')
or destDb.startswith(
'oracle://cms_orcoff_prep'):
735 hostName = defaultHostname
736 if destDb.startswith(
'oracle://cms_orcoff_prep'):
737 hostName = defaultDevHostname
738 dropBox.setHost( hostName )
739 authRet = dropBox.signIn( username, password )
741 msg =
"Error trying to connect to the server. Aborting."
743 msg =
"Error while signin in. Aborting."
745 return {
'status' : authRet,
'error' : msg }
746 results[filename] = dropBox.uploadFile(filename, options.backend, options.temporaryFile)
748 results[filename] =
False
749 logging.error(
"DestinationDatabase %s is not valid. Skipping the upload." %destDb)
750 if not results[filename]:
754 ret[
'files'] = results
755 logging.debug(
"all files processed, logging out now.")
759 except HTTPError
as e:
760 logging.error(
'got HTTP error: %s',
str(e))
761 return {
'status' : -1,
'error' :
str(e) }
766 '''Uploads a bunch of files coming from Tier0.
767 This has the following requirements:
768 * Username/Password based authentication.
769 * Uses the online backend.
770 * Ignores errors related to the upload/content (e.g. duplicated file).
775 dropBox.signIn(username, password)
777 for filename
in filenames:
779 result = dropBox.uploadFile(filename, backend =
'test')
780 except HTTPError
as e:
785 logging.error(
'HTTP Exception 400 Bad Request: Upload-related, skipping. Message: %s', e)
797 logging.error(
'Error from dropbox, upload-related, skipping.')
804 logDbSrv = prodLogDbSrv
805 if options.hostname == defaultDevHostname:
806 logDbSrv = devLogDbSrv
807 if options.authPath
is not None:
808 netrcPath = os.path.join( options.authPath,
'.netrc' )
810 netrcKey =
'%s/%s' %(logDbSrv,logDbSchema)
812 (username, account, password) = netrc.netrc( netrcPath ).authenticators( netrcKey )
814 logging.error(
'Cannot access netrc file.')
816 except Exception
as e:
817 logging.error(
'Netrc file is invalid: %s' %
str(e))
819 conStr =
'%s/%s@%s' %(username,password,logDbSrv)
820 con = cx_Oracle.connect( conStr )
822 fh = options.reUpload
823 cur.execute(
'SELECT FILECONTENT, STATE FROM FILES WHERE FILEHASH = :HASH',{
'HASH':fh})
829 logging.info(
"Found file %s in state '%s;" %(fh,r[1]))
833 logging.error(
"No file uploaded found with hash %s" %fh)
837 with open(fname,
"wb" )
as f:
839 rname =
'reupload_%s' %fh
840 with tarfile.open(fname)
as tar:
844 mdfile =
'metadata.txt'
845 if os.path.exists(dfile):
847 os.chmod(dfile,0o755)
848 os.rename(dfile,
'%s.db' %rname)
850 logging.error(
'Tar file does not contain the data file')
852 if os.path.exists(mdfile):
853 os.utime(mdfile,
None)
854 os.chmod(mdfile,0o755)
856 with open(mdfile)
as md:
857 mdata = json.load(md)
858 datelabel = datetime.now().strftime(
"%y-%m-%d %H:%M:%S")
860 logging.error(
'Metadata file is empty.')
862 logging.debug(
'Preparing new metadata file...')
863 mdata[
'userText'] =
'reupload %s : %s' %(datelabel,mdata[
'userText'])
864 with open(
'%s.txt' %rname,
'wb')
as jf:
865 jf.write( json.dumps( mdata, sort_keys=
True, indent = 2 ) )
869 logging.error(
'Tar file does not contain the metadata file')
871 logging.info(
'Files %s prepared for the upload.' %rname)
873 return upload(options, arguments)
878 if 'status' not in results:
879 print(
'Unexpected error.')
881 ret = results[
'status']
883 print(
"upload ended with code: %s" %ret)
890 parser = optparse.OptionParser(usage =
891 'Usage: %prog [options] <file> [<file> ...]\n'
894 parser.add_option(
'-d',
'--debug',
898 help =
'Switch on printing debug information. Default: %default',
901 parser.add_option(
'-b',
'--backend',
903 default = defaultBackend,
904 help =
'dropBox\'s backend to upload to. Default: %default',
907 parser.add_option(
'-H',
'--hostname',
909 default = defaultHostname,
910 help =
'dropBox\'s hostname. Default: %default',
913 parser.add_option(
'-u',
'--urlTemplate',
914 dest =
'urlTemplate',
915 default = defaultUrlTemplate,
916 help =
'dropBox\'s URL template. Default: %default',
919 parser.add_option(
'-f',
'--temporaryFile',
920 dest =
'temporaryFile',
921 default = defaultTemporaryFile,
922 help =
'Temporary file that will be used to store the first tar file. Note that it then will be moved to a file with the hash of the file as its name, so there will be two temporary files created in fact. Default: %default',
925 parser.add_option(
'-n',
'--netrcHost',
927 default = defaultNetrcHost,
928 help =
'The netrc host (machine) from where the username and password will be read. Default: %default',
931 parser.add_option(
'-a',
'--authPath',
934 help =
'The path of the .netrc file for the authentication. Default: $HOME',
937 parser.add_option(
'-r',
'--reUpload',
940 help =
'The hash of the file to upload again.',
943 (options, arguments) = parser.parse_args()
945 logLevel = logging.INFO
947 logLevel = logging.DEBUG
949 format =
'[%(asctime)s] %(levelname)s: %(message)s',
953 if len(arguments) < 1:
954 if options.reUpload
is None:
959 if options.reUpload
is not None:
960 print(
"ERROR: options -r can't be specified on a new file upload.")
963 return upload(options, arguments)
967 global defaultNetrcHost
969 (username, account, password) = netrc.netrc().authenticators(defaultNetrcHost)
971 filenames = [
'testFiles/localSqlite-top2']
976 if __name__ ==
'__main__':