예제 #1
0
    def save(
        self,
        chunk_size: Optional[int] = api.DEFAULT_CHUNK_SIZE,
        named: Union[str, bool] = False,
    ) -> Iterator[bytes]:
        """Returns Image as tarball.

        Args:
            chunk_size: If None, data will be streamed in received buffer size.
                If not None, data will be returned in sized buffers. Default: 2MB
            named: When False, tarball will not retain repository and tag information.
                When True, first tag is used to identify the Image.
                when str, value is used as tag to identify the Image.
                Always ignored.

        Raises:
            APIError: when service returns an error.

        Notes:
            Format is set to docker-archive, this allows load() to import this tarball.
        """
        _ = named

        response = self.client.get(f"/images/{self.id}/get",
                                   params={"format": ["docker-archive"]},
                                   stream=True)

        if response.status_code == requests.codes.okay:
            return response.iter_content(chunk_size=chunk_size)

        body = response.json()
        raise APIError(body["cause"],
                       response=response,
                       explanation=body["message"])
예제 #2
0
    def load(self, data: bytes) -> Iterable[Image]:
        """Restore an image previously saved.

        Args:
            data: Image to be loaded in tarball format.

        Yields:

        Raises:
            APIError: when service returns an error.
        """
        response = self.client.post(
            "/images/load",
            data=data,
            headers={"Content-type": "application/x-www-form-urlencoded"})
        body = response.json()

        if response.status_code != 200:
            raise APIError(body["cause"],
                           response=response,
                           explanation=body["message"])

        # Dict[Literal["Names"], List[str]]]
        for i in body["Names"]:
            yield self.get(i)
예제 #3
0
    def remove(self, digest: str) -> None:
        """Remove Image digest from manifest list.

        Args:
            digest: Image digest to be removed. Should a full Image reference be provided
                the digest will be parsed out.

        Raises:
            ImageNotFound: When the Image could not be found
            APIError: When service reports an error
        """
        if "@" in digest:
            digest = digest.split("@", maxsplit=2)[1]

        response = self.client.delete(f"/manifests/{self.quoted_name}",
                                      params={"digest": digest})

        if response.status_code == requests.codes.okay:
            return self.reload()

        body = response.json()
        if response.status_code == requests.codes.not_found:
            raise ImageNotFound(body["cause"],
                                response=response,
                                explanation=body["message"])
        raise APIError(body["cause"],
                       response=response,
                       explanation=body["message"])
예제 #4
0
    def search(self, term: str, **kwargs) -> List[Dict[str, Any]]:
        """Search Images on registries.

        Args:
            term: Used to target Image results.

        Keyword Args:
            filters (Mapping[str, List[str]): Refine results of search. Available filters:
                - is-automated (bool): Image build is automated.
                - is-official (bool): Image build is owned by product provider.
                - stars (int): Image has at least this number of stars.
            noTrunc (bool): Do not truncate any result string. Default: True.
            limit (int): Maximum number of results.

        Raises:
            APIError: when service returns an error
        """
        params = {
            "term": [term],
            "noTrunc": True,
        }
        if "limit" in kwargs:
            params["limit"] = kwargs.pop("limit")
        if "filters" in kwargs:
            params["filters"] = api.format_filters(kwargs.pop("filters"))

        response = self.client.get("/images/search", params=params)
        body = response.json()

        if response.status_code == 200:
            return body
        raise APIError(body["cause"],
                       response=response,
                       explanation=body["message"])
예제 #5
0
    def push(
        self,
        destination: str,
        all: Optional[bool] = None  # pylint: disable=redefined-builtin
    ) -> None:
        """Push a manifest list or image index to a registry.

        Args:
            destination: Target for push.
            all: Push all images.

        Raises:
            NotFound: When the Manifest could not be found
            APIError: When service reports an error
        """
        params = {
            "all": all,
            "destination": destination,
        }
        response = self.client.post(f"/manifests/{self.quoted_name}/push",
                                    params=params)

        if response.status_code == requests.codes.okay:
            return

        body = response.json()
        if response.status_code == requests.codes.not_found:
            raise NotFound(body["cause"],
                           response=response,
                           explanation=body["message"])
        raise APIError(body["cause"],
                       response=response,
                       explanation=body["message"])
