text: # coding: utf-8
  
  
  """Provides container image retrieval.
  
  The images are downloaded from a repository.
  Then, they are tagged, saved on the disk and optionally compressed.
  
  All of these actions are done by a single task.
  """
  
  
  import abc
  import itertools
  import operator
  from pathlib import Path
  from typing import Any, Optional, List, Sequence
  
  from buildchain import docker_command
  from buildchain import config
  from buildchain import types
  
  from . import image
  
  
  class RemoteImage(image.ContainerImage):
      """A remote container image to download."""
  
      def __init__(
          self,
          repository: str,
          name: str,
          version: str,
          digest: str,
          destination: Path,
          save_as: Optional[Sequence['ImageSaveFormat']]=None,
          remote_name: Optional[str]=None,
          **kwargs: Any
      ):
          """Initialize a remote container image.
  
          Arguments:
              repository:     repository where the image is stored
              name:           image name
              version:        image version
              digest:         image digest
              destination:    save location for the image
              save_as:        format(s) in which to save the image
              remote_name:    image name in the registry
  
          Keyword Arguments:
              They are passed to `Target` init method.
          """
          self._repository = repository
          self._digest = digest
          self._remote_name = remote_name or name
          self._save_as = save_as or [SaveAsLayers()]
          kwargs.setdefault('task_dep', []).append('check_for:skopeo')
          super().__init__(
              name=name, version=version,
              destination=destination,
              **kwargs
          )
          self._targets = self.filepaths
  
      repository  = property(operator.attrgetter('_repository'))
      digest      = property(operator.attrgetter('_digest'))
      remote_name = property(operator.attrgetter('_remote_name'))
  
      @property
      def remote_fullname(self) -> str:
          """Complete image name retrieved from the remote repository."""
          return (
              "{img.repository}/{img._remote_name}:{img.version}"
          ).format(img=self)
  
      @property
      def fullname(self) -> str:
          """Complete image name to use as a tag before saving with Docker."""
          return "{img.repository}/{img.tag}".format(img=self)
  
      @property
      def filepaths(self) -> List[Path]:
          """Iterate files generated by this task."""
          return [fmt.filepath(self) for fmt in self._save_as]
  
      @property
      def task(self) -> types.TaskDict:
          task = self.basic_task
          task.update({
              'title': lambda _: self.show('PULL IMG'),
              'doc': 'Download {} container image.'.format(self.name),
              'uptodate': [True],
              'actions': list(
                  itertools.chain(*[fmt.save(self) for fmt in self._save_as])
              ),
              'clean': [fmt.clean(self) for fmt in self._save_as],
          })
          return task
  
  
  class ImageSaveFormat(abc.ABC):
      """Interface for an image save format."""
  
      @abc.abstractmethod
      def filepath(self, img: RemoteImage) -> Path:
          """Return the file path of the saved image."""
  
      @abc.abstractmethod
      def save(self, img: RemoteImage) -> List[types.Action]:
          """Return the actions required to save image."""
  
      @abc.abstractmethod
      def clean(self, img: RemoteImage) -> types.Action:
          """Return the action to delete the image."""
  
  
  class SaveAsLayers(ImageSaveFormat):
      """Save an image as layers in a directory."""
      def filepath(self, img: RemoteImage) -> Path:
          return img.dirname/'manifest.json'
  
      def save(self, img: RemoteImage) -> List[types.Action]:
          # Use Skopeo to directly copy the remote image into a directory
          # of image layers
          skopeo_copy = [
              config.ExtCommand.SKOPEO.value, '--override-os', 'linux',
              '--insecure-policy', 'copy', '--format', 'v2s2'
          ]
          skopeo_copy.append('docker://{}'.format(img.remote_fullname))
          skopeo_copy.append('dir:{}'.format(img.dirname))
          return [img.mkdirs, skopeo_copy]
  
      def clean(self, img: RemoteImage) -> types.Action:
          """Return the action to delete the image."""
          return img.clean
  
  
  class SaveAsTar(ImageSaveFormat):
      """Save an image as a tarball."""
      def filepath(self, img: RemoteImage) -> Path:
          filename = '{img.name}-{img.version}.tar'.format(img=img)
          return img.dest_dir/filename
  
      def save(self, img: RemoteImage) -> List[types.Action]:
          qualname = '{img.repository}/{img.name}'.format(img=img)
          # Use Docker to pull, tag, then save the image
          return [(
              docker_command.docker_pull,
              [img.repository, img.remote_name, img.version, img.digest], {}
          ), (
              docker_command.docker_tag,
              [qualname, img.remote_fullname, img.version], {}
          ), (
              docker_command.docker_save,
              [img.fullname, self.filepath(img)], {}
          )]
  
      def clean(self, img: RemoteImage) -> types.Action:
          """Return the action to delete the image."""
          return self.filepath(img).unlink
  