#!/usr/bin/env python #-*- python -*- ################################################################################ # DIANE Project. http://cern.ch/diane # # ################################################################################ # written by: Kuba Moscicki, 2004 CERN, based on lcg.installation.manager, diane.installation.manager and ganga-install import os,sys # default configuration CONFIG = {} CONFIG['script_revision'] = "$Revision: 1.14 $" CONFIG['website_url'] = 'http://cern.ch/diane' CONFIG['download_url'] = CONFIG['website_url']+'/packages' CONFIG['prefix'] = os.path.join(os.path.expandvars('$HOME'),'diane') CONFIG['platform'] = '' CONFIG['pre4.3'] = False CONFIG['ganga-version'] = 'auto' CONFIG['ganga-download'] = True CONFIG['ganga-set-platform'] = False # list of plugin Runtime Packages which are active in the installer # external package dependencies will be processed CONFIG['plugin_packages'] = [] def usage(): msg = """ usage: diane-install [options] VERSION LAST gets the last production version options: --prefix=DIR : (default: ~/diane) --silent : print less messages --verbose : print more messages --force : override existing installation --fetch-again : fetch all files again and override existing installation (implies --force) --platf=PLATFORM : select the platform (default %(platform)s) --prerelease : download the prerelease version --make-tarballs : instead of downloading make the tarballs in the tarball area (--force does not affect external dependencies tarballs in this case) --ganga-version=VER : download specified version of ganga (default: last), specify None to disable ganga download --ganga-set-platform : install ganga forcing the same platform as the one detected for DIANE --download-url=URL : the packages repository URL --http-proxy : the configuration for http(s) proxy (if used) --help : help message Install diane and all its components automatically into a local directory and creates a mirror of the installation packages. For the end-user convenience this installer will make a private ganga installation inside the diane installation tree. If specified, the --force flag will override this private ganga installation. To disable ganga installation specify --ganga-version=none. As Ganga is always downloaded from the central repository, the mirror and local installation options described below do not apply to Ganga. The local installation may be used as a mirror of the central repository. The packages directory of the mirror should be made accessible via a web server. To install from the mirror specify --download-url accordingly. The --download-url may be a local path to the packages directory. This may be handy for making local installations from network file systems or in case when you want to ship private diane installation to the Grid worker node in a sandbox. The file prefix/packages/FILES-VERSION-PLATFORM contains the minimal list of files necessary to install a given VERSION of diane. The --http-proxy option is used in two modes: 1) --http-proxy=http://user:password@host:port/ Note that is not safe to set the value in the command line is proxy required user/password authentication as this is considered not to comply with Unix security standards. 2) Instead set the option to empty string (i.e. --http-proxy=) and input the configuration string in interactive mode See http://cern.ch/diane for the list of available releases """ % CONFIG print msg # cookbook -- 15.3 def importName(modulename,name): module = __import__(modulename,globals(),locals(),[name]) return vars(module)[name] def main(): print "DIANE Installer, %(script_revision)s"%CONFIG import getopt try: opts, args = getopt.getopt(sys.argv[1:], "", ["prefix=","extern=","silent","force","make-tarballs","fetch-again","help","brute","verbose","ganga-version=","platf=","download-url=","http-proxy="]) except getopt.error, x: print "command line syntax error",x usage() sys.exit(2) silent = 1 force = 0 brute = 0 # access to the repository even in maintenance mode, dangerous thus undocumented -> for internal use only alldeps = False mode = 'download' for o,a in opts: if o == "--prefix": CONFIG['prefix'] = os.path.realpath(os.path.expandvars(os.path.expanduser(a.strip()))) if o == "--platf": CONFIG['platform'] = a if o == '--ganga-version': if a.lower() == 'none': CONFIG['ganga-download'] = False else: CONFIG['ganga-version'] = a if o == '--ganga-set-platform': CONFIG['ganga-set-platform'] = True if o == "--extern": pkgs = a.split(',') CONFIG['plugin_packages'] += pkgs if o == "--silent": silent = 1 if o == "--verbose": silent = 0 if o == "--force": force = 1 if o == "--fetch-again": force = 2 if o == "--brute": brute = 1 if o == "--make-tarballs": mode = 'create' silent = 0 if o == "--download-url": CONFIG['download_url'] = a if o == "--http-proxy": CONFIG['http_proxy'] = a if o == "--help": usage() sys.exit(0) CONFIG['tarball_dir'] = '%(prefix)s/packages'%CONFIG # all diane tarballs are stored in this directory CONFIG['external_dir'] = '%(prefix)s/external'%CONFIG # all external packages are unpacked here CONFIG['install_dir'] = '%(prefix)s/install'%CONFIG # diane is unpacked here print 'Packages repository:',CONFIG['download_url'] # create a temporary directory import getpass user = getpass.getuser() tmpdir = '/tmp/diane-%s'%user makedirs(tmpdir) makedirs(CONFIG['prefix']) makedirs(CONFIG['external_dir']) makedirs(CONFIG['install_dir']) makedirs(CONFIG['tarball_dir']) # check if there is a newer diane-install script, download the latest to tmpdir, import it as a module and check the CONFIG['script_revision'] if mode != 'create' and not fetch('diane-install',CONFIG['tarball_dir'],override=2,silent=1): incomplete_release_error() sys.exit(1) if mode != 'create': latest_installer_path = os.path.join(CONFIG['tarball_dir'],'diane-install') latest_installer_file = file(latest_installer_path) try: try: import imp latest_installer_module = imp.load_module('latest_installer_module',latest_installer_file,latest_installer_path,('','r',imp.PY_SOURCE)) def get_revision(s): return s[1:-1].split(':')[-1].strip() latest_revision = get_revision(latest_installer_module.CONFIG['script_revision']) current_revision = get_revision(CONFIG['script_revision']) except Exception,x: print 'WARNING: cannot check the latest version of the installer script: ',x if current_revision != latest_revision: print 'WARNING: your diane-install script (%s) does not match the one in the diane packages repository (%s)'%(current_revision, latest_revision) print print 'Get the newest diane-install from %s/diane-install or from %s'%(CONFIG['download_url'],latest_installer_path) print sys.exit(1) finally: latest_installer_file.close() if len(args) < 1: usage() print "ERROR: VERSION not specified" sys.exit(2) VERSION = args[0] CONFIG['VERSION'] = VERSION if fetch('MAINTENANCE',tmpdir,override=2,silent=2): # be very silent, always fetch this file print file(tmpdir+'/MAINTENANCE').read() if not brute: sys.exit(4) print print 'using brute force, at your own risk!' print if fetch('NEWS',tmpdir,override=2,silent=2): # be very silent, always fetch this file print file(tmpdir+'/NEWS').read() # resolve last production version if CONFIG['VERSION'].lower() == 'last': print 'Getting last production release' if not fetch('LAST_VERSION',tmpdir,override=2,silent=1): incomplete_release_error() CONFIG['VERSION'] = file(os.path.join(tmpdir,'LAST_VERSION')).read().strip() #if CONFIG['VERSION'].split('.')[:2] < ['4','3']: # CONFIG['pre4.3'] = True diane_top = '%(prefix)s/install/%(VERSION)s'%CONFIG diane_tarball = 'diane-%(VERSION)s.tar.gz' % CONFIG diane_tarball_path = '%(VERSION)s'%CONFIG # fetch a tarball to the tarball directory and then unpack in the target directory # tarballs must be correctly created with the relative paths stored inside def fetch_and_untar(tarball, targetDir, override=force,silent=silent, **kwds): if not fetch(tarball,CONFIG['tarball_dir'],override=force,silent=silent): incomplete_release_or_platform_error() untar(os.path.join(CONFIG['tarball_dir'],tarball),targetDir,silent=silent) # create a tarball in a tarball directory # the tarball contains a tarPath stored relatively from targetDir def make_tarball(tarball, targetDir, tarPath=None, override=force,silent=silent,**kwds): assert(tarPath) tar(os.path.join(CONFIG['tarball_dir'],tarball), targetDir, tarPath,silent=silent, opts="--exclude='*.py[co]'") def action(command): command(diane_tarball,CONFIG['install_dir'],tarPath=diane_tarball_path) # import setup.py module from bin directory to get the dependency list sys.path.insert(0,diane_top+'/python') if CONFIG['plugin_packages']: print "Resolving additional external dependencies for: ",' '.join(CONFIG['plugin_packages']) #override the platform if overriden with --platf option (required for versions >=4.3.2) import diane.PACKAGE if CONFIG['platform']: diane.PACKAGE._platformString = CONFIG['platform'] CONFIG['platform'] = diane.PACKAGE.getPlatformString() FILE_LIST = file(os.path.join(CONFIG['tarball_dir'],'FILES-%(VERSION)s-%(platform)s'%CONFIG),'w') FILE_LIST.write(os.path.join(CONFIG['tarball_dir'],'diane-install')+'\n') print 'Created:',FILE_LIST.name # we need a tarPath relative to the externalHome diane.PACKAGE._defaultExternalHome = '' for pkg in ['diane']+CONFIG['plugin_packages']: print 'resolving dependencies for',pkg try: _externalPackages = importName(pkg+'.PACKAGE','_externalPackages') except Exception,x: print 'WARNING: Cannot find dependency information in file '+pkg+'/PACKAGE.py' print x continue for name in _externalPackages: if silent: print '[%s] Checking %-25s '%(mode,name), else: print '='*10 print '[%s] Checking %s %s '%(mode,name,_externalPackages[name]), tarPath,tarball = diane.PACKAGE.getPackageInstallationPaths(name) if tarball: print ': REQUIRED' command(tarball,CONFIG['external_dir'],tarPath=tarPath) FILE_LIST.write(os.path.join(CONFIG['tarball_dir'],tarball)+'\n') else: print ': NOT REQUIRED' if mode == 'download': print "Downloading release %(VERSION)s %(platform)s into %(prefix)s" % CONFIG print '='*80 if os.path.exists(diane_top) and not force: error('This release already exists. Use --force to override it.') action(fetch_and_untar) if CONFIG['ganga-download']: print '='*80 print 'Installing ganga...', if not fetch('ganga-install',downloadURL='http://cern.ch/ganga/download',override=True,silent=silent): error('cannot download ganga-install') if CONFIG['ganga-version'] == 'auto': if not fetch('GANGA_VERSION',CONFIG['tarball_dir'],override=2,silent=silent): error('Cannot determine the ganga version to install (GANGA_VERSION file missing in the download repository)') CONFIG['ganga-version'] = file('%s/GANGA_VERSION'%CONFIG['tarball_dir']).read().strip() args = [] if CONFIG['platform'] and CONFIG['ganga-set-platform']: # Ganga platform string does not use the UCS extension, so remove if any ganga_platform = CONFIG['platform'].replace('-UCS2','').replace('-UCS4','') args.append('--platf=%s'%ganga_platform) ganga_top = '%s/ganga/install/%s'%(CONFIG['prefix'],CONFIG['ganga-version']) args.append('--prefix=%s/ganga'%CONFIG['prefix']) if silent: args.append('--silent') if force: args.append('--force') args.append(CONFIG['ganga-version']) makedirs(os.path.join(CONFIG['prefix'],'ganga')) logfn = os.path.join(CONFIG['prefix'],'ganga','install.log') print 'version', CONFIG['ganga-version'], '(logfile: %s)'%logfn cmd = 'python %s/ganga-install %s 2>&1 > %s'%(CONFIG['prefix'],' '.join(args),logfn) if not silent: print 'running:',cmd if os.system(cmd): error('problem while running ganga-install, details in %s'%logfn) #replace_prefix('%s/etc/env.sh'%diane_top,[('$GANGA_TOP',ganga_top)], silent) #replace_prefix('%s/etc/env.sh'%diane_top,[('$DIANE_TOP',diane_top)], silent) print '='*80 print print 'DIANE installed in %s.'%CONFIG['prefix'] print print 'Set user environment in bash shell by typing: $(%s/bin/diane-env)' % diane_top print print 'In case of problems see Frequently Asked Questions: http://twiki.cern.ch/twiki/bin/view/ArdaGrid/DIANEQuestionsAndAnswers' print if mode == 'create': if force: os.system('rm -f '+os.path.join(CONFIG['tarball_dir'],diane_tarball)) action(make_tarball) def replace_prefix(fn,prefix_list,silent): text = file(fn,'r').read() for p in prefix_list: text = text.replace(p[0],p[1]) if not silent: print 'replacing prefix %s by %s in file %s'%(p[0],p[1],fn) file(fn,'w').write(text) def makedirs(d): try: os.makedirs(d) print 'created:',d except OSError,x: import errno if x.errno != errno.EEXIST: raise def RED(s): return '\033[1;31mERROR: %s\033[m' % s def error(what=None): if what: print RED(what) sys.exit(2) def incomplete_release_error(): error('A component of diane release was not found on %s \nPlease report to the diane team'%CONFIG['download_url']) def incomplete_release_or_platform_error(): error('''A component of diane release was not found on %(download_url)s Check if the version which you specified (%(VERSION)s) is correct and not mispelled. Your platform (%(platform)s) may be unknown to us and hence precompiled dependencies unavailable. Please report to the diane team'''%CONFIG) def undefined_release_error(rel,avail=""): if avail: avail = 'Available releases\n---\n'+avail+'---' error('Requested release (%s) was not found in the diane repository\n%s\nConsult %s' %(rel,avail,CONFIG['website_url'])) import urllib, urllib2, string, stat # download files over http # override == 0 : never override any files # override == 1 : override if files differ in length # override > 1 : always override _urlOpenerSet=False def _configureURLOpener(): global _urlOpenerSet #configure the URL opener once if _urlOpenerSet: return handlers=[] if CONFIG.has_key('http_proxy'): if CONFIG['http_proxy']=='': #input interactively proxyConfig = raw_input("Enter http(s) proxy config (e.g. http://[user:password@]host:port/) : \n") else: proxyConfig=CONFIG['http_proxy'] if proxyConfig: handlers.append(urllib2.ProxyHandler({'http':proxyConfig,'https':proxyConfig})) opener = urllib2.build_opener(*handlers) urllib2.install_opener(opener) _urlOpenerSet = True def _urlretrieve(url,filename): fp = urllib2.urlopen(url) headers = fp.info() if not filename: raise IOError('Local file not specified') tfp = open(filename, 'wb') result = filename, headers bs = 1024*8 size = -1 if "content-length" in headers: size = int(headers["Content-Length"]) read = 0 blocknum = 0 while 1: block = fp.read(bs) if block == "": break read += len(block) tfp.write(block) blocknum += 1 fp.close() tfp.close() del fp del tfp # raise exception if actual size does not match content-length header if size >= 0 and read < size: raise IOError("%s (%s) retrieval incomplete: got only %i out of %i bytes" % (url,filename,read, size)) return result def fetch(tarFileName, targetDir=None, downloadURL = "", override=0, silent=0): _configureURLOpener() if not targetDir: targetDir = CONFIG['prefix'] if downloadURL == "": downloadURL = CONFIG['download_url'] tarFileURL = downloadURL + '/' + tarFileName tarFiles = [] fileName = targetDir + '/'+tarFileName if not silent: print "fetching file: " + fileName, if ( os.path.exists(fileName) ) : # compare size here inf = urllib2.urlopen(tarFileURL).info() label = 'Content-Length:' filesize = -1 for h in inf.headers: if h.find(label) != -1: filesize = int(h[len(label):].strip()) break if not silent: print 'size:',filesize, if override > 1: if not silent: print "overriding...", os.unlink(fileName) else: if override: if filesize != os.stat(fileName)[stat.ST_SIZE]: if not silent: print "file has different size, overriding...", os.unlink(fileName) else: if not silent: print "local file of the same size already exists, skipped" return 1 else: if not silent: print "file already fetched " tarFiles.append(tarFileName) return 1 def msg_nofile(): if silent < 2: print print RED("could not download the file : %s"%tarFileName) try: (fileNameRet, info) = _urlretrieve (tarFileURL, fileName) if (info.gettype() == "text/html") : tmpFile = open(fileNameRet) tmpTxt = tmpFile.readlines() tmpFile.close() if ( string.find( str(tmpTxt), "404 Not Found" ) != -1 ) : msg_nofile() os.remove(fileName) return 0 except urllib2.HTTPError,x: if x.code != 404: raise msg_nofile() return 0 except urllib2.URLError,x: print "An error encountered while retrieving a remote file (%s).\n"\ "If you are using a http proxy double-check the configuration string (see --http-proxy option)" % str(x) raise except IOError,x: import errno if x.errno != errno.ENOENT: raise msg_nofile() return 0 if not silent: print "ok" return 1 def tar(tarFileName, targetDir, tarPath, silent,skip=1,force=0,opts=""): import os.path if os.path.exists(tarFileName): msg = 'tarball already exists: %s'%tarFileName if not force and not skip: error(msg) print 'WARNING:',msg if skip: print 'skipping...' return cmd = 'cd %s; tar %s -czhf %s %s'%(targetDir,opts,tarFileName,tarPath) if not silent: print 'tarring ',tarFileName print cmd if os.system(cmd) != 0: #remove incomplete archive try: os.unlink(tarFileName) except OSError,e: pass error('Error creating tarball file %s in directory %s for %s' % (tarFileName,targetDir,tarPath)) def untar(tarFileName, targetDir,silent): cmd = 'cd %s; tar xfz %s'%(targetDir,tarFileName) if not silent: print 'untarring ',tarFileName print cmd if os.system(cmd) != 0: error('Error untarring file: %s in directory %s' % (tarFileName,targetDir)) if __name__ == '__main__': main()