예제 #6
0
    def create(
        self,
        name: str,
        data: bytes,
        labels: Optional[Mapping[str, Any]] = None,  # pylint: disable=unused-argument
        driver: Optional[str] = None,
    ) -> Secret:
        """Create a Secret.

        Args:
            name: User-defined name of the secret.
            data: Secret to be registered with Podman service.
            labels: Ignored.
            driver: Secret driver.

        Raises:
            APIError when service returns an error.
        """
        params = {
            "name": name,
            "driver": driver,
        }
        response = self.client.post("/secrets/create", params=params, data=data)
        body = response.json()

        if response.status_code == requests.codes.okay:
            return self.get(body["ID"])
        raise APIError(body["cause"], response=response, explanation=body["message"])
예제 #7
0
    def list(self, **kwargs) -> List[Image]:
        """Report on images.

        Keyword Args:
            name (str) – Only show images belonging to the repository name
            all (bool) – Show intermediate image layers. By default, these are filtered out.
            filters (Mapping[str, Union[str, List[str]]) – Filters to be used on the image list.
                Available filters:
                - dangling (bool)
                - label (Union[str, List[str]]): format either "key", "key=value"

        Raises:
            APIError: when service returns an error.
        """
        params = {}
        if "all" in kwargs:
            params["all"] = kwargs.pop("all")
        if "name" in kwargs:
            params["name"] = kwargs.pop("name")
        if "filters" in kwargs:
            params["filters"] = api.format_filters(kwargs.pop("filters"))

        response = self.client.get("/images/json", params=params)
        body = response.json()

        if response.status_code != 200:
            raise APIError(body["cause"],
                           response=response,
                           explanation=body["message"])

        images: List[Image] = []
        for element in body:
            images.append(self.prepare_model(element))
        return images
예제 #8
0
    def save(self,
             chunk_size: Optional[int] = 2097152,
             named: Union[str, bool] = False) -> Iterator[bytes]:
        """Returns Image as tarball.

        Args:
            chunk_size: If None, data will be streamed in received buffer size.
                If not None, data will be returned in sized buffers. Default: 2MB
            named: When False, tarball will not retain repository and tag information.
                When True, first tag is used to identify the Image.
                when str, value is used as tag to identify the Image.
                Always ignored.

        Raises:
            APIError: when service returns an error.
        """
        _ = named

        response = self.client.get(f"/images/{self.id}/get", stream=True)

        if response.status_code == 200:
            return response.iter_content(chunk_size=chunk_size)

        body = response.json()
        raise APIError(body["cause"],
                       response=response,
                       explanation=body["message"])
예제 #9
0
    def push(
        self, repository: str, tag: Optional[str] = None, **kwargs
    ) -> Union[str, Iterator[Union[str, Dict[str, Any]]]]:
        """Push Image or repository to the registry.

        Args:
            repository: Target repository for push
            tag: Tag to push, if given

        Keyword Args:
            auth_config (Mapping[str, str]: Override configured credentials. Must include
                username and password keys.
            decode (bool): return data from server as Dict[str, Any]. Ignored unless stream=True.
            destination (str): alternate destination for image. (Podman only)
            stream (bool): return output as blocking generator. Default: False.
            tlsVerify (bool): Require TLS verification.

        Raises:
            APIError: when service returns an error.
        """
        # TODO set X-Registry-Auth
        headers = {
            # A base64url-encoded auth configuration
            "X-Registry-Auth": ""
        }

        params = {
            "destination": kwargs.get("destination"),
            "tlsVerify": kwargs.get("tlsVerify"),
        }

        name = urllib.parse.quote_plus(repository)
        response = self.client.post(f"/images/{name}/push", params=params, headers=headers)

        if response.status_code != requests.codes.ok:
            body = response.json()
            raise APIError(body["cause"], response=response, explanation=body["message"])

        tag_count = 0 if tag is None else 1
        body = [
            {
                "status": f"Pushing repository {repository} ({tag_count} tags)",
            },
            {
                "status": "Pushing",
                "progressDetail": {},
                "id": repository,
            },
        ]

        stream = kwargs.get("stream", False)
        decode = kwargs.get("decode", False)
        if stream:
            return self._push_helper(decode, body)

        with io.StringIO() as buffer:
            for entry in body:
                buffer.write(json.dumps(entry) + "\n")
            return buffer.getvalue()
