Exemplo n.º 1
0
Arquivo: cli.py Projeto: rnc/cekit
    def add(self, location, md5, sha1, sha256, sha512):
        artifact_cache = ArtifactCache()

        resource = {}
        resource['url'] = location

        if md5:
            resource['md5'] = md5

        if sha1:
            resource['sha1'] = sha1

        if sha256:
            resource['sha256'] = sha256

        if sha512:
            resource['sha512'] = sha512

        artifact = create_resource(resource)

        cached = artifact_cache.cached(artifact)

        if cached:
            click.echo("Artifact {} is already cached!".format(location))
            sys.exit(0)

        try:
            artifact_id = artifact_cache.add(artifact)
            click.echo("Artifact {} cached with UUID '{}'".format(
                location, artifact_id))
        except Exception as ex:  # pylint: disable=broad-except
            click.secho("Cannot cache artifact {}: {}".format(
                location, str(ex)),
                        fg='red')
            sys.exit(1)
Exemplo n.º 2
0
class Resource(Descriptor):
    CHECK_INTEGRITY = True

    def __new__(cls, resource, **kwargs):
        if cls is Resource:
            if 'path' in resource:
                return super(Resource, cls).__new__(_PathResource)
            elif 'url' in resource:
                return super(Resource, cls).__new__(_UrlResource)
            elif 'git' in resource:
                return super(Resource, cls).__new__(_GitResource)
            elif 'md5' in resource:
                return super(Resource, cls).__new__(_PlainResource)
            raise CekitError("Resource type is not supported: %s" % resource)

    def __init__(self, descriptor):
        self.schemas = [
            yaml.safe_load("""
        map:
          name: {type: str}
          git:
            map:
              url: {type: str, required: True}
              ref: {type: str}
          path: {type: str, required: False}
          url: {type: str, required: False}
          md5: {type: str}
          sha1: {type: str}
          sha256: {type: str}
          sha512: {type: str}
          description: {type: str}
          target: {type: str}
        assert: \"val['git'] is not None or val['path'] is not None or val['url] is not None or val['md5'] is not None\""""
                           )
        ]
        super(Resource, self).__init__(descriptor)
        self.skip_merging = ['md5', 'sha1', 'sha256', 'sha512']

        # forwarded import to prevent circural imports
        from cekit.cache.artifact import ArtifactCache
        self.cache = ArtifactCache()

        self.name = descriptor['name']

        self.description = None
        if 'description' in descriptor:
            self.description = descriptor['description']

    def __eq__(self, other):
        # All subclasses of Resource are considered same object type
        if isinstance(other, Resource):
            return self['name'] == other['name']
        return NotImplemented

    def __ne__(self, other):
        # All subclasses of Resource are considered same object type
        if isinstance(other, Resource):
            return not self['name'] == other['name']
        return NotImplemented

    def _copy_impl(self, target):
        raise NotImplementedError("Implement _copy_impl() for Resource: " +
                                  self.__module__ + "." + type(self).__name__)

    def target_file_name(self):
        if 'target' not in self:
            self['target'] = os.path.basename(self.name)
        return self['target']

    def copy(self, target=os.getcwd()):
        if os.path.isdir(target):
            target = os.path.join(target, self.target_file_name())

        logger.info("Preparing resource '%s'" % (self.name))

        if os.path.exists(target) and self.__verify(target):
            logger.debug("Local resource '%s' exists and is valid" % self.name)
            return target

        cached_resource = self.cache.cached(self)

        if cached_resource:
            shutil.copy(cached_resource['cached_path'], target)
            logger.info("Using cached artifact '%s'." % self.name)

        else:
            try:
                self.cache.add(self)
                cached_resource = self.cache.get(self)
                shutil.copy(cached_resource['cached_path'], target)
                logger.info("Using cached artifact '%s'." % self.name)
            except ValueError:
                return self.guarded_copy(target)

    def guarded_copy(self, target):
        try:
            self._copy_impl(target)
        except Exception as ex:
            logger.warning(
                "Cekit is not able to fetch resource '%s' automatically. "
                "Please use cekit-cache command to add this artifact manually."
                % self.name)

            if self.description:
                logger.info(self.description)

            # exception is fatal we be logged before Cekit dies
            raise CekitError(
                "Error copying resource: '%s'. See logs for more info." %
                self.name, ex)

        if set(SUPPORTED_HASH_ALGORITHMS).intersection(self) and \
                not self.__verify(target):
            raise CekitError('Artifact checksum verification failed!')

        return target

    def __verify(self, target):
        """ Checks all defined check_sums for an aritfact """
        if not set(SUPPORTED_HASH_ALGORITHMS).intersection(self):
            logger.debug("Artifact '%s' lacks any checksum definition." %
                         self.name)
            return False
        if not Resource.CHECK_INTEGRITY:
            logger.info("Integrity checking disabled, skipping verification.")
            return True
        if os.path.isdir(target):
            logger.info("Target is directory, cannot verify checksum.")
            return True
        for algorithm in SUPPORTED_HASH_ALGORITHMS:
            if algorithm in self and self[algorithm]:
                if not check_sum(target, algorithm, self[algorithm],
                                 self['name']):
                    return False
        return True

    def __substitute_cache_url(self, url):
        cache = config.get('common', 'cache_url')

        if not cache:
            return url

        for algorithm in SUPPORTED_HASH_ALGORITHMS:
            if algorithm in self:
                logger.debug(
                    "Using {} checksum to fetch artifacts from cacher".format(
                        algorithm))

                url = cache.replace('#filename#', self.name).replace(
                    '#algorithm#', algorithm).replace('#hash#',
                                                      self[algorithm])

                logger.debug("Using cache url '{}'".format(url))

        return url

    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))
