def verify(config): """Verify the configuration contain the necessary details.""" errors = [] if 'build' in config: for error in verify_build(config['build']): errors.append(error) else: utils.debug('No "build" section found in configuration.') for error in verify_package(config): errors.append(error) for section in {'build', 'package'}: for local_path, container_path in config.get(section, dict()).get('volumes', dict()).items(): if not path.exists(local_path) and not create_local_directory(local_path): errors.append('The path "{}" specified as a {} volume does not exist on the local machine.'.format(local_path, section)) if not path.isabs(container_path): errors.append('The path "{}" specified as a {} volume has to be absolute.'.format(container_path, section)) if 'environment' in config and not isinstance(config['environment'], dict): errors.append('The environment variables should be described as a dictionary.') if errors: utils.log('The following errors were encountered while verifying the configuration:', err=True, fg='red', bold=True) for error in errors: utils.log(' - {}'.format(error), err=True, prefix=False) return False else: utils.info('The configuration has been verified without errors.') return True
def create_build_script(script): """Write the build script to a temporary file.""" utils.debug('Writing build script to temporary file.') with NamedTemporaryFile(prefix='berth-build-', suffix='.sh', delete=False, dir='.') as script_file: script_file.write(script.encode('utf-8')) script_path = script_file.name chmod(script_path, 493) # Add execute permissions: 493 = rwxr-xr-x utils.info('Wrote temporary build script to "{}".'.format(script_path)) return script_path
def create_local_directory(local_path): """Create a local directory for a specified path that doesn't exist.""" try: utils.debug('Creating a directory at the local path "{}".'.format(local_path)) mkdir(local_path) utils.info('Created directory at "{}" as the path did not exist.'.format(local_path)) return True except PermissionError: return False
def build(config): """Run the build part of the configuration.""" utils.log('Building project.') script_path = create_build_script(config['build']['script']) script_container_path = '/{}'.format(path.basename(script_path)) volumes = config['build'].get('volumes', dict()) volumes[script_path] = script_container_path binds = utils.convert_volumes_list(volumes) docker = utils.docker_client() container = docker.create_container( image=config['build']['image'], command=[script_container_path], environment={str(k): str(v) for k, v in config.get('environment', dict()).items()}, host_config=docker.create_host_config(binds=binds) ) if container['Warnings']: utils.log('We got a warning when creating the build container:', err=True, fg='red', bold=True) utils.log(str(container['Warnings']), err=True, prefix=False) remove_build_script(script_path) return False utils.debug('Starting build container.') start_time = time.time() docker.start(container['Id']) utils.info('Build container started.') if utils.get_log_level() > 0: for line in docker.logs(container['Id'], stream=True): utils.log(line.decode('utf-8').rstrip(), prefix=False) exit_code = docker.wait(container['Id']) utils.info('The build container has stopped after {:.1f} seconds.'.format(time.time() - start_time)) if exit_code == 0: utils.log('Build succeeded.') else: if utils.get_log_level() > 0: utils.log('Build script failed with exit code: {}.'.format(exit_code), err=True, fg='red', bold=True) else: utils.log('Build script failed with exit code: {}. Output follows:'.format(exit_code), err=True, fg='red', bold=True) utils.log(docker.logs(container['Id']).decode('utf-8').rstrip(), err=True, prefix=False) if config['keep_containers']: utils.info('Keeping build container (ID: {}).'.format(container['Id'][:12])) else: utils.debug('Removing build container.') docker.remove_container(container['Id']) utils.info('Removed build container.') remove_build_script(script_path) return exit_code == 0
def build(config): """Run the build part of the configuration.""" utils.log('Building project.') script_path = create_build_script(config['build']['script']) script_container_path = '/{}'.format(path.basename(script_path)) volumes = config['build'].get('volumes', dict()) volumes[script_path] = script_container_path volume_list, binds = utils.convert_volumes_list(volumes) docker = utils.docker_client() container = docker.create_container( image=config['build']['image'], command=[script_container_path], volumes=volume_list, environment={str(k): str(v) for k, v in config.get('environment', dict()).items()}, ) if container['Warnings']: utils.log('We got a warning when creating the build container:', err=True, fg='red', bold=True) utils.log(str(container['Warnings']), err=True, prefix=False) remove_build_script(script_path) return False utils.debug('Starting build container.') start_time = time.time() docker.start(container['Id'], binds=binds) utils.info('Build container started.') if utils.get_log_level() > 0: for line in docker.logs(container['Id'], stream=True): utils.log(line.decode('utf-8').rstrip(), prefix=False) exit_code = docker.wait(container['Id']) utils.info('The build container has stopped after {:.1f} seconds.'.format(time.time() - start_time)) if exit_code == 0: utils.log('Build succeeded.') else: if utils.get_log_level() > 0: utils.log('Build script failed with exit code: {}.'.format(exit_code), err=True, fg='red', bold=True) else: utils.log('Build script failed with exit code: {}. Output follows:'.format(exit_code), err=True, fg='red', bold=True) utils.log(docker.logs(container['Id']).decode('utf-8').rstrip(), err=True, prefix=False) if config['keep_containers']: utils.info('Keeping build container (ID: {}).'.format(container['Id'][:12])) else: utils.debug('Removing build container.') docker.remove_container(container['Id']) utils.info('Removed build container.') remove_build_script(script_path) return exit_code == 0
def create_local_directory(local_path): """Create a local directory for a specified path that doesn't exist.""" try: utils.debug( 'Creating a directory at the local path "{}".'.format(local_path)) mkdir(local_path) utils.info( 'Created directory at "{}" as the path did not exist.'.format( local_path)) return True except PermissionError: return False
def replace_envvars(config, lines): """Replace environment variables in FPM command parameters.""" environment_variables = config.get('environment', dict()) if not environment_variables: utils.debug('No environment variables has been specified for the packaging. No replacements are being performed.') for line in lines: yield line else: utils.debug('The following environment variables has been provided: {}'.format(str(environment_variables))) os.environ.update({str(k): str(v) for k, v in environment_variables.items()}) for line in lines: yield os.path.expandvars(line)
def read(file_pointer): """Read the configuration file and parse the YAML contents.""" utils.debug('Reading the configuration file.') try: config = yaml.load(file_pointer.read()) except yaml.parser.ParserError as error: utils.log('The configuration file could not be parsed:', err=True, fg='red', bold=True) utils.log(error.context, err=True, prefix=False) utils.log(str(error.context_mark), err=True, prefix=False) utils.log(error.problem, err=True, prefix=False) utils.log(str(error.problem_mark), err=True, prefix=False) return False utils.info('The configuration file "{}" has been parsed.'.format(file_pointer.name)) utils.debug('Parsed configuration:\n{}'.format(config)) return config
def verify(config): """Verify the configuration contain the necessary details.""" errors = [] if 'build' in config: for error in verify_build(config['build']): errors.append(error) else: utils.debug('No "build" section found in configuration.') for error in verify_package(config): errors.append(error) for section in {'build', 'package'}: for local_path, container_path in config.get(section, dict()).get( 'volumes', dict()).items(): if not path.exists(local_path) and not create_local_directory( local_path): errors.append( 'The path "{}" specified as a {} volume does not exist on the local machine.' .format(local_path, section)) if not path.isabs(container_path): errors.append( 'The path "{}" specified as a {} volume has to be absolute.' .format(container_path, section)) if 'environment' in config and not isinstance(config['environment'], dict): errors.append( 'The environment variables should be described as a dictionary.') if errors: utils.log( 'The following errors were encountered while verifying the configuration:', err=True, fg='red', bold=True) for error in errors: utils.log(' - {}'.format(error), err=True, prefix=False) return False else: utils.info('The configuration has been verified without errors.') return True
def read(file_pointer): """Read the configuration file and parse the YAML contents.""" utils.debug('Reading the configuration file.') try: config = yaml.load(file_pointer.read()) except yaml.parser.ParserError as error: utils.log('The configuration file could not be parsed:', err=True, fg='red', bold=True) utils.log(error.context, err=True, prefix=False) utils.log(str(error.context_mark), err=True, prefix=False) utils.log(error.problem, err=True, prefix=False) utils.log(str(error.problem_mark), err=True, prefix=False) return False utils.info('The configuration file "{}" has been parsed.'.format( file_pointer.name)) utils.debug('Parsed configuration:\n{}'.format(config)) return config
def replace_envvars(config, lines): """Replace environment variables in FPM command parameters.""" environment_variables = config.get('environment', dict()) if not environment_variables: utils.debug( 'No environment variables has been specified for the packaging. No replacements are being performed.' ) for line in lines: yield line else: utils.debug( 'The following environment variables has been provided: {}'.format( str(environment_variables))) os.environ.update( {str(k): str(v) for k, v in environment_variables.items()}) for line in lines: yield os.path.expandvars(line)
def package(config): """Package a project of some kind to a package of some kind.""" utils.log('Packaging project.') volumes = config['package'].get('volumes', dict()) volume_list, binds = utils.convert_volumes_list(volumes) command = list(replace_envvars(config, map_to_fpm(config['package']['fpm']))) utils.debug('Command to be run in packaging container is: {}'.format(str(command))) docker = utils.docker_client() container = docker.create_container( image=config['package'].get('image', 'tenzer/fpm'), command=command, volumes=volume_list, ) if container['Warnings']: utils.log('We got a warning when creating the packaging container:', err=True, fg='red', bold=True) utils.log(str(container['Warnings']), err=True, prefix=False) return False utils.debug('Starting packaging container.') start_time = time.time() docker.start(container['Id'], binds=binds) utils.info('Packaging container started.') if utils.get_log_level() > 0: for line in docker.logs(container['Id'], stream=True): utils.log(line.decode('utf-8').rstrip(), prefix=False) exit_code = docker.wait(container['Id']) utils.info('The packaging container has stopped after {:.1f} seconds.'.format(time.time() - start_time)) if exit_code == 0: utils.log('Packaging succeeded.') else: if utils.get_log_level() > 0: utils.log('Packaging command failed with exit code: {}.'.format(exit_code), err=True, fg='red', bold=True) else: utils.log('Packaging command failed with exit code: {}. Output follows:'.format(exit_code), err=True, fg='red', bold=True) utils.log(docker.logs(container['Id']).decode('utf-8').rstrip(), err=True, prefix=False) if config['keep_containers']: utils.info('Keeping packaging container (ID: {}).'.format(container['Id'][:12])) else: utils.debug('Removing packaging container.') docker.remove_container(container['Id']) utils.info('Removed packaging container.') return exit_code == 0
def package(config): """Package a project of some kind to a package of some kind.""" utils.log('Packaging project.') volumes = config['package'].get('volumes', dict()) binds = utils.convert_volumes_list(volumes) command = list( replace_envvars(config, map_to_fpm(config['package']['fpm']))) utils.debug('Command to be run in packaging container is: {}'.format( str(command))) docker = utils.docker_client() container = docker.create_container( image=config['package'].get('image', 'tenzer/fpm'), command=command, host_config=docker.create_host_config(binds=binds)) if container['Warnings']: utils.log('We got a warning when creating the packaging container:', err=True, fg='red', bold=True) utils.log(str(container['Warnings']), err=True, prefix=False) return False utils.debug('Starting packaging container.') start_time = time.time() docker.start(container['Id']) utils.info('Packaging container started.') if utils.get_log_level() > 0: for line in docker.logs(container['Id'], stream=True): utils.log(line.decode('utf-8').rstrip(), prefix=False) exit_code = docker.wait(container['Id']) utils.info( 'The packaging container has stopped after {:.1f} seconds.'.format( time.time() - start_time)) if exit_code == 0: utils.log('Packaging succeeded.') else: if utils.get_log_level() > 0: utils.log('Packaging command failed with exit code: {}.'.format( exit_code), err=True, fg='red', bold=True) else: utils.log( 'Packaging command failed with exit code: {}. Output follows:'. format(exit_code), err=True, fg='red', bold=True) utils.log(docker.logs(container['Id']).decode('utf-8').rstrip(), err=True, prefix=False) if config['keep_containers']: utils.info('Keeping packaging container (ID: {}).'.format( container['Id'][:12])) else: utils.debug('Removing packaging container.') docker.remove_container(container['Id']) utils.info('Removed packaging container.') return exit_code == 0
def remove_build_script(script_path): """Remove the temporary build script file.""" utils.debug('Removing temporary build script at "{}".'.format(script_path)) unlink(script_path) utils.info('Removed temporary build script.')