예제 #10
0
    def prune(self, filters: Optional[Mapping[str, Any]] = None) -> Dict[str, Any]:
        """Delete unused images.

        Args:
            filters: Qualify Images to prune. Available filters:
                - dangling (bool): when true, only delete unused and untagged images.
                - until (str): Delete images older than this timestamp.

        Raises:
            APIError: when service returns an error.

        Note:
            The Untagged key will always be "".
        """
        response = self.client.post(
            "/images/prune", params={"filters": api.prepare_filters(filters)}
        )
        body = response.json()

        if response.status_code != requests.codes.ok:
            raise APIError(body["cause"], response=response, explanation=body["message"])

        deleted: List[Dict[str, str]] = []
        error: List[str] = []
        reclaimed: int = 0
        for element in response.json():
            if "Err" in element and element["Err"] is not None:
                error.append(element["Err"])
            else:
                reclaimed += element["Size"]
                deleted.append(
                    {
                        "Deleted": element["Id"],
                        "Untagged": "",
                    }
                )
        if len(error) > 0:
            raise APIError(response.url, response=response, explanation="; ".join(error))

        # body -> Dict[Literal["ImagesDeleted", "SpaceReclaimed"],
        #   List[Dict[Literal["Deleted", "Untagged"], str]
        return {
            "ImagesDeleted": deleted,
            "SpaceReclaimed": reclaimed,
        }
예제 #11
0
    def add(self, images: List[Union[Image, str]], **kwargs) -> None:
        """Add Image to manifest list.

        Args:
            images: List of Images to be added to manifest.

        Keyword Args:
            all (bool):
            annotation (Dict[str, str]):
            arch (str):
            features (List[str]):
            os (str):
            os_version (str):
            variant (str):

        Raises:
            ImageNotFound: When Image(s) could not be found
            APIError: When service reports an error
        """
        params = {
            "all": kwargs.get("all"),
            "annotation": kwargs.get("annotation"),
            "arch": kwargs.get("arch"),
            "features": kwargs.get("features"),
            "images": list(),
            "os": kwargs.get("os"),
            "os_version": kwargs.get("os_version"),
            "variant": kwargs.get("variant"),
        }
        for item in images:
            if isinstance(item, Image):
                item = item.attrs["RepoTags"][0]
            params["images"].append(item)

        data = api.prepare_body(params)
        response = self.client.post(f"/manifests/{self.quoted_name}/add",
                                    data=data)

        if response.status_code == requests.codes.okay:
            return self.reload()

        body = response.json()
        if response.status_code == requests.codes.not_found:
            raise ImageNotFound(body["cause"],
                                response=response,
                                explanation=body["message"])
        raise APIError(body["cause"],
                       response=response,
                       explanation=body["message"])
예제 #12
0
    def _request(
        self,
        method: str,
        path: Union[str, bytes],
        data: _Data = None,
        params: Union[None, bytes, Mapping[str, str]] = None,
        headers: Optional[Mapping[str, str]] = None,
        timeout: _Timeout = None,
        stream: Optional[bool] = None,
        **kwargs,
    ) -> Response:
        """HTTP operation against configured Podman service.

        Args:
            method: HTTP method to use for request
            path: Relative path to RESTful resource. base_url will be prepended to path.
            params: Optional parameters to include with URL.
            headers: Optional headers to include in request.
            timeout: Number of seconds to wait on request, or (connect timeout, read timeout) tuple

        Keyword Args:
            compatible: Will override the default path prefix with compatible prefix

        Raises:
            APIError: when service returns an Error.
        """
        if timeout is None:
            timeout = api.DEFAULT_TIMEOUT

        if not path.startswith("/"):
            path = f"/{path}"

        compatible = kwargs.get("compatible", False)
        path_prefix = self.compatible_prefix if compatible else self.path_prefix
        uri = self.base_url + path_prefix + path

        try:
            return self.request(
                method.upper(),
                uri,
                params=params,
                data=data,
                headers=(headers or {}),
                timeout=timeout,
                stream=stream,
            )
        except OSError as e:
            raise APIError(
                uri, explanation=f"{method.upper()} operation failed") from e
