File indexing completed on 2025-01-17 09:03:01
0001
0002
0003
0004
0005 '''
0006 Deploy the singularity container built by the CI for this version of the software.
0007
0008 The current version is determined from the currently loaded git branch or tag,
0009 unless it is explicitly set on the command line.
0010
0011 Authors:
0012 - Whitney Armstrong <warmstrong@anl.gov>
0013 - Sylvester Joosten <sjoosten@anl.gov>
0014 '''
0015
0016 import os
0017 import argparse
0018 import re
0019 import urllib.request
0020
0021
0022 DEFAULT_IMG='eic_xl'
0023 DEFAULT_VERSION='3.0.1'
0024
0025 SHORTCUTS = ['eic-shell']
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035 CONTAINER_URL = r'https://eicweb.phy.anl.gov/api/v4/projects/290/jobs/artifacts/{ref}/raw/build/{img}.sif?job={job}'
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045 DOCKER_REF = r'docker://eicweb/{img}:{tag}'
0046
0047
0048 BIND_DIRECTIVE= '-B {0}:{0}'
0049
0050 class UnknownVersionError(Exception):
0051 pass
0052 class ContainerDownloadError(Exception):
0053 pass
0054 class InvalidArgumentError(Exception):
0055 pass
0056
0057 def smart_mkdir(dir):
0058 '''functions as mkdir -p, with a write-check.
0059
0060 Raises an exception if the directory is not writeable.
0061 '''
0062 if not os.path.exists(dir):
0063 try:
0064 os.makedirs(dir)
0065 except Exception as e:
0066 print('ERROR: unable to create directory', dir)
0067 raise e
0068 if not os.access(dir, os.W_OK):
0069 print('ERROR: We do not have the write privileges to', dir)
0070 raise InvalidArgumentError()
0071
0072
0073 _LAUNCHER='''#!/usr/bin/env bash
0074
0075 ## Boilerplate to make pipes work
0076 piped_args=
0077 if [ -p /dev/stdin ]; then
0078 # If we want to read the input line by line
0079 while IFS= read line; do
0080 if [ -z "$piped_args" ]; then
0081 piped_args="${{line}}"
0082 else
0083 piped_args="${{piped_args}}\n${{line}}"
0084 fi
0085 done
0086 fi
0087
0088 ## Fire off the application wrapper
0089 if [ ${{piped_args}} ] ; then
0090 echo -e ${{piped_args}} | singularity exec {bind} {container} {exe} $@
0091 else
0092 singularity exec {bind} {container} {exe} $@
0093 fi
0094 '''
0095
0096 def _write_script(path, content):
0097 print(' - creating', path)
0098 with open(path, 'w') as file:
0099 file.write(content)
0100 os.system('chmod +x {}'.format(path))
0101
0102 def make_launcher(app, container, bindir,
0103 bind='', exe=None):
0104 '''Configure and install a launcher.
0105
0106 Generic launcher script to launch applications in this container.
0107
0108 The launcher script calls the desired executable from the singularity image.
0109 As the new images have the environment properly setup, we can accomplish this
0110 without using any wrapper scripts.
0111
0112 Arguments:
0113 - app: our application
0114 - container: absolute path to container
0115 - bindir: absolute launcher install path
0116 Optional:
0117 - bind: singularity bind directives
0118 - exe: executable to be associated with app.
0119 Default is app.
0120 - env: environment directives to be added to the wrapper.
0121 Multiline string. Default is nothing
0122 '''
0123 if not exe:
0124 exe = app
0125
0126
0127 launcher_path = '{}/{}'.format(bindir, app)
0128
0129
0130 launcher = _LAUNCHER.format(container=container,
0131 bind=bind,
0132 exe=exe)
0133
0134
0135 _write_script(launcher_path, launcher)
0136
0137
0138 _MODULEFILE='''#%Module1.0#####################################################################
0139 ##
0140 ## for {name} {version}
0141 ##
0142 proc ModulesHelp {{ }} {{
0143 puts stderr "This module sets up the environment for the {name} container"
0144 }}
0145 module-whatis "{name} {version}"
0146
0147 # For Tcl script use only
0148 set version 4.1.4
0149
0150 prepend-path PATH {bindir}
0151 '''
0152
0153 def make_modulefile(project, version, moduledir, bindir):
0154 '''Configure and install a modulefile for this project.
0155
0156 Arguments:
0157 - project: project name
0158 - version: project version
0159 - moduledir: root modulefile directory
0160 - bindir: where executables for this project are located
0161 '''
0162
0163
0164 content = _MODULEFILE.format(name=project, version=version, bindir=bindir)
0165 fname = '{}/{}'.format(moduledir, version)
0166 print(' - creating', fname)
0167 with open(fname, 'w') as file:
0168 file.write(content)
0169
0170 if __name__ == "__main__":
0171 parser = argparse.ArgumentParser()
0172 parser.add_argument(
0173 'prefix',
0174 help='Install prefix. This is where the container will be deployed.')
0175 parser.add_argument(
0176 '-c', '--container',
0177 dest='container',
0178 default=DEFAULT_IMG,
0179 help='(opt.) Container to install. '
0180 'D: {} (also available: eic_dev, and legacy "eic" container).')
0181 parser.add_argument(
0182 '-v', '--version',
0183 dest='version',
0184
0185 default=DEFAULT_VERSION,
0186 help='(opt.) project version. '
0187 'D: {}. For MRs, use mr-XXX.'.format(DEFAULT_VERSION))
0188 parser.add_argument(
0189 '-f', '--force',
0190 action='store_true',
0191 help='Force-overwrite already downloaded container',
0192 default=False)
0193 parser.add_argument(
0194 '-b', '--bind-path',
0195 dest='bind_paths',
0196 action='append',
0197 help='(opt.) extra bind paths for singularity.')
0198 parser.add_argument(
0199 '-m', '--module-path',
0200 dest='module_path',
0201 help='(opt.) Root module path to install a modulefile. '
0202 'D: Do not install a modulefile')
0203
0204 args = parser.parse_args()
0205
0206 print('Deploying', args.container, 'version', args.version)
0207
0208
0209 bind_directive = ''
0210 if args.bind_paths and len(args.bind_paths):
0211 print('Singularity bind paths:')
0212 for path in args.bind_paths:
0213 print(' -', path)
0214 if not os.path.exists(path):
0215 print('ERROR: path', path, 'does not exist.')
0216 raise InvalidArgumentError()
0217 bind_directive = ' '.join([BIND_DIRECTIVE.format(path) for path in args.bind_paths])
0218
0219
0220
0221
0222
0223
0224
0225
0226
0227
0228
0229
0230
0231
0232 version_docker = None
0233 version_gitlab = None
0234 build_job = '{}:singularity:default'.format(args.container)
0235
0236 if args.container == 'acts_material_scan':
0237 version_docker = args.version
0238 version_gitlab = 'acts-material-scan'
0239 elif args.version in ('master', 'testing'):
0240 version_docker = 'testing'
0241 version_gitlab = 'master'
0242 elif re.search('[0-9]+\.[0-9]', args.version) is not None:
0243 suffix='-stable'
0244 if re.search('{}$'.format(suffix), args.version):
0245 suffix=''
0246 version_docker = args.version + suffix
0247 version_gitlab = args.version + suffix
0248 if version_docker[0] == 'v':
0249 version_docker = version_docker[1:]
0250 if version_gitlab[0].isdigit():
0251 version_gitlab = 'v{}'.format(version_gitlab)
0252 elif args.version[:3] == 'mr-':
0253 version_docker = 'unstable'
0254 version_gitlab = 'refs/merge-requests/{}/head'.format(args.version[3:])
0255 elif args.version == 'nightly':
0256 version_docker = 'nightly'
0257 version_gitlab = 'master'
0258 build_job = '{}:singularity:nightly'.format(args.container)
0259 else:
0260
0261 print('Unknown requested version:', args.version)
0262 raise UnknownVersionError()
0263
0264
0265 if version_docker == 'master':
0266 version_docker = testing
0267
0268
0269 if args.container == 'eic':
0270 build_job = 'singularity'
0271
0272
0273 args.prefix = os.path.abspath(args.prefix)
0274 if not args.module_path:
0275 deploy_local=True
0276 else:
0277 deploy_local=False
0278 print('Install prefix:', args.prefix)
0279 print('Creating install prefix if needed...')
0280 bindir = '{}/bin'.format(args.prefix)
0281 libdir = '{}/lib'.format(args.prefix)
0282 libexecdir = '{}/libexec'.format(args.prefix)
0283 root_prefix = os.path.abspath('{}/..'.format(args.prefix))
0284 dirs = [bindir, libdir, libexecdir]
0285 if not deploy_local:
0286 moduledir = '{}/{}'.format(args.module_path, args.container)
0287 dirs.append(moduledir)
0288 for dir in dirs:
0289 print(' -', dir)
0290 smart_mkdir(dir)
0291
0292
0293
0294
0295
0296
0297 img = args.container
0298
0299
0300
0301 container = '{}/{}-{}.sif'.format(libdir, img, version_docker)
0302 if not os.path.exists(container) or args.force:
0303 url = CONTAINER_URL.format(ref=version_gitlab, img=img, job=build_job)
0304 print('Downloading container from:', url)
0305 print('Destination:', container)
0306 try:
0307 urllib.request.urlretrieve(url, container)
0308 except:
0309 print('WARNING: failed to retrieve container artifact')
0310 print('Attempting alternative download from docker registry')
0311 cmd = ['singularity pull', '--force', container, DOCKER_REF.format(img=img, tag=version_docker)]
0312 cmd = ' '.join(cmd)
0313 print('Executing:', cmd)
0314 err = os.system(cmd)
0315 if err:
0316 raise ContainerDownloadError()
0317 else:
0318 print('WARNING: Container found at', container)
0319 print(' ---> run with -f to force a re-download')
0320
0321 if not deploy_local:
0322 make_modulefile(args.container, version_docker, moduledir, bindir)
0323
0324
0325 print('Configuring applications launchers: ')
0326 for prog in SHORTCUTS:
0327 app = prog
0328 exe = prog
0329 if type(prog) == tuple:
0330 app = prog[0]
0331 exe = prog[1]
0332 make_launcher(app, container, bindir,
0333 bind=bind_directive,
0334 exe=exe)
0335
0336 print('Container deployment successful!')