def create_resource(descriptor, **kwargs): """ Module method responsible for instantiating proper resource object based on the provided descriptor. In most cases, only descriptor is required to create the object from descriptor. In case additional data is required, the kwargs 'dictionary' will be checked if the required key exists and will be used in the object constructor. """ if 'image' in descriptor: return _ImageContentResource(descriptor) if 'path' in descriptor: directory = kwargs.pop('directory') if not directory: raise CekitError(( "Internal error: cannot instantiate PathResource: {}, directory was not provided, " + "please report it: https://github.com/cekit/cekit/issues" ).format(descriptor)) return _PathResource(descriptor, directory) if 'url' in descriptor: return _UrlResource(descriptor) if 'git' in descriptor: return _GitResource(descriptor) if 'md5' in descriptor: return _PlainResource(descriptor) raise CekitError("Resource '{}' is not supported".format(descriptor))
def add(self, artifact): if not set(SUPPORTED_HASH_ALGORITHMS).intersection(artifact): raise ValueError('Cannot cache Artifact without checksum') if self.is_cached(artifact): raise CekitError('Artifact is already cached') artifact_id = str(uuid.uuid4()) artifact_file = os.path.expanduser( os.path.join(self._cache_dir, artifact_id)) if not os.path.exists(artifact_file): artifact.guarded_copy(artifact_file) cache_entry = { 'names': [artifact['name']], 'cached_path': artifact_file } for alg in SUPPORTED_HASH_ALGORITHMS: if alg in artifact: if not check_sum(artifact_file, alg, artifact[alg]): raise CekitError('Artifact contains invalid checksum!') chksum = artifact[alg] else: chksum = get_sum(artifact_file, alg) cache_entry.update({alg: chksum}) self._update_cache(cache_entry, artifact_id) return artifact_id
def _get_repo_url(self, descriptor): """Retruns repository url from Cekit config files repositories section""" configured_repositories = config.get('repositories') # We need to remove the custom "__name__" element before we can show # which repository keys are defined in the configuration configured_repository_names = configured_repositories.keys() if '__name__' in configured_repository_names: configured_repository_names.remove('__name__') if descriptor['name'] not in configured_repositories: if configured_repository_names: raise CekitError( "Package repository '%s' used in descriptor is not " "available in Cekit configuration file. " "Available repositories: %s" % (descriptor['name'], ' '.join(configured_repository_names))) else: raise CekitError( "Package repository '%s' used in descriptor is not " "available in Cekit configuration file. " % descriptor['name']) return configured_repositories[descriptor['name']]
def _copy_impl(self, target): if not os.path.exists(self.path): cache = config.get('common', 'cache_url') # If cache_url is specified in Cekit configuration # file - try to fetch the 'path' artifact from cacher # even if it was not defined as 'url'. if cache: try: self._download_file(self.path, target) return target except Exception as ex: logger.exception(ex) raise CekitError( "Could not download resource '%s' from cache" % self.name) else: raise CekitError("Could not copy resource '%s', " "source path does not exist. " "Make sure you provided correct path" % self.name) logger.debug("Copying repository from '%s' to '%s'." % (self.path, target)) if os.path.isdir(self.path): shutil.copytree(self.path, target) else: shutil.copy(self.path, target) return target
def copy_module_to_target(name, version, target): """Copies a module from args.target/repo/... directory into args.target/image/modules/... and update module path to be the new location Arguments: name - name of the module to lookup in modules list version - version of the module to used target - directory where module will be copied Returns instance of copied module. """ if not os.path.exists(target): os.makedirs(target) # FIXME: version checking candidates = [m for m in modules if name == m.name] if not candidates: raise CekitError("Cannot find requested module: '%s'" % name) for module in candidates: if not version or version == module.get('version', None): dest = os.path.join(target, module.name) # if module is already copied, check that the version is correct if os.path.exists(dest) and version: check_module_version(dest, version) if not os.path.exists(dest): logger.debug("Copying module '%s' to: '%s'" % (name, dest)) shutil.copytree(module.path, dest) return module raise CekitError("Cannot find requested module: '%s', version:'%s'." % (name, version))
def __init__(self, descriptor, descriptor_path): self.schema = packages_schema self.descriptor_path = descriptor_path super(Packages, self).__init__(descriptor) # If 'content_sets' and 'content_sets_file' are defined at the same time if set(['content_sets', 'content_sets_file']).issubset(set(descriptor.keys())): raise CekitError( "You cannot specify 'content_sets' and 'content_sets_file' together in the packages section!" ) # If the 'content_sets_file' key is set and is not None we need to read the specified # file and make it available in the 'content_sets' key. The 'content_sets_file' key is removed # afterwards. if descriptor.get('content_sets_file', None): content_sets_file = os.path.join(self.descriptor_path, descriptor['content_sets_file']) if not os.path.exists(content_sets_file): raise CekitError("'%s' file not found!" % content_sets_file) with open(content_sets_file, 'r') as file_: descriptor['content_sets'] = yaml.safe_load(file_) del descriptor['content_sets_file'] self._prepare()
def get_brew_url(md5): try: LOGGER.debug("Getting brew details for an artifact with '%s' md5 sum" % md5) list_archives_cmd = [ '/usr/bin/brew', 'call', '--json-output', 'listArchives', 'checksum=%s' % md5, 'type=maven' ] LOGGER.debug("Executing '%s'." % " ".join(list_archives_cmd)) archive_yaml = yaml.safe_load( subprocess.check_output(list_archives_cmd)) if not archive_yaml: raise CekitError( "Artifact with md5 checksum %s could not be found in Brew" % md5) archive = archive_yaml[0] build_id = archive['build_id'] filename = archive['filename'] group_id = archive['group_id'] artifact_id = archive['artifact_id'] version = archive['version'] get_build_cmd = [ 'brew', 'call', '--json-output', 'getBuild', 'buildInfo=%s' % build_id ] LOGGER.debug("Executing '%s'" % " ".join(get_build_cmd)) build = yaml.safe_load(subprocess.check_output(get_build_cmd)) build_states = [ 'BUILDING', 'COMPLETE', 'DELETED', 'FAILED', 'CANCELED' ] # State 1 means: COMPLETE which is the only success state. Other states are: # # 'BUILDING': 0 # 'COMPLETE': 1 # 'DELETED': 2 # 'FAILED': 3 # 'CANCELED': 4 if build['state'] != 1: raise CekitError( "Artifact with checksum {} was found in Koji metadata but the build is in incorrect state ({}) making " "the artifact not available for downloading anymore".format( md5, build_states[build['state']])) package = build['package_name'] release = build['release'] url = 'http://download.devel.redhat.com/brewroot/packages/' + package + '/' + \ version.replace('-', '_') + '/' + release + '/maven/' + \ group_id.replace('.', '/') + '/' + \ artifact_id.replace('.', '/') + '/' + version + '/' + filename except subprocess.CalledProcessError as ex: LOGGER.error("Can't fetch artifacts details from brew: '%s'." % ex.output) raise ex return url
def load_descriptor(descriptor): """ parses descriptor and validate it against requested schema type Args: descriptor - yaml descriptor or path to a descriptor to be loaded. If a path is provided it must be an absolute path. In other case it's assumed that it is a yaml descriptor. Returns descriptor as a dictionary """ try: data = yaml.safe_load(descriptor) except Exception as ex: raise CekitError('Cannot load descriptor', ex) if isinstance(data, basestring): LOGGER.debug("Reading descriptor from '{}' file...".format(descriptor)) if os.path.exists(descriptor): with open(descriptor, 'r') as fh: return yaml.safe_load(fh) raise CekitError( "Descriptor could not be found on the '{}' path, please check your arguments!" .format(descriptor)) LOGGER.debug("Reading descriptor directly...") return data
def build(self, build_args=None): """After the source files are generated, the container image can be built. We're using Docker to build the image currently. """ args = {} args['path'] = os.path.join(self.target, 'image') args['tag'] = self._tags[0] args['pull'] = self._pull # Custom tags for the container image logger.debug("Building image with tags: '%s'" % "', '".join(self._tags)) logger.info("Building container image...") try: last_tag = "" out = docker_client.build(**args) lastmsg = "" for line in out: if b'stream' in line: line = yaml.safe_load(line)['stream'] elif b'status' in line: line = yaml.safe_load(line)['status'] elif b'errorDetail' in line: line = yaml.safe_load(line)['errorDetail']['message'] raise CekitError("Image build failed: '%s'" % line) if '---> Running in ' in line: last_tag = line.split(' ')[-1] if line != lastmsg: # this prevents poluting cekit log with dowloading/extracting msgs log_msg = ANSI_ESCAPE.sub('', line).strip() for msg in log_msg.split('\n'): logger.info('Docker: %s' % msg) lastmsg = line for tag in self._tags[1:]: if ':' in tag: img_repo, img_tag = tag.split(":") docker_client.tag(self._tags[0], img_repo, tag=img_tag) else: docker_client.tag(self._tags[0], tag) logger.info("Image built and available under following tags: %s" % ", ".join(self._tags)) except Exception as ex: if last_tag: failed_img = self._tags[0] + '-failed' if ':' in failed_img: img_repo, img_tag = failed_img.split(":") docker_client.commit(last_tag, img_repo, tag=img_tag) else: docker_client.commit(last_tag, failed_img) logger.error("You can look inside the failed image by running " "'docker run --rm -ti %s bash'" % failed_img) raise CekitError("Image build failed, see logs above.", ex)
def check_prerequisities(self): try: subprocess.check_output(['sudo', 'buildah', 'version'], stderr=subprocess.STDOUT) except subprocess.CalledProcessError as ex: raise CekitError("Buildah build engine needs buildah" " installed and configured, error: %s" % ex.output) except Exception as ex: raise CekitError("Buildah build engine needs buildah installed and configured!", ex)
def get_module(self, name, version=None, suppress_warnings=False): """ Returns the module available in registry based on the name and version requested. If no modules are found for the requested name, an error is thrown. If version requirement could not be satisfied, an error is thrown too. If there is a version mismatch, default version is returned. See 'add_module' for more information how default versions are defined. Args: name (str): module name version (float or str): module version Returns: Module object. Raises: CekitError: If a module is not found or version requirement is not satisfied """ # Get all modules for specied nam modules = self._modules.get(name, {}) # If there are no modules with the requested name, fail if not modules: raise CekitError( "There are no modules with '{}' name available".format(name)) # If there is no module version requested, get default one if version is None: default_version = self._defaults.get(name) if not default_version: raise CekitError( "Internal error: default version for module '{}' could not be found, please report it" .format(name)) default_module = self.get_module(name, default_version) if not suppress_warnings and len(modules) > 1: LOGGER.warning( "Module version not specified for '{}' module, using '{}' default version" .format(name, default_version)) return default_module # Finally, get the module for specified version module = modules.get(version) # If there is no such module, fail if not module: raise CekitError( "Module '{}' with version '{}' could not be found, available versions: {}" .format(name, version, ", ".join(list(modules.keys())))) return module
def _download_file(self, url, destination, use_cache=True): """ Downloads a file from url and save it as destination """ if use_cache: url = self.__substitute_cache_url(url) if not url: raise CekitError( "Artifact %s cannot be downloaded, no URL provided" % self.name) logger.debug("Downloading from '%s' as %s" % (url, destination)) parsedUrl = urlparse(url) if parsedUrl.scheme == 'file' or not parsedUrl.scheme: if os.path.isdir(parsedUrl.path): shutil.copytree(parsedUrl.path, destination) else: shutil.copy(parsedUrl.path, destination) elif parsedUrl.scheme in ['http', 'https']: verify = config.get('common', 'ssl_verify') if str(verify).lower() == 'false': verify = False ctx = ssl.create_default_context() if not verify: ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE res = urlopen(url, context=ctx) if res.getcode() != 200: raise CekitError("Could not download file from %s" % url) try: with open(destination, 'wb') as f: while True: chunk = res.read(1048576) # 1 MB if not chunk: break f.write(chunk) except Exception: try: logger.debug( "Removing incompletely downloaded '{}' file".format( destination)) os.remove(destination) except OSError: logger.warning( "An error occurred while removing file '{}'".format( destination)) raise else: raise CekitError("Unsupported URL scheme: %s" % (url))
def _prepare_content_sets(self, content_sets): if not content_sets: return False arch = platform.machine() if arch not in content_sets: raise CekitError("There are no content_sets defined for platform '{}'!".format(arch)) repos = ' '.join(content_sets[arch]) try: # ideally this will be API for ODCS, but there is no python3 package for ODCS cmd = ['/usr/bin/odcs'] odcs_service_type = "Fedora" if CONFIG.get('common', 'redhat'): odcs_service_type = "Red Hat" cmd.append('--redhat') LOGGER.info("Using {} ODCS service to created composes".format(odcs_service_type)) cmd.append('create') compose = self.image.get('osbs', {}).get( 'configuration', {}).get('container', {}).get('compose', {}) if compose.get(Generator.ODCS_HIDDEN_REPOS_FLAG, False): cmd.extend(['--flag', Generator.ODCS_HIDDEN_REPOS_FLAG]) cmd.extend(['pulp', repos]) LOGGER.debug("Creating ODCS content set via '%s'" % " ".join(cmd)) output = subprocess.check_output(cmd).decode() normalized_output = '\n'.join(output.replace(" u'", " '") .replace(' u"', ' "') .split('\n')[1:]) odcs_result = yaml.safe_load(normalized_output) if odcs_result['state'] != 2: raise CekitError("Cannot create content set: '%s'" % odcs_result['state_reason']) repo_url = odcs_result['result_repofile'] return repo_url except CekitError as ex: raise ex except OSError as ex: raise CekitError("ODCS is not installed, please install 'odcs-client' package") except subprocess.CalledProcessError as ex: raise CekitError("Cannot create content set: '%s'" % ex.output) except Exception as ex: raise CekitError('Cannot create content set!', ex)
def check_prerequisities(self): try: subprocess.check_output( [self._rhpkg, 'help'], stderr=subprocess.STDOUT) except subprocess.CalledProcessError as ex: raise CekitError("OSBS build engine needs 'rhpkg' tools installed, error: %s" % ex.output) except Exception as ex: raise CekitError( "OSBS build engine needs '%s' tools installed!" % self._rhpkg, ex)
def _docker_client(self): LOGGER.debug("Preparing Docker client...") # Default Docker daemon connection timeout 10 minutes # It needs to be high enough to allow Docker daemon to export the # image for squashing. try: timeout = int(os.getenv('DOCKER_TIMEOUT', '600')) except ValueError: raise CekitError( "Provided timeout value: '{}' cannot be parsed as integer, exiting." .format(os.getenv('DOCKER_TIMEOUT'))) if timeout <= 0: raise CekitError( "Provided timeout value needs to be greater than zero, currently: '{}', exiting." .format(timeout)) params = {"version": "1.22"} params.update(docker.utils.kwargs_from_env()) params["timeout"] = timeout try: client = APIClientClass(**params) except docker.errors.DockerException as e: LOGGER.error( "Could not create Docker client, please make sure that you " "specified valid parameters in the 'DOCKER_HOST' environment variable, " "examples: 'unix:///var/run/docker.sock', 'tcp://192.168.22.33:1234'" ) raise CekitError("Error while creating the Docker client", e) if client and self._valid_docker_connection(client): LOGGER.debug("Docker client ready and working") LOGGER.debug(client.version()) return client LOGGER.error( "Could not connect to the Docker daemon at '{}', please make sure the Docker " "daemon is running.".format(client.base_url)) if client.base_url.startswith('unix'): LOGGER.error( "Please make sure the Docker socket has correct permissions.") if os.environ.get('DOCKER_HOST'): LOGGER.error( "If Docker daemon is running, please make sure that you specified valid " "parameters in the 'DOCKER_HOST' environment variable, examples: " "'unix:///var/run/docker.sock', 'tcp://192.168.22.33:1234'. You may " "also need to specify 'DOCKER_TLS_VERIFY', and 'DOCKER_CERT_PATH' " "environment variables.") raise CekitError("Cannot connect to Docker daemon")
def __init__(self, target): """Check if behave and docker is installed properly""" self.target = os.path.abspath(target) try: subprocess.check_output(['behave', '--version'], stderr=subprocess.STDOUT) except subprocess.CalledProcessError as ex: raise CekitError("Test Runner needs 'behave' installed, '%s'" % ex.output) except Exception as ex: raise CekitError( "Test Runner needs behave installed!", ex)
def check_prerequisities(self): try: subprocess.check_output(['docker', 'info'], stderr=subprocess.STDOUT) except subprocess.CalledProcessError as ex: raise CekitError( "Docker build engine needs docker installed and configured, error: %s" % ex.output) except Exception as ex: raise CekitError( "Docker build engine needs docker installed and configured!", ex)
def __init__(self, target): """Check if behave and docker is installed properly""" self.target = os.path.abspath(target) try: # check that we have behave installed from behave.__main__ import main as behave_main except subprocess.CalledProcessError as ex: raise CekitError("Test Runner needs 'behave' installed, '%s'" % ex.output) except Exception as ex: raise CekitError( "Test Runner needs behave installed!", ex)
def _prepare_configuration(self): if 'container' in self and 'container_file' in self: raise CekitError( 'You cannot specify container and container_file together!') if 'container_file' in self: if not os.path.exists(self['container_file']): raise CekitError("'%s' file not found!" % self['container_file']) with open(self['container_file'], 'r') as file_: self['container'] = yaml.safe_load(file_) del self['container_file']
def _wait_for_osbs_task(self, task_id, current_time=0, timeout=7200): """ Default timeout is 2hrs """ LOGGER.debug("Checking if task {} is finished...".format(task_id)) # Time between subsequent querying the API sleep_time = 20 if current_time > timeout: raise CekitError( "Timed out while waiting for the task {} to finish, please check the task logs!" .format(task_id)) # Definition of task states states = { 'free': 0, 'open': 1, 'closed': 2, 'cancelled': 3, 'assigned': 4, 'failed': 5 } # Get information about the task try: json_info = subprocess.check_output( [self._koji, "call", "--json-output", "getTaskInfo", task_id]).strip().decode("utf8") except subprocess.CalledProcessError as ex: raise CekitError( "Could not check the task {} result".format(task_id), ex) # Parse the returned JSON info = json.loads(json_info) # Task is closed which means that it was successfully finished if info['state'] == states['closed']: return True # Task is in progress if info['state'] == states['free'] or info['state'] == states[ 'open'] or info['state'] == states['assigned']: # It's not necessary to query the API so often time.sleep(sleep_time) return self._wait_for_osbs_task(task_id, current_time + sleep_time, timeout) # In all other cases (failed, cancelled) task did not finish successfully raise CekitError( "Task {} did not finish successfully, please check the task logs!". format(task_id))
def _validate_steps_requirements(self): logger.debug("Validating steps requirements...") req_docker = self._requirement_available('docker', True) req_docker_py = self._requirement_available('docker-py', True) if not (req_docker or req_docker_py): self._suggest_package('docker') raise CekitError( "Could not find Docker client library, see logs above") for req in ['behave', 'requests']: if not self._requirement_available(req): raise CekitError("Handling of test steps requirements " "failed, see log for more info.")
def _copy_impl(self, target): # First of all try to download the file using cacher if specified if config.get('common', 'cache_url'): try: self._download_file(None, target) return target except Exception as e: logger.debug(str(e)) logger.warning( "Could not download '{}' artifact using cacher".format( self.name)) md5 = self.get('md5') # Next option is to download it from Brew directly but only if the md5 checkum # is provided and we are running with the --redhat switch if md5 and config.get('common', 'redhat'): logger.debug( "Trying to download artifact '{}' from Brew directly".format( self.name)) try: # Generate the URL url = get_brew_url(md5) # Use the URL to download the file self._download_file(url, target, use_cache=False) return target except Exception as e: logger.debug(str(e)) logger.warning( "Could not download artifact '{}' from Brew".format( self.name)) raise CekitError("Artifact {} could not be found".format(self.name))
def add(self, artifact): if not set(SUPPORTED_HASH_ALGORITHMS).intersection(artifact): raise ValueError('Cannot cache artifact without checksum') if self.cached(artifact): raise CekitError('Artifact is already cached!') artifact_id = str(uuid.uuid4()) artifact_file = os.path.expanduser( os.path.join(self.cache_dir, artifact_id)) if not os.path.exists(artifact_file): artifact.guarded_copy(artifact_file) cache_entry = { 'names': [artifact['name']], 'cached_path': artifact_file } # We should populate the cache entry with checksums for all supported algorithms for alg in SUPPORTED_HASH_ALGORITHMS: cache_entry.update({alg: get_sum(artifact_file, alg)}) self._update_cache(cache_entry, artifact_id) return artifact_id
def run(self): """Build container image using buildah.""" tags = self.params.tags cmd = ["/usr/bin/buildah", "build-using-dockerfile"] if not tags: tags = self.generator.get_tags() if self.params.pull: cmd.append('--pull-always') # Custom tags for the container image LOGGER.debug("Building image with tags: '%s'" % "', '".join(tags)) for tag in tags: cmd.extend(["-t", tag]) LOGGER.info("Building container image...") cmd.append(os.path.join(self.target, 'image')) LOGGER.debug("Running Buildah build: '%s'" % " ".join(cmd)) try: subprocess.check_call(cmd) LOGGER.info("Image built and available under following tags: %s" % ", ".join(tags)) except: raise CekitError("Image build failed, see logs above.")
def check_module_version(path, version): descriptor = Module( tools.load_descriptor(os.path.join(path, 'module.yaml')), path, os.path.dirname(os.path.abspath(os.path.join(path, 'module.yaml')))) if hasattr(descriptor, 'version') and descriptor.version != version: raise CekitError("Requested conflicting version '%s' of module '%s'" % (version, descriptor['name']))
def run(self, image, run_tags): """Run test suite""" cmd = ['behave', '--junit', '--junit-directory', 'results', '-t', '~ignore', '--no-skipped', '-D', 'IMAGE=%s' % image] for tag in run_tags: if ':' in tag: test_tag = tag.split(':')[0] cmd.append('-t') if '/' in tag: cmd.append("@%s,@%s" % (test_tag.split('/')[0], test_tag)) else: cmd.append(tag) # Check if we're running runtests on CI or locally # If we run tests locally - skip all features that # are marked with the @ci annotation if getpass.getuser() != "jenkins": cmd.append("-t") cmd.append("~ci ") logger.debug("Running '%s'" % ' '.join(cmd)) try: subprocess.check_call(cmd, stderr=subprocess.STDOUT, cwd=os.path.join(self.target, 'test')) except: raise CekitError("Test execution failed, please consult output above")
def _find_artifact(self, alg, chksum): cache = self._get_cache() for _, artifact in cache.items(): if alg in artifact and artifact[alg] == chksum: return artifact raise CekitError('Artifact is not cached.')
def __init__(self, descriptor): self.schemas = configuration_schema super(Configuration, self).__init__(descriptor) self.skip_merging = ['container', 'container_file'] if 'container' in self and 'container_file' in self: raise CekitError( 'You cannot specify container and container_file together!') if 'container_file' in self: if not os.path.exists(self['container_file']): raise CekitError("'%s' file not found!" % self['container_file']) with open(self['container_file'], 'r') as file_: self['container'] = yaml.safe_load(file_) del self['container_file']
def build(self): """After the source siles are generated, the container image can be built. We're using Docker to build the image currently. """ tags = self._tags cmd = ["docker", "build"] if self._pull: cmd.append('--pull') # Custom tags for the container image logger.debug("Building image with tags: '%s'" % "', '".join(tags)) for tag in tags: cmd.extend(["-t", tag]) logger.info("Building container image...") cmd.append(os.path.join(self.target, 'image')) logger.debug("Running Docker build: '%s'" % " ".join(cmd)) try: subprocess.check_call(cmd) logger.info("Image built and available under following tags: %s" % ", ".join(tags)) except: raise CekitError("Image build failed, see logs above.")
def process_install_list(self, source, to_install_list, install_list, module_registry): module_overrides = self._image_overrides['modules'] artifact_overrides = self._image_overrides['artifacts'] for to_install in to_install_list: logger.debug("Preparing module '{}' required by '{}'.".format( to_install.name, source.name)) override = module_overrides.get(to_install.name, None) if override: if override.version != to_install.version: logger.debug("Module '{}:{}' being overridden with '{}:{}'.".format (to_install.name, to_install.version, override.name, override.version)) # apply module override to_install = override existing = install_list.get(to_install.name, None) # see if we've already processed this if existing: # check for a version conflict if existing.version != to_install.version: logger.warning("Module version inconsistency for {}: {} requested, but {} will be used.".format( to_install.name, to_install.version, existing.version)) continue module = module_registry.get_module(to_install.name, to_install.version) if not module: raise CekitError("Could not locate module %s version %s. Please verify that it is included in one of the " "specified module repositories." % (to_install.name, to_install.version)) # collect artifacts and apply overrides module_artifacts = Image._to_dict(module.artifacts) for artifact in module.artifacts: name = artifact.name if name in artifact_overrides: override = artifact_overrides[name] self._all_artifacts[name] = override module_artifacts[name] = override else: self._all_artifacts[name] = artifact module._descriptor['artifacts'] = list(module_artifacts.values()) # collect package repositories for repo in module.packages.repositories: name = repo.name if not name in self._package_repositories: self._package_repositories[name] = repo # incorporate run specification contributed by module if module.run: # we're looping in order of install, so we want the current module to override whatever we have self._module_run = module.run.merge(self._module_run) # prevent circular dependencies. we'll move it to the end after processing install_list[to_install.name] = to_install # process this modules dependencies self.process_install_list(module, module.modules.install, install_list, module_registry) # move this module to the end of the list. install_list.pop(to_install.name) install_list[to_install.name] = to_install