예제 #13
0
    def list(self, **kwargs) -> List[Secret]:
        """Report on Secrets.

        Keyword Args:
            filters (Dict[str, Any]): Ignored.

        Raises:
            APIError: When error returned by service.
        """
        response = self.client.get("/secrets/json")
        body = response.json()

        if response.status_code == requests.codes.okay:
            return [self.prepare_model(attrs=item) for item in body]
        raise APIError(body["cause"], response=response, explanation=body["message"])
예제 #14
0
    def create(
            self,
            names: List[str],
            images: Optional[List[Union[Image, str]]] = None,
            all: Optional[bool] = None,  # pylint: disable=redefined-builtin
    ) -> Manifest:
        """Create a Manifest.

        Args:
            names: Identifiers to be added to the manifest. There must be at least one.
            images: Images or Image identifiers to be included in the manifest.
            all: When True, add all contents from images given.

        Raises:
            ValueError: When no names are provided.
            NotFoundImage: When a given image does not exist.
        """
        if names is None or len(names) == 0:
            raise ValueError("At least one manifest name is required.")

        params = {"name": names}
        if images is not None:
            params["image"] = list()
            for item in images:
                if isinstance(item, Image):
                    item = item.attrs["RepoTags"][0]
                params["image"].append(item)

        if all is not None:
            params["all"] = all

        response = self.client.post("/manifests/create", params=params)
        body = response.json()

        if response.status_code == requests.codes.okay:
            manifest = self.get(body["Id"])
            manifest.attrs["names"] = names
            return manifest

        if response.status_code == requests.codes.not_found:
            raise ImageNotFound(body["cause"],
                                response=response,
                                explanation=body["message"])
        raise APIError(body["cause"],
                       response=response,
                       explanation=body["message"])
예제 #15
0
    def remove(self, all: Optional[bool] = None):  # pylint: disable=redefined-builtin
        """Delete secret.

        Args:
            all: When True, delete all secrets.

        Raises:
            NotFound: Secret does not exist.
            APIError: Error returned by service.
        """
        response = self.client.delete(f"/secrets/{self.id}", params={"all": all})

        if response.status_code == requests.codes.no_content:
            return

        body = response.json()
        raise APIError(body["cause"], response=response, explanation=body["message"])
예제 #16
0
    def get(self, secret_id: str) -> Secret:  # pylint: disable=arguments-differ
        """Return information for Secret by name or id.

        Args:
            secret_id: Secret name or id.

        Raises:
            NotFound: Secret does not exist.
            APIError: Error returned by service.
        """
        response = self.client.get(f"/secrets/{secret_id}/json")
        body = response.json()

        if response.status_code == requests.codes.okay:
            return self.prepare_model(attrs=body)

        if response.status_code == 404:
            raise NotFound(body["cause"], response=response, explanation=body["message"])
        raise APIError(body["cause"], response=response, explanation=body["message"])
예제 #17
0
    def remove(self,
               image: str,
               force: bool = False,
               noprune: bool = False) -> List[Dict[str, Union[str, int]]]:
        """Delete image from Podman service.

        Args:
            image: Name or Id of Image to remove
            force: delete Image if in use
            noprune: do not delete untagged parents. Ignored.

        Returns:
            List[Dict[Literal["Deleted", "Untagged", "Errors", "ExitCode"], Union[str, int]]]

        Raises:
            APIError: when service returns an error.

        Notes:
            The dictionaries with keys Errors and ExitCode are added.
        """
        _ = noprune

        params = {}
        if force:
            params["force"] = str(bool(force))

        response = self.client.delete(f"/images/{image}", params=params)
        body = response.json()

        if response.status_code != 200:
            raise APIError(body["cause"],
                           response=response,
                           explanation=body["message"])

        # Dict[Literal["Deleted", "Untagged", "Errors", "ExitCode"], Union[int, List[str]]]
        results: List[Dict[str, Union[int, str]]] = []
        for key in ("Deleted", "Untagged", "Errors"):
            if key in body:
                for element in body[key]:
                    results.append({key: element})
        results.append({"ExitCode": body["ExitCode"]})
        return results