Exemplo n.º 3
0
class Resource(Descriptor):
    """
    Base class for handling resources.

    In most cases resources are synonym to artifacts.
    """

    CHECK_INTEGRITY = True

    def __init__(self, descriptor):
        # Schema must be provided by the implementing class
        if not self.schema:
            raise CekitError("Resource '{}' has no schema defined".format(
                type(self).__name__))

        # Includes validation
        super(Resource, self).__init__(descriptor)

        # Make sure the we have 'name' set
        self._ensure_name(descriptor)
        # Make sure the we have 'target' set
        self._ensure_target(descriptor)
        # Add a single slash at the end of the 'dest' value
        self._normalize_dest(descriptor)
        # Convert the dictionary into a Map object for easier access
        self._descriptor = self.__to_map(descriptor)

        self.skip_merging = ['md5', 'sha1', 'sha256', 'sha512']

        # forwarded import to prevent circular imports
        from cekit.cache.artifact import ArtifactCache
        self.cache = ArtifactCache()

    def __to_map(self, dictionary):
        """
        Convert provided dictionary, recursively, into a Map object.

        This will make it possible to access nested elements
        via properties:

                res.git.url

        instead of:

                res.git['url]
        """
        if not isinstance(dictionary, dict):
            return dictionary

        converted = Map()

        for key in dictionary:
            converted[key] = self.__to_map(dictionary[key])

        return converted

    def __eq__(self, other):
        # All subclasses of Resource are considered same object type
        if isinstance(other, Resource):
            return self['name'] == other['name']
        return NotImplemented

    def __ne__(self, other):
        # All subclasses of Resource are considered same object type
        if isinstance(other, Resource):
            return not self['name'] == other['name']
        return NotImplemented

    def _ensure_name(self, descriptor):
        """
        Makes sure the 'name' attribute exists.

        If it does not, a default value will be computed based on the implementation
        type of the resource class.
        """

        # If the 'name' key is present and there is a value, we have nothing to do
        if descriptor.get('name') is not None:
            return

        # Get the default value set for particular resource type
        default = self._get_default_name_value(descriptor)  # pylint: disable=assignment-from-none

        # If there is still no default, we need to fail, because 'name' is required.
        # If we ever get here, it is a bug and should be reported.
        if not default:
            raise CekitError((
                "Internal error: no value found for 'name' in '{}' artifact; unable to generate default value, "
                + "please report it: https://github.com/cekit/cekit/issues"
            ).format(descriptor))

        logger.warning(
            "No value found for 'name' in '{}' artifact; using auto-generated value of '{}'"
            .format(json.dumps(descriptor, sort_keys=True), default))

        descriptor['name'] = default

    def _ensure_target(self, descriptor):
        if descriptor.get('target') is not None:
            return

        descriptor['target'] = self._get_default_target_value(descriptor)

    def _normalize_dest(self, descriptor):
        """
        Make sure that the 'dest' value, if provided, does end with a single slash.
        """

        if descriptor.get('dest') is not None:
            descriptor['dest'] = os.path.normpath(descriptor.get('dest')) + '/'

    def _get_default_name_value(self, descriptor):  # pylint: disable=unused-argument
        """
        Returns default identifier value for particular class.

        This method must be overridden in classes extending Resource.
        Returned should be a string that will be be a unique identifier
        of the resource across thw whole image.
        """
        return None

    def _get_default_target_value(self, descriptor):  # pylint: disable=unused-argument
        return os.path.basename(descriptor.get('name'))

    def _copy_impl(self, target):
        raise NotImplementedError("Implement _copy_impl() for Resource: " +
                                  self.__module__ + "." + type(self).__name__)

    def copy(self, target=os.getcwd()):

        if os.path.isdir(target):
            target = os.path.join(target, self.target)

        logger.info("Copying resource '{}'...".format(self.name))

        if os.path.exists(target) and self.__verify(target):
            logger.debug("Local resource '{}' exists and is valid".format(
                self.name))
            return target

        cached_resource = self.cache.cached(self)

        if cached_resource:
            shutil.copy(cached_resource['cached_path'], target)
            logger.info("Using cached artifact '{}'.".format(self.name))

        else:
            try:
                self.cache.add(self)
                cached_resource = self.cache.get(self)
                shutil.copy(cached_resource['cached_path'], target)
                logger.info("Using cached artifact '{}'.".format(self.name))
            except ValueError:
                return self.guarded_copy(target)

    def guarded_copy(self, target):
        try:
            self._copy_impl(target)
        except Exception as ex:
            logger.warning(
                "Cekit is not able to fetch resource '{}' automatically. "
                "Please use cekit-cache command to add this artifact manually."
                .format(self.name))

            if self.description:
                logger.info(self.description)

            # exception is fatal we be logged before Cekit dies
            raise CekitError(
                "Error copying resource: '%s'. See logs for more info." %
                self.name, ex)

        if set(SUPPORTED_HASH_ALGORITHMS).intersection(self) and \
                not self.__verify(target):
            raise CekitError('Artifact checksum verification failed!')

        return target

    def __verify(self, target):
        """ Checks all defined check_sums for an aritfact """
        if not set(SUPPORTED_HASH_ALGORITHMS).intersection(self):
            logger.debug("Artifact '{}' lacks any checksum definition.".format(
                self.name))
            return False
        if not Resource.CHECK_INTEGRITY:
            logger.info("Integrity checking disabled, skipping verification.")
            return True
        if os.path.isdir(target):
            logger.info("Target is directory, cannot verify checksum.")
            return True
        for algorithm in SUPPORTED_HASH_ALGORITHMS:
            if algorithm in self and self[algorithm]:
                if not check_sum(target, algorithm, self[algorithm],
                                 self['name']):
                    return False
        return True

    def __substitute_cache_url(self, url):
        cache = config.get('common', 'cache_url')

        if not cache:
            return url

        for algorithm in SUPPORTED_HASH_ALGORITHMS:
            if algorithm in self:
                logger.debug(
                    "Using {} checksum to fetch artifacts from cacher".format(
                        algorithm))

                url = cache.replace('#filename#', self.name).replace(
                    '#algorithm#', algorithm).replace('#hash#',
                                                      self[algorithm])

                logger.debug("Using cache url '{}'".format(url))

        return url

    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 '{}' as {}".format(url, destination))

        parsed_url = urlparse(url)

        if parsed_url.scheme == 'file' or not parsed_url.scheme:
            if os.path.isdir(parsed_url.path):
                shutil.copytree(parsed_url.path, destination)
            else:
                shutil.copy(parsed_url.path, destination)
        elif parsed_url.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: {}".format(url))