예제 #18
0
    def history(self) -> List[Dict[str, Any]]:
        """Returns history of the Image.

        Raises:
            APIError: when service returns an error.
        """

        response = self.client.get(f"/images/{self.id}/history")
        body = response.json()

        if response.status_code == requests.codes.ok:
            return body

        if response.status_code == requests.codes.not_found:
            raise ImageNotFound(body["cause"],
                                response=response,
                                explanation=body["message"])
        raise APIError(body["cause"],
                       response=response,
                       explanation=body["message"])
예제 #19
0
    def get(self, name: str) -> Image:  # pylint: disable=arguments-differ
        """Returns an image by name or id.

        Args:
            name: Image id or name for which to search

        Raises:
            ImageNotFound: when image does not exist.
            APIError: when service returns an error.
        """
        name = urllib.parse.quote_plus(name)
        response = self.client.get(f"/images/{name}/json")
        body = response.json()

        if response.status_code == requests.codes.ok:
            return self.prepare_model(body)

        if response.status_code == requests.codes.not_found:
            raise ImageNotFound(body["cause"], response=response, explanation=body["message"])
        raise APIError(body["cause"], response=response, explanation=body["message"])
예제 #20
0
    def remove(
        self, image: Union[Image, str], force: Optional[bool] = None, noprune: bool = False
    ) -> List[Dict[str, Union[str, int]]]:
        """Delete image from Podman service.

        Args:
            image: Name or Id of Image to remove
            force: Delete Image even if in use
            noprune: Do not delete untagged parents. Ignored.

        Returns:
            List[Dict[Literal["Deleted", "Untagged", "Errors", "ExitCode"], Union[str, int]]]

        Raises:
            APIError: when service returns an error.

        Notes:
            The dictionaries with keys Errors and ExitCode are added.
        """
        _ = noprune

        if isinstance(image, Image):
            image = image.id

        response = self.client.delete(f"/images/{image}", params={"force": force})
        body = response.json()

        if response.status_code != requests.codes.ok:
            if response.status_code == requests.codes.not_found:
                raise ImageNotFound(body["cause"], response=response, explanation=body["message"])
            raise APIError(body["cause"], response=response, explanation=body["message"])

        # Dict[Literal["Deleted", "Untagged", "Errors", "ExitCode"], Union[int, List[str]]]
        results: List[Dict[str, Union[int, str]]] = []
        for key in ("Deleted", "Untagged", "Errors"):
            if key in body:
                for element in body[key]:
                    results.append({key: element})
        results.append({"ExitCode": body["ExitCode"]})
        return results
예제 #21
0
    def load(self, data: bytes) -> Generator[Image, None, None]:
        """Restore an image previously saved.

        Args:
            data: Image to be loaded in tarball format.

        Raises:
            APIError: when service returns an error.
        """
        # TODO fix podman swagger cannot use this header!
        # headers = {"Content-type": "application/x-www-form-urlencoded"}

        headers = {"Content-type": "application/x-tar"}
        response = self.client.post("/images/load", data=data, headers=headers)
        body = response.json()

        if response.status_code != requests.codes.ok:
            raise APIError(body["cause"], response=response, explanation=body["message"])

        # Dict[Literal["Names"], List[str]]]
        for item in body["Names"]:
            yield self.get(item)
예제 #22
0
    def tag(self,
            repository: str,
            tag: Optional[str],
            force: bool = False) -> bool:
        """Tag Image into repository.

        Args:
            repository: The repository for tagging Image.
            tag: optional tag name.
            force: force tagging. Always ignored.

        Returns:
            True, when operational succeeds.

        Raises:
            ImageNotFound: when service cannot find image.
            APIError: when service returns an error.
        """
        _ = force

        params = {"repo": repository}
        if tag is not None:
            params["tag"] = tag

        response = self.client.post(f"/images/{self.id}/tag", params=params)

        if response.status_code == requests.codes.created:
            return True

        error = response.json()
        if response.status_code == requests.codes.not_found:
            raise ImageNotFound(error["cause"],
                                response=response,
                                explanation=error["message"])
        raise APIError(error["cause"],
                       response=response,
                       explanation=error["message"])
예제 #23
0
    def pull(
        self, repository: str, tag: Optional[str] = None, all_tags: bool = False, **kwargs
    ) -> Union[Image, List[Image]]:
        """Request Podman service to pull image(s) from repository.

        Args:
            repository: repository to pull from
            tag: image tag to pull, if None all tags in repository are pulled.
            all_tags: pull all image tags from repository.

        Keyword Args:
            auth_config (Mapping[str, str]) – Override the credentials that are found in the
                config for this request. auth_config should contain the username and password
                keys to be valid.
            platform (str) – Platform in the format os[/arch[/variant]]
            tls_verify (bool) - Require TLS verification. Default: True.

        Returns:
            If all_tags is True, return list of Image's rather than Image pulled.

        Raises:
            APIError: when service returns an error.
        """
        if tag is None or len(tag) == 0:
            tag = "latest"

        params = {
            "reference": repository,
            "tlsVerify": kwargs.get("tls_verify"),
        }

        if all_tags:
            params["allTags"] = True
        else:
            params["reference"] = f"{repository}:{tag}"

        if "platform" in kwargs:
            platform = kwargs.get("platform")
            ospm, arch, variant = platform.split("/")

            if ospm is not None:
                params["OS"] = ospm
            if arch is not None:
                params["Arch"] = arch
            if variant is not None:
                params["Variant"] = variant

        if "auth_config" in kwargs:
            username = kwargs["auth_config"].get("username")
            password = kwargs["auth_config"].get("password")
            if username is None or password is None:
                raise ValueError("'auth_config' requires keys 'username' and 'password'")
            params["credentials"] = f"{username}:{password}"

        response = self.client.post("/images/pull", params=params)

        if response.status_code != requests.codes.ok:
            body = response.json()
            raise APIError(body["cause"], response=response, explanation=body["error"])

        for item in response.iter_lines():
            body = json.loads(item)
            if all_tags and "images" in body:
                images: List[Image] = []
                for name in body["images"]:
                    images.append(self.get(name))
                return images

            if "id" in body:
                return self.get(body["id"])
        return self.resource()
예제 #24
0
    def build(self, **kwargs) -> Tuple[Image, Iterator[bytes]]:
        """Returns built image.

        Keyword Args:
            path (str) – Path to the directory containing the Dockerfile
            fileobj – A file object to use as the Dockerfile. (Or an IO object)
            tag (str) – A tag to add to the final image
            quiet (bool) – Whether to return the status
            nocache (bool) – Don’t use the cache when set to True
            rm (bool) – Remove intermediate containers. Default True
            timeout (int) – HTTP timeout
            custom_context (bool) – Optional if using fileobj (ignored)
            encoding (str) – The encoding for a stream. Set to gzip for compressing (ignored)
            pull (bool) – Downloads any updates to the FROM image in Dockerfile
            forcerm (bool) – Always remove intermediate containers, even after unsuccessful builds
            dockerfile (str) – path within the build context to the Dockerfile
            buildargs (Mapping[str,str) – A dictionary of build arguments
            container_limits (Dict[str, Union[int,str]]) –
                A dictionary of limits applied to each container created by the build process.
                    Valid keys:

                    - memory (int): set memory limit for build
                    - memswap (int): Total memory (memory + swap), -1 to disable swap
                    - cpushares (int): CPU shares (relative weight)
                    - cpusetcpus (str): CPUs in which to allow execution, For example, "0-3", "0,1"
                    - cpuperiod (int): CPU CFS (Completely Fair Scheduler) period (Podman only)
                    - cpuquota (int): CPU CFS (Completely Fair Scheduler) quota (Podman only)
            shmsize (int) – Size of /dev/shm in bytes. The size must be greater than 0.
                If omitted the system uses 64MB
            labels (Mapping[str,str]) – A dictionary of labels to set on the image
            cache_from (List[str]) – A list of image's identifier used for build cache resolution
            target (str) – Name of the build-stage to build in a multi-stage Dockerfile
            network_mode (str) – networking mode for the run commands during build
            squash (bool) – Squash the resulting images layers into a single layer.
            extra_hosts (Dict[str,str]) – Extra hosts to add to /etc/hosts in building
                containers, as a mapping of hostname to IP address.
            platform (str) – Platform in the format os[/arch[/variant]].
            isolation (str) – Isolation technology used during build. (ignored)
            use_config_proxy (bool) – (ignored)
            http_proxy (bool) - Inject http proxy environment variables into container (Podman only)

        Returns:
            first item is Image built
            second item is the build logs

        Raises:
            BuildError: when there is an error during the build.
            APIError: when service returns an error.
            TypeError: when neither path nor fileobj is not specified.
        """

        params = self._render_params(kwargs)

        body = None
        path = None
        if "fileobj" in kwargs:
            path = tempfile.TemporaryDirectory()
            filename = pathlib.Path(path.name) / params["dockerfile"]

            with open(filename, "w") as file:
                shutil.copyfileobj(kwargs["fileobj"], file)
            body = api.create_tar(anchor=path.name, gzip=kwargs.get("gzip", False))
        elif "path" in kwargs:
            # The Dockerfile will be copied into the context_dir if needed
            params["dockerfile"] = api.prepare_containerfile(kwargs["path"], params["dockerfile"])

            excludes = api.prepare_dockerignore(kwargs["path"])
            body = api.create_tar(
                anchor=kwargs["path"], exclude=excludes, gzip=kwargs.get("gzip", False)
            )

        timeout = kwargs.get("timeout", api.DEFAULT_TIMEOUT)
        response = self.client.post(
            "/build",
            params=params,
            data=body,
            headers={
                "Content-type": "application/x-tar",
                # "X-Registry-Config": "TODO",
            },
            stream=True,
            timeout=timeout,
        )
        if hasattr(body, "close"):
            body.close()

        if hasattr(path, "cleanup"):
            path.cleanup()

        if response.status_code != requests.codes.okay:
            body = response.json()
            raise APIError(body["cause"], response=response, explanation=body["message"])

        image_id = unknown = None
        marker = re.compile(r"(^[0-9a-f]+)\n$")
        report_stream, stream = itertools.tee(response.iter_lines())
        for line in stream:
            result = json.loads(line)
            if "error" in result:
                raise BuildError(result["error"], report_stream)
            if "stream" in result:
                match = marker.match(result["stream"])
                if match:
                    image_id = match.group(1)
            unknown = line

        if image_id:
            return self.get(image_id), report_stream

        raise BuildError(unknown or "Unknown", report_stream)
예제 #25
0
    def build(self, **kwargs) -> Tuple[Image, Iterator[bytes]]:
        """Returns built image.

        Keyword Args:
            path (str) – Path to the directory containing the Dockerfile
            fileobj – A file object to use as the Dockerfile. (Or an IO object)
            tag (str) – A tag to add to the final image
            quiet (bool) – Whether to return the status
            nocache (bool) – Don’t use the cache when set to True
            rm (bool) – Remove intermediate containers. Default True
            timeout (int) – HTTP timeout
            custom_context (bool) – Optional if using fileobj (ignored)
            encoding (str) – The encoding for a stream. Set to gzip for compressing (ignored)
            pull (bool) – Downloads any updates to the FROM image in Dockerfile
            forcerm (bool) – Always remove intermediate containers, even after unsuccessful builds
            dockerfile (str) – path within the build context to the Dockerfile
            buildargs (Mapping[str,str) – A dictionary of build arguments
            container_limits (Dict[str, Union[int,str]]) –
                A dictionary of limits applied to each container created by the build process.
                    Valid keys:

                    - memory (int): set memory limit for build
                    - memswap (int): Total memory (memory + swap), -1 to disable swap
                    - cpushares (int): CPU shares (relative weight)
                    - cpusetcpus (str): CPUs in which to allow execution, For example, "0-3", "0,1"
                    - cpuperiod (int): CPU CFS (Completely Fair Scheduler) period (Podman only)
                    - cpuquota (int): CPU CFS (Completely Fair Scheduler) quota (Podman only)
            shmsize (int) – Size of /dev/shm in bytes. The size must be greater than 0.
                If omitted the system uses 64MB
            labels (Mapping[str,str]) – A dictionary of labels to set on the image
            cache_from (List[str]) – A list of image's identifier used for build cache resolution
            target (str) – Name of the build-stage to build in a multi-stage Dockerfile
            network_mode (str) – networking mode for the run commands during build
            squash (bool) – Squash the resulting images layers into a single layer.
            extra_hosts (Dict[str,str]) – Extra hosts to add to /etc/hosts in building
                containers, as a mapping of hostname to IP address.
            platform (str) – Platform in the format os[/arch[/variant]].
            isolation (str) – Isolation technology used during build. (ignored)
            use_config_proxy (bool) – (ignored)
            http_proxy (bool) - Inject http proxy environment variables into container (Podman only)

        Returns:
            first item is Image built
            second item is the build logs

        Raises:
            BuildError: when there is an error during the build.
            APIError: when service returns an error.
            TypeError: when neither path nor fileobj is not specified.
        """
        if "path" not in kwargs and "fileobj" not in kwargs:
            raise TypeError("Either path or fileobj must be provided.")

        if "gzip" in kwargs and "encoding" in kwargs:
            raise PodmanError(
                "Custom encoding not supported when gzip enabled.")

        # All unsupported kwargs are silently ignored
        params = {
            "dockerfile": kwargs.get("dockerfile", None),
            "forcerm": kwargs.get("forcerm", None),
            "httpproxy": kwargs.get("http_proxy", None),
            "networkmode": kwargs.get("network_mode", None),
            "nocache": kwargs.get("nocache", None),
            "platform": kwargs.get("platform", None),
            "pull": kwargs.get("pull", None),
            "q": kwargs.get("quiet", None),
            "remote": kwargs.get("remote", None),
            "rm": kwargs.get("rm", None),
            "shmsize": kwargs.get("shmsize", None),
            "squash": kwargs.get("squash", None),
            "t": kwargs.get("tag", None),
            "target": kwargs.get("target", None),
        }

        if "buildargs" in kwargs:
            params["buildargs"] = json.dumps(kwargs.get("buildargs"))

        if "cache_from" in kwargs:
            params["cacheform"] = json.dumps(kwargs.get("cache_from"))

        if "container_limits" in kwargs:
            params["cpuperiod"] = kwargs["container_limits"].get(
                "cpuperiod", None)
            params["cpuquota"] = kwargs["container_limits"].get(
                "cpuquota", None)
            params["cpusetcpus"] = kwargs["container_limits"].get(
                "cpusetcpus", None)
            params["cpushares"] = kwargs["container_limits"].get(
                "cpushares", None)
            params["memory"] = kwargs["container_limits"].get("memory", None)
            params["memswap"] = kwargs["container_limits"].get("memswap", None)

        if "extra_hosts" in kwargs:
            params["extrahosts"] = json.dumps(kwargs.get("extra_hosts"))

        if "labels" in kwargs:
            params["labels"] = json.dumps(kwargs.get("labels"))

        params = dict(filter(lambda e: e[1] is not None, params.items()))

        context_dir = params.pop("path", None)
        timeout = params.pop("timeout", api.DEFAULT_TIMEOUT)

        body = None
        if context_dir:
            # The Dockerfile will be copied into the context_dir if needed
            params["dockerfile"] = api.prepare_dockerfile(
                context_dir, params["dockerfile"])

            excludes = api.prepare_dockerignore(context_dir)
            body = api.create_tar(context_dir,
                                  exclude=excludes,
                                  gzip=kwargs.get("gzip", False))

        response = self.client.post(
            "/build",
            params=params,
            data=body,
            headers={
                "Content-type": "application/x-tar",
                "X-Registry-Config": "TODO",
            },
            stream=True,
            timeout=timeout,
        )
        if hasattr(body, "close"):
            body.close()

        if response.status_code != 200:
            body = response.json()
            raise APIError(body["cause"],
                           response=response,
                           explanation=body["message"])

        image_id = unknown = None
        marker = re.compile(r'(^Successfully built |sha256:)([0-9a-f]+)$')
        report_stream, stream = itertools.tee(response.iter_lines())
        for line in stream:
            result = json.loads(line)
            if "error" in result:
                raise BuildError(result["error"], report_stream)
            if "stream" in result:
                match = marker.match(result["stream"])
                if match:
                    image_id = match.group(2)
            unknown = line

        if image_id:
            return self.get(image_id), report_stream

        raise BuildError(unknown or 'Unknown', report_stream)