github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/varlinkapi/images.go (about)

     1  // +build varlink
     2  
     3  package varlinkapi
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/containers/buildah"
    18  	"github.com/containers/buildah/imagebuildah"
    19  	dockerarchive "github.com/containers/image/v5/docker/archive"
    20  	"github.com/containers/image/v5/manifest"
    21  	"github.com/containers/image/v5/transports/alltransports"
    22  	"github.com/containers/image/v5/types"
    23  	"github.com/containers/libpod/cmd/podman/shared"
    24  	"github.com/containers/libpod/libpod"
    25  	"github.com/containers/libpod/libpod/define"
    26  	"github.com/containers/libpod/libpod/image"
    27  	"github.com/containers/libpod/pkg/channelwriter"
    28  	"github.com/containers/libpod/pkg/util"
    29  	iopodman "github.com/containers/libpod/pkg/varlink"
    30  	"github.com/containers/libpod/utils"
    31  	"github.com/containers/storage/pkg/archive"
    32  	v1 "github.com/opencontainers/image-spec/specs-go/v1"
    33  	"github.com/pkg/errors"
    34  	"github.com/sirupsen/logrus"
    35  )
    36  
    37  // ListImagesWithFilters returns a list of images that have been filtered
    38  func (i *VarlinkAPI) ListImagesWithFilters(call iopodman.VarlinkCall, filters []string) error {
    39  	images, err := i.Runtime.ImageRuntime().GetImagesWithFilters(filters)
    40  	if err != nil {
    41  		return call.ReplyErrorOccurred(fmt.Sprintf("unable to get list of images %q", err))
    42  	}
    43  	imageList, err := imagesToImageList(images)
    44  	if err != nil {
    45  		return call.ReplyErrorOccurred("unable to parse response " + err.Error())
    46  	}
    47  	return call.ReplyListImagesWithFilters(imageList)
    48  }
    49  
    50  // imagesToImageList converts a slice of Images to an imagelist for varlink responses
    51  func imagesToImageList(images []*image.Image) ([]iopodman.Image, error) {
    52  	var imageList []iopodman.Image
    53  	for _, img := range images {
    54  		labels, _ := img.Labels(getContext())
    55  		containers, _ := img.Containers()
    56  		repoDigests, err := img.RepoDigests()
    57  		if err != nil {
    58  			return nil, err
    59  		}
    60  
    61  		size, _ := img.Size(getContext())
    62  		isParent, err := img.IsParent(context.TODO())
    63  		if err != nil {
    64  			return nil, err
    65  		}
    66  
    67  		i := iopodman.Image{
    68  			Id:          img.ID(),
    69  			Digest:      string(img.Digest()),
    70  			ParentId:    img.Parent,
    71  			RepoTags:    img.Names(),
    72  			RepoDigests: repoDigests,
    73  			Created:     img.Created().Format(time.RFC3339),
    74  			Size:        int64(*size),
    75  			VirtualSize: img.VirtualSize,
    76  			Containers:  int64(len(containers)),
    77  			Labels:      labels,
    78  			IsParent:    isParent,
    79  			ReadOnly:    img.IsReadOnly(),
    80  			History:     img.NamesHistory(),
    81  		}
    82  		imageList = append(imageList, i)
    83  	}
    84  	return imageList, nil
    85  }
    86  
    87  // ListImages lists all the images in the store
    88  // It requires no inputs.
    89  func (i *VarlinkAPI) ListImages(call iopodman.VarlinkCall) error {
    90  	images, err := i.Runtime.ImageRuntime().GetImages()
    91  	if err != nil {
    92  		return call.ReplyErrorOccurred("unable to get list of images " + err.Error())
    93  	}
    94  	imageList, err := imagesToImageList(images)
    95  	if err != nil {
    96  		return call.ReplyErrorOccurred("unable to parse response " + err.Error())
    97  	}
    98  	return call.ReplyListImages(imageList)
    99  }
   100  
   101  // GetImage returns a single image in the form of a Image
   102  func (i *VarlinkAPI) GetImage(call iopodman.VarlinkCall, id string) error {
   103  	newImage, err := i.Runtime.ImageRuntime().NewFromLocal(id)
   104  	if err != nil {
   105  		return call.ReplyImageNotFound(id, err.Error())
   106  	}
   107  	labels, err := newImage.Labels(getContext())
   108  	if err != nil {
   109  		return err
   110  	}
   111  	containers, err := newImage.Containers()
   112  	if err != nil {
   113  		return err
   114  	}
   115  	repoDigests, err := newImage.RepoDigests()
   116  	if err != nil {
   117  		return err
   118  	}
   119  	size, err := newImage.Size(getContext())
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	il := iopodman.Image{
   125  		Id:          newImage.ID(),
   126  		ParentId:    newImage.Parent,
   127  		RepoTags:    newImage.Names(),
   128  		RepoDigests: repoDigests,
   129  		Created:     newImage.Created().Format(time.RFC3339),
   130  		Size:        int64(*size),
   131  		VirtualSize: newImage.VirtualSize,
   132  		Containers:  int64(len(containers)),
   133  		Labels:      labels,
   134  		TopLayer:    newImage.TopLayer(),
   135  		ReadOnly:    newImage.IsReadOnly(),
   136  		History:     newImage.NamesHistory(),
   137  	}
   138  	return call.ReplyGetImage(il)
   139  }
   140  
   141  // BuildImage ...
   142  func (i *VarlinkAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildInfo) error {
   143  	var (
   144  		namespace []buildah.NamespaceOption
   145  		imageID   string
   146  		err       error
   147  	)
   148  
   149  	contextDir := config.ContextDir
   150  
   151  	newContextDir, err := ioutil.TempDir("", "buildTarball")
   152  	if err != nil {
   153  		call.ReplyErrorOccurred("unable to create tempdir")
   154  	}
   155  	logrus.Debugf("created new context dir at %s", newContextDir)
   156  
   157  	reader, err := os.Open(contextDir)
   158  	if err != nil {
   159  		logrus.Errorf("failed to open the context dir tar file %s", contextDir)
   160  		return call.ReplyErrorOccurred(fmt.Sprintf("unable to open context dir tar file %s", contextDir))
   161  	}
   162  	defer reader.Close()
   163  	if err := archive.Untar(reader, newContextDir, &archive.TarOptions{}); err != nil {
   164  		logrus.Errorf("fail to untar the context dir tarball (%s) to the context dir (%s)", contextDir, newContextDir)
   165  		return call.ReplyErrorOccurred(fmt.Sprintf("unable to untar context dir %s", contextDir))
   166  	}
   167  	logrus.Debugf("untar of %s successful", contextDir)
   168  	defer func() {
   169  		if err := os.Remove(contextDir); err != nil {
   170  			logrus.Errorf("unable to delete file '%s': %q", contextDir, err)
   171  		}
   172  		if err := os.RemoveAll(newContextDir); err != nil {
   173  			logrus.Errorf("unable to delete directory '%s': %q", newContextDir, err)
   174  		}
   175  	}()
   176  
   177  	systemContext := types.SystemContext{}
   178  	// All output (stdout, stderr) is captured in output as well
   179  	var output bytes.Buffer
   180  
   181  	commonOpts := &buildah.CommonBuildOptions{
   182  		AddHost:      config.BuildOptions.AddHosts,
   183  		CgroupParent: config.BuildOptions.CgroupParent,
   184  		CPUPeriod:    uint64(config.BuildOptions.CpuPeriod),
   185  		CPUQuota:     config.BuildOptions.CpuQuota,
   186  		CPUSetCPUs:   config.BuildOptions.CpusetCpus,
   187  		CPUSetMems:   config.BuildOptions.CpusetMems,
   188  		Memory:       config.BuildOptions.Memory,
   189  		MemorySwap:   config.BuildOptions.MemorySwap,
   190  		ShmSize:      config.BuildOptions.ShmSize,
   191  		Ulimit:       config.BuildOptions.Ulimit,
   192  		Volumes:      config.BuildOptions.Volume,
   193  	}
   194  
   195  	options := imagebuildah.BuildOptions{
   196  		AddCapabilities:         config.AddCapabilities,
   197  		AdditionalTags:          config.AdditionalTags,
   198  		Annotations:             config.Annotations,
   199  		Architecture:            config.Architecture,
   200  		Args:                    config.BuildArgs,
   201  		CNIConfigDir:            config.CniConfigDir,
   202  		CNIPluginPath:           config.CniPluginDir,
   203  		CommonBuildOpts:         commonOpts,
   204  		Compression:             stringCompressionToArchiveType(config.Compression),
   205  		ContextDirectory:        newContextDir,
   206  		DefaultMountsFilePath:   config.DefaultsMountFilePath,
   207  		Devices:                 config.Devices,
   208  		Err:                     &output,
   209  		ForceRmIntermediateCtrs: config.ForceRmIntermediateCtrs,
   210  		IIDFile:                 config.Iidfile,
   211  		Labels:                  config.Label,
   212  		Layers:                  config.Layers,
   213  		NamespaceOptions:        namespace,
   214  		NoCache:                 config.Nocache,
   215  		OS:                      config.Os,
   216  		Out:                     &output,
   217  		Output:                  config.Output,
   218  		OutputFormat:            config.OutputFormat,
   219  		PullPolicy:              stringPullPolicyToType(config.PullPolicy),
   220  		Quiet:                   config.Quiet,
   221  		RemoveIntermediateCtrs:  config.RemoteIntermediateCtrs,
   222  		ReportWriter:            &output,
   223  		RuntimeArgs:             config.RuntimeArgs,
   224  		SignBy:                  config.SignBy,
   225  		Squash:                  config.Squash,
   226  		SystemContext:           &systemContext,
   227  		Target:                  config.Target,
   228  		TransientMounts:         config.TransientMounts,
   229  	}
   230  
   231  	if call.WantsMore() {
   232  		call.Continues = true
   233  	} else {
   234  		return call.ReplyErrorOccurred("endpoint requires a more connection")
   235  	}
   236  
   237  	var newPathDockerFiles []string
   238  
   239  	for _, d := range config.Dockerfiles {
   240  		if strings.HasPrefix(d, "http://") ||
   241  			strings.HasPrefix(d, "https://") ||
   242  			strings.HasPrefix(d, "git://") ||
   243  			strings.HasPrefix(d, "github.com/") {
   244  			newPathDockerFiles = append(newPathDockerFiles, d)
   245  			continue
   246  		}
   247  		base := filepath.Base(d)
   248  		newPathDockerFiles = append(newPathDockerFiles, filepath.Join(newContextDir, base))
   249  	}
   250  
   251  	c := make(chan error)
   252  	go func() {
   253  		iid, _, err := i.Runtime.Build(getContext(), options, newPathDockerFiles...)
   254  		imageID = iid
   255  		c <- err
   256  		close(c)
   257  	}()
   258  
   259  	var log []string
   260  	done := false
   261  	for {
   262  		outputLine, err := output.ReadString('\n')
   263  		if err == nil {
   264  			log = append(log, outputLine)
   265  			if call.WantsMore() {
   266  				//	 we want to reply with what we have
   267  				br := iopodman.MoreResponse{
   268  					Logs: log,
   269  				}
   270  				call.ReplyBuildImage(br)
   271  				log = []string{}
   272  			}
   273  			continue
   274  		} else if err == io.EOF {
   275  			select {
   276  			case err := <-c:
   277  				if err != nil {
   278  					return call.ReplyErrorOccurred(err.Error())
   279  				}
   280  				done = true
   281  			default:
   282  				if call.WantsMore() {
   283  					time.Sleep(1 * time.Second)
   284  					break
   285  				}
   286  			}
   287  		} else {
   288  			return call.ReplyErrorOccurred(err.Error())
   289  		}
   290  		if done {
   291  			break
   292  		}
   293  	}
   294  	call.Continues = false
   295  
   296  	br := iopodman.MoreResponse{
   297  		Logs: log,
   298  		Id:   imageID,
   299  	}
   300  	return call.ReplyBuildImage(br)
   301  }
   302  
   303  // InspectImage returns an image's inspect information as a string that can be serialized.
   304  // Requires an image ID or name
   305  func (i *VarlinkAPI) InspectImage(call iopodman.VarlinkCall, name string) error {
   306  	newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
   307  	if err != nil {
   308  		return call.ReplyImageNotFound(name, err.Error())
   309  	}
   310  	inspectInfo, err := newImage.Inspect(getContext())
   311  	if err != nil {
   312  		return call.ReplyErrorOccurred(err.Error())
   313  	}
   314  	b, err := json.Marshal(inspectInfo)
   315  	if err != nil {
   316  		return call.ReplyErrorOccurred(fmt.Sprintf("unable to serialize"))
   317  	}
   318  	return call.ReplyInspectImage(string(b))
   319  }
   320  
   321  // HistoryImage returns the history of the image's layers
   322  // Requires an image or name
   323  func (i *VarlinkAPI) HistoryImage(call iopodman.VarlinkCall, name string) error {
   324  	newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
   325  	if err != nil {
   326  		return call.ReplyImageNotFound(name, err.Error())
   327  	}
   328  	history, err := newImage.History(getContext())
   329  	if err != nil {
   330  		return call.ReplyErrorOccurred(err.Error())
   331  	}
   332  	var histories []iopodman.ImageHistory
   333  	for _, hist := range history {
   334  		imageHistory := iopodman.ImageHistory{
   335  			Id:        hist.ID,
   336  			Created:   hist.Created.Format(time.RFC3339),
   337  			CreatedBy: hist.CreatedBy,
   338  			Tags:      newImage.Names(),
   339  			Size:      hist.Size,
   340  			Comment:   hist.Comment,
   341  		}
   342  		histories = append(histories, imageHistory)
   343  	}
   344  	return call.ReplyHistoryImage(histories)
   345  }
   346  
   347  // PushImage pushes an local image to registry
   348  func (i *VarlinkAPI) PushImage(call iopodman.VarlinkCall, name, tag string, compress bool, format string, removeSignatures bool, signBy string) error {
   349  	var (
   350  		manifestType string
   351  	)
   352  	newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
   353  	if err != nil {
   354  		return call.ReplyImageNotFound(name, err.Error())
   355  	}
   356  	destname := name
   357  	if tag != "" {
   358  		destname = tag
   359  	}
   360  	dockerRegistryOptions := image.DockerRegistryOptions{}
   361  	if format != "" {
   362  		switch format {
   363  		case "oci": // nolint
   364  			manifestType = v1.MediaTypeImageManifest
   365  		case "v2s1":
   366  			manifestType = manifest.DockerV2Schema1SignedMediaType
   367  		case "v2s2", "docker":
   368  			manifestType = manifest.DockerV2Schema2MediaType
   369  		default:
   370  			return call.ReplyErrorOccurred(fmt.Sprintf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", format))
   371  		}
   372  	}
   373  	so := image.SigningOptions{
   374  		RemoveSignatures: removeSignatures,
   375  		SignBy:           signBy,
   376  	}
   377  
   378  	if call.WantsMore() {
   379  		call.Continues = true
   380  	}
   381  
   382  	output := bytes.NewBuffer([]byte{})
   383  	c := make(chan error)
   384  	go func() {
   385  		writer := bytes.NewBuffer([]byte{})
   386  		err := newImage.PushImageToHeuristicDestination(getContext(), destname, manifestType, "", "", "", writer, compress, so, &dockerRegistryOptions, nil)
   387  		if err != nil {
   388  			c <- err
   389  		}
   390  		_, err = io.CopyBuffer(output, writer, nil)
   391  		c <- err
   392  		close(c)
   393  	}()
   394  
   395  	// TODO When pull output gets fixed for the remote client, we need to look into how we can turn below
   396  	// into something re-usable.  it is in build too
   397  	var log []string
   398  	done := false
   399  	for {
   400  		line, err := output.ReadString('\n')
   401  		if err == nil {
   402  			log = append(log, line)
   403  			continue
   404  		} else if err == io.EOF {
   405  			select {
   406  			case err := <-c:
   407  				if err != nil {
   408  					logrus.Errorf("reading of output during push failed for %s", newImage.ID())
   409  					return call.ReplyErrorOccurred(err.Error())
   410  				}
   411  				done = true
   412  			default:
   413  				if !call.WantsMore() {
   414  					break
   415  				}
   416  				br := iopodman.MoreResponse{
   417  					Logs: log,
   418  					Id:   newImage.ID(),
   419  				}
   420  				call.ReplyPushImage(br)
   421  				log = []string{}
   422  			}
   423  		} else {
   424  			return call.ReplyErrorOccurred(err.Error())
   425  		}
   426  		if done {
   427  			break
   428  		}
   429  	}
   430  	call.Continues = false
   431  
   432  	br := iopodman.MoreResponse{
   433  		Logs: log,
   434  		Id:   newImage.ID(),
   435  	}
   436  	return call.ReplyPushImage(br)
   437  }
   438  
   439  // TagImage accepts an image name and tag as strings and tags an image in the local store.
   440  func (i *VarlinkAPI) TagImage(call iopodman.VarlinkCall, name, tag string) error {
   441  	newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
   442  	if err != nil {
   443  		return call.ReplyImageNotFound(name, err.Error())
   444  	}
   445  	if err := newImage.TagImage(tag); err != nil {
   446  		return call.ReplyErrorOccurred(err.Error())
   447  	}
   448  	return call.ReplyTagImage(newImage.ID())
   449  }
   450  
   451  // UntagImage accepts an image name and tag as strings and removes the tag from the local store.
   452  func (i *VarlinkAPI) UntagImage(call iopodman.VarlinkCall, name, tag string) error {
   453  	newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
   454  	if err != nil {
   455  		return call.ReplyImageNotFound(name, err.Error())
   456  	}
   457  	if err := newImage.UntagImage(tag); err != nil {
   458  		return call.ReplyErrorOccurred(err.Error())
   459  	}
   460  	return call.ReplyUntagImage(newImage.ID())
   461  }
   462  
   463  // RemoveImage accepts a image name or ID as a string and force bool to determine if it should
   464  // remove the image even if being used by stopped containers
   465  func (i *VarlinkAPI) RemoveImage(call iopodman.VarlinkCall, name string, force bool) error {
   466  	ctx := getContext()
   467  	newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
   468  	if err != nil {
   469  		return call.ReplyImageNotFound(name, err.Error())
   470  	}
   471  	_, err = i.Runtime.RemoveImage(ctx, newImage, force)
   472  	if err != nil {
   473  		return call.ReplyErrorOccurred(err.Error())
   474  	}
   475  	return call.ReplyRemoveImage(newImage.ID())
   476  }
   477  
   478  // RemoveImageWithResponse accepts an image name and force bool.  It returns details about what
   479  // was done in removeimageresponse struct.
   480  func (i *VarlinkAPI) RemoveImageWithResponse(call iopodman.VarlinkCall, name string, force bool) error {
   481  	ir := iopodman.RemoveImageResponse{}
   482  	ctx := getContext()
   483  	newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
   484  	if err != nil {
   485  		return call.ReplyImageNotFound(name, err.Error())
   486  	}
   487  	response, err := i.Runtime.RemoveImage(ctx, newImage, force)
   488  	if err != nil {
   489  		return call.ReplyErrorOccurred(err.Error())
   490  	}
   491  	ir.Untagged = append(ir.Untagged, response.Untagged...)
   492  	ir.Deleted = response.Deleted
   493  	return call.ReplyRemoveImageWithResponse(ir)
   494  }
   495  
   496  // SearchImages searches all registries configured in /etc/containers/registries.conf for an image
   497  // Requires an image name and a search limit as int
   498  func (i *VarlinkAPI) SearchImages(call iopodman.VarlinkCall, query string, limit *int64, filter iopodman.ImageSearchFilter) error {
   499  	// Transform all arguments to proper types first
   500  	argLimit := 0
   501  	argIsOfficial := types.OptionalBoolUndefined
   502  	argIsAutomated := types.OptionalBoolUndefined
   503  	if limit != nil {
   504  		argLimit = int(*limit)
   505  	}
   506  	if filter.Is_official != nil {
   507  		argIsOfficial = types.NewOptionalBool(*filter.Is_official)
   508  	}
   509  	if filter.Is_automated != nil {
   510  		argIsAutomated = types.NewOptionalBool(*filter.Is_automated)
   511  	}
   512  
   513  	// Transform a SearchFilter the backend can deal with
   514  	sFilter := image.SearchFilter{
   515  		IsOfficial:  argIsOfficial,
   516  		IsAutomated: argIsAutomated,
   517  		Stars:       int(filter.Star_count),
   518  	}
   519  
   520  	searchOptions := image.SearchOptions{
   521  		Limit:  argLimit,
   522  		Filter: sFilter,
   523  	}
   524  	results, err := image.SearchImages(query, searchOptions)
   525  	if err != nil {
   526  		return call.ReplyErrorOccurred(err.Error())
   527  	}
   528  
   529  	var imageResults []iopodman.ImageSearchResult
   530  	for _, result := range results {
   531  		i := iopodman.ImageSearchResult{
   532  			Registry:     result.Index,
   533  			Description:  result.Description,
   534  			Is_official:  result.Official == "[OK]",
   535  			Is_automated: result.Automated == "[OK]",
   536  			Name:         result.Name,
   537  			Star_count:   int64(result.Stars),
   538  		}
   539  		imageResults = append(imageResults, i)
   540  	}
   541  	return call.ReplySearchImages(imageResults)
   542  }
   543  
   544  // DeleteUnusedImages deletes any images that do not have containers associated with it.
   545  // TODO Filters are not implemented
   546  func (i *VarlinkAPI) DeleteUnusedImages(call iopodman.VarlinkCall) error {
   547  	images, err := i.Runtime.ImageRuntime().GetImages()
   548  	if err != nil {
   549  		return call.ReplyErrorOccurred(err.Error())
   550  	}
   551  	var deletedImages []string
   552  	for _, img := range images {
   553  		containers, err := img.Containers()
   554  		if err != nil {
   555  			return call.ReplyErrorOccurred(err.Error())
   556  		}
   557  		if len(containers) == 0 {
   558  			if err := img.Remove(context.TODO(), false); err != nil {
   559  				return call.ReplyErrorOccurred(err.Error())
   560  			}
   561  			deletedImages = append(deletedImages, img.ID())
   562  		}
   563  	}
   564  	return call.ReplyDeleteUnusedImages(deletedImages)
   565  }
   566  
   567  // Commit ...
   568  func (i *VarlinkAPI) Commit(call iopodman.VarlinkCall, name, imageName string, changes []string, author, message string, pause bool, manifestType string) error {
   569  	var (
   570  		newImage *image.Image
   571  		log      []string
   572  		mimeType string
   573  	)
   574  	output := channelwriter.NewChannelWriter()
   575  	channelClose := func() {
   576  		if err := output.Close(); err != nil {
   577  			logrus.Errorf("failed to close channel writer: %q", err)
   578  		}
   579  	}
   580  	defer channelClose()
   581  
   582  	ctr, err := i.Runtime.LookupContainer(name)
   583  	if err != nil {
   584  		return call.ReplyContainerNotFound(name, err.Error())
   585  	}
   586  	rtc, err := i.Runtime.GetConfig()
   587  	if err != nil {
   588  		return call.ReplyErrorOccurred(err.Error())
   589  	}
   590  	sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
   591  	switch manifestType {
   592  	case "oci", "": // nolint
   593  		mimeType = buildah.OCIv1ImageManifest
   594  	case "docker":
   595  		mimeType = manifest.DockerV2Schema2MediaType
   596  	default:
   597  		return call.ReplyErrorOccurred(fmt.Sprintf("unrecognized image format %q", manifestType))
   598  	}
   599  	coptions := buildah.CommitOptions{
   600  		SignaturePolicyPath:   rtc.Engine.SignaturePolicyPath,
   601  		ReportWriter:          output,
   602  		SystemContext:         sc,
   603  		PreferredManifestType: mimeType,
   604  	}
   605  	options := libpod.ContainerCommitOptions{
   606  		CommitOptions: coptions,
   607  		Pause:         pause,
   608  		Message:       message,
   609  		Changes:       changes,
   610  		Author:        author,
   611  	}
   612  
   613  	if call.WantsMore() {
   614  		call.Continues = true
   615  	}
   616  
   617  	c := make(chan error)
   618  
   619  	go func() {
   620  		newImage, err = ctr.Commit(getContext(), imageName, options)
   621  		if err != nil {
   622  			c <- err
   623  		}
   624  		c <- nil
   625  		close(c)
   626  	}()
   627  
   628  	// reply is the func being sent to the output forwarder.  in this case it is replying
   629  	// with a more response struct
   630  	reply := func(br iopodman.MoreResponse) error {
   631  		return call.ReplyCommit(br)
   632  	}
   633  	log, err = forwardOutput(log, c, call.WantsMore(), output, reply)
   634  	if err != nil {
   635  		return call.ReplyErrorOccurred(err.Error())
   636  	}
   637  	call.Continues = false
   638  	br := iopodman.MoreResponse{
   639  		Logs: log,
   640  		Id:   newImage.ID(),
   641  	}
   642  	return call.ReplyCommit(br)
   643  }
   644  
   645  // ImportImage imports an image from a tarball to the image store
   646  func (i *VarlinkAPI) ImportImage(call iopodman.VarlinkCall, source, reference, message string, changes []string, delete bool) error {
   647  	configChanges, err := util.GetImageConfig(changes)
   648  	if err != nil {
   649  		return call.ReplyErrorOccurred(err.Error())
   650  	}
   651  	history := []v1.History{
   652  		{Comment: message},
   653  	}
   654  	config := v1.Image{
   655  		Config:  configChanges.ImageConfig,
   656  		History: history,
   657  	}
   658  	newImage, err := i.Runtime.ImageRuntime().Import(getContext(), source, reference, nil, image.SigningOptions{}, config)
   659  	if err != nil {
   660  		return call.ReplyErrorOccurred(err.Error())
   661  	}
   662  	if delete {
   663  		if err := os.Remove(source); err != nil {
   664  			return call.ReplyErrorOccurred(err.Error())
   665  		}
   666  	}
   667  
   668  	return call.ReplyImportImage(newImage.ID())
   669  }
   670  
   671  // ExportImage exports an image to the provided destination
   672  // destination must have the transport type!!
   673  func (i *VarlinkAPI) ExportImage(call iopodman.VarlinkCall, name, destination string, compress bool, tags []string) error {
   674  	newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
   675  	if err != nil {
   676  		return call.ReplyImageNotFound(name, err.Error())
   677  	}
   678  
   679  	additionalTags, err := image.GetAdditionalTags(tags)
   680  	if err != nil {
   681  		return err
   682  	}
   683  
   684  	if err := newImage.PushImageToHeuristicDestination(getContext(), destination, "", "", "", "", nil, compress, image.SigningOptions{}, &image.DockerRegistryOptions{}, additionalTags); err != nil {
   685  		return call.ReplyErrorOccurred(err.Error())
   686  	}
   687  	return call.ReplyExportImage(newImage.ID())
   688  }
   689  
   690  // PullImage pulls an image from a registry to the image store.
   691  func (i *VarlinkAPI) PullImage(call iopodman.VarlinkCall, name string, creds iopodman.AuthConfig) error {
   692  	var (
   693  		imageID string
   694  		err     error
   695  	)
   696  	dockerRegistryOptions := image.DockerRegistryOptions{
   697  		DockerRegistryCreds: &types.DockerAuthConfig{
   698  			Username: creds.Username,
   699  			Password: creds.Password,
   700  		},
   701  	}
   702  
   703  	so := image.SigningOptions{}
   704  
   705  	if call.WantsMore() {
   706  		call.Continues = true
   707  	}
   708  	output := channelwriter.NewChannelWriter()
   709  	channelClose := func() {
   710  		if err := output.Close(); err != nil {
   711  			logrus.Errorf("failed to close channel writer: %q", err)
   712  		}
   713  	}
   714  	defer channelClose()
   715  	c := make(chan error)
   716  	defer close(c)
   717  
   718  	go func() {
   719  		var foundError bool
   720  		if strings.HasPrefix(name, dockerarchive.Transport.Name()+":") {
   721  			srcRef, err := alltransports.ParseImageName(name)
   722  			if err != nil {
   723  				c <- errors.Wrapf(err, "error parsing %q", name)
   724  			}
   725  			newImage, err := i.Runtime.ImageRuntime().LoadFromArchiveReference(getContext(), srcRef, "", output)
   726  			if err != nil {
   727  				foundError = true
   728  				c <- errors.Wrapf(err, "error pulling image from %q", name)
   729  			} else {
   730  				imageID = newImage[0].ID()
   731  			}
   732  		} else {
   733  			newImage, err := i.Runtime.ImageRuntime().New(getContext(), name, "", "", output, &dockerRegistryOptions, so, nil, util.PullImageMissing)
   734  			if err != nil {
   735  				foundError = true
   736  				c <- errors.Wrapf(err, "unable to pull %s", name)
   737  			} else {
   738  				imageID = newImage.ID()
   739  			}
   740  		}
   741  		if !foundError {
   742  			c <- nil
   743  		}
   744  	}()
   745  
   746  	var log []string
   747  	reply := func(br iopodman.MoreResponse) error {
   748  		return call.ReplyPullImage(br)
   749  	}
   750  	log, err = forwardOutput(log, c, call.WantsMore(), output, reply)
   751  	if err != nil {
   752  		return call.ReplyErrorOccurred(err.Error())
   753  	}
   754  	call.Continues = false
   755  	br := iopodman.MoreResponse{
   756  		Logs: log,
   757  		Id:   imageID,
   758  	}
   759  	return call.ReplyPullImage(br)
   760  }
   761  
   762  // ImageExists returns bool as to whether the input image exists in local storage
   763  func (i *VarlinkAPI) ImageExists(call iopodman.VarlinkCall, name string) error {
   764  	_, err := i.Runtime.ImageRuntime().NewFromLocal(name)
   765  	if errors.Cause(err) == image.ErrNoSuchImage {
   766  		return call.ReplyImageExists(1)
   767  	}
   768  	if err != nil {
   769  		return call.ReplyErrorOccurred(err.Error())
   770  	}
   771  	return call.ReplyImageExists(0)
   772  }
   773  
   774  // ContainerRunlabel ...
   775  func (i *VarlinkAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.Runlabel) error {
   776  	ctx := getContext()
   777  	dockerRegistryOptions := image.DockerRegistryOptions{}
   778  	stdErr := os.Stderr
   779  	stdOut := os.Stdout
   780  	stdIn := os.Stdin
   781  
   782  	runLabel, imageName, err := shared.GetRunlabel(input.Label, input.Image, ctx, i.Runtime, input.Pull, "", dockerRegistryOptions, input.Authfile, "", nil)
   783  	if err != nil {
   784  		return call.ReplyErrorOccurred(err.Error())
   785  	}
   786  	if runLabel == "" {
   787  		return call.ReplyErrorOccurred(fmt.Sprintf("%s does not contain the label %s", input.Image, input.Label))
   788  	}
   789  
   790  	cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs, "")
   791  	if err != nil {
   792  		return call.ReplyErrorOccurred(err.Error())
   793  	}
   794  	if err := utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...); err != nil {
   795  		return call.ReplyErrorOccurred(err.Error())
   796  	}
   797  	return call.ReplyContainerRunlabel()
   798  }
   799  
   800  // ImagesPrune ....
   801  func (i *VarlinkAPI) ImagesPrune(call iopodman.VarlinkCall, all bool, filter []string) error {
   802  	prunedImages, err := i.Runtime.ImageRuntime().PruneImages(context.TODO(), all, []string{})
   803  	if err != nil {
   804  		return call.ReplyErrorOccurred(err.Error())
   805  	}
   806  	return call.ReplyImagesPrune(prunedImages)
   807  }
   808  
   809  // ImageSave ....
   810  func (i *VarlinkAPI) ImageSave(call iopodman.VarlinkCall, options iopodman.ImageSaveOptions) error {
   811  	newImage, err := i.Runtime.ImageRuntime().NewFromLocal(options.Name)
   812  	if err != nil {
   813  		if errors.Cause(err) == define.ErrNoSuchImage {
   814  			return call.ReplyImageNotFound(options.Name, err.Error())
   815  		}
   816  		return call.ReplyErrorOccurred(err.Error())
   817  	}
   818  
   819  	// Determine if we are dealing with a tarball or dir
   820  	var output string
   821  	outputToDir := false
   822  	if options.Format == "oci-archive" || options.Format == "docker-archive" {
   823  		tempfile, err := ioutil.TempFile("", "varlink_send")
   824  		if err != nil {
   825  			return call.ReplyErrorOccurred(err.Error())
   826  		}
   827  		output = tempfile.Name()
   828  		tempfile.Close()
   829  	} else {
   830  		var err error
   831  		outputToDir = true
   832  		output, err = ioutil.TempDir("", "varlink_send")
   833  		if err != nil {
   834  			return call.ReplyErrorOccurred(err.Error())
   835  		}
   836  	}
   837  	if err != nil {
   838  		return call.ReplyErrorOccurred(err.Error())
   839  	}
   840  	if call.WantsMore() {
   841  		call.Continues = true
   842  	}
   843  
   844  	saveOutput := bytes.NewBuffer([]byte{})
   845  	c := make(chan error)
   846  	go func() {
   847  		err := newImage.Save(getContext(), options.Name, options.Format, output, options.MoreTags, options.Quiet, options.Compress)
   848  		c <- err
   849  		close(c)
   850  	}()
   851  	var log []string
   852  	done := false
   853  	for {
   854  		line, err := saveOutput.ReadString('\n')
   855  		if err == nil {
   856  			log = append(log, line)
   857  			continue
   858  		} else if err == io.EOF {
   859  			select {
   860  			case err := <-c:
   861  				if err != nil {
   862  					logrus.Errorf("reading of output during save failed for %s", newImage.ID())
   863  					return call.ReplyErrorOccurred(err.Error())
   864  				}
   865  				done = true
   866  			default:
   867  				if !call.WantsMore() {
   868  					break
   869  				}
   870  				br := iopodman.MoreResponse{
   871  					Logs: log,
   872  				}
   873  				call.ReplyImageSave(br)
   874  				log = []string{}
   875  			}
   876  		} else {
   877  			return call.ReplyErrorOccurred(err.Error())
   878  		}
   879  		if done {
   880  			break
   881  		}
   882  	}
   883  	call.Continues = false
   884  
   885  	sendfile := output
   886  	// Image has been saved to `output`
   887  	if outputToDir {
   888  		// If the output is a directory, we need to tar up the directory to send it back
   889  		// Create a tempfile for the directory tarball
   890  		outputFile, err := ioutil.TempFile("", "varlink_save_dir")
   891  		if err != nil {
   892  			return err
   893  		}
   894  		defer outputFile.Close()
   895  		if err := utils.TarToFilesystem(output, outputFile); err != nil {
   896  			return call.ReplyErrorOccurred(err.Error())
   897  		}
   898  		sendfile = outputFile.Name()
   899  	}
   900  	br := iopodman.MoreResponse{
   901  		Logs: log,
   902  		Id:   sendfile,
   903  	}
   904  	return call.ReplyPushImage(br)
   905  }
   906  
   907  // LoadImage ...
   908  func (i *VarlinkAPI) LoadImage(call iopodman.VarlinkCall, name, inputFile string, deleteInputFile, quiet bool) error {
   909  	var (
   910  		names  string
   911  		writer io.Writer
   912  		err    error
   913  	)
   914  	if !quiet {
   915  		writer = os.Stderr
   916  	}
   917  
   918  	if call.WantsMore() {
   919  		call.Continues = true
   920  	}
   921  	output := bytes.NewBuffer([]byte{})
   922  
   923  	c := make(chan error)
   924  	go func() {
   925  		names, err = i.Runtime.LoadImage(getContext(), name, inputFile, writer, "")
   926  		c <- err
   927  		close(c)
   928  	}()
   929  
   930  	var log []string
   931  	done := false
   932  	for {
   933  		line, err := output.ReadString('\n')
   934  		if err == nil {
   935  			log = append(log, line)
   936  			continue
   937  		} else if err == io.EOF {
   938  			select {
   939  			case err := <-c:
   940  				if err != nil {
   941  					logrus.Error(err)
   942  					return call.ReplyErrorOccurred(err.Error())
   943  				}
   944  				done = true
   945  			default:
   946  				if !call.WantsMore() {
   947  					break
   948  				}
   949  				br := iopodman.MoreResponse{
   950  					Logs: log,
   951  				}
   952  				call.ReplyLoadImage(br)
   953  				log = []string{}
   954  			}
   955  		} else {
   956  			return call.ReplyErrorOccurred(err.Error())
   957  		}
   958  		if done {
   959  			break
   960  		}
   961  	}
   962  	call.Continues = false
   963  
   964  	br := iopodman.MoreResponse{
   965  		Logs: log,
   966  		Id:   names,
   967  	}
   968  	if deleteInputFile {
   969  		if err := os.Remove(inputFile); err != nil {
   970  			logrus.Errorf("unable to delete input file %s", inputFile)
   971  		}
   972  	}
   973  	return call.ReplyLoadImage(br)
   974  }
   975  
   976  // Diff ...
   977  func (i *VarlinkAPI) Diff(call iopodman.VarlinkCall, name string) error {
   978  	var response []iopodman.DiffInfo
   979  	changes, err := i.Runtime.GetDiff("", name)
   980  	if err != nil {
   981  		return call.ReplyErrorOccurred(err.Error())
   982  	}
   983  	for _, change := range changes {
   984  		response = append(response, iopodman.DiffInfo{Path: change.Path, ChangeType: change.Kind.String()})
   985  	}
   986  	return call.ReplyDiff(response)
   987  }
   988  
   989  // GetLayersMapWithImageInfo is a development only endpoint to obtain layer information for an image.
   990  func (i *VarlinkAPI) GetLayersMapWithImageInfo(call iopodman.VarlinkCall) error {
   991  	layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime())
   992  	if err != nil {
   993  		return call.ReplyErrorOccurred(err.Error())
   994  	}
   995  	b, err := json.Marshal(layerInfo)
   996  	if err != nil {
   997  		return call.ReplyErrorOccurred(err.Error())
   998  	}
   999  	return call.ReplyGetLayersMapWithImageInfo(string(b))
  1000  }
  1001  
  1002  // BuildImageHierarchyMap ...
  1003  func (i *VarlinkAPI) BuildImageHierarchyMap(call iopodman.VarlinkCall, name string) error {
  1004  	img, err := i.Runtime.ImageRuntime().NewFromLocal(name)
  1005  	if err != nil {
  1006  		return call.ReplyErrorOccurred(err.Error())
  1007  	}
  1008  	imageInfo := &image.InfoImage{
  1009  		ID:   img.ID(),
  1010  		Tags: img.Names(),
  1011  	}
  1012  	layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime())
  1013  	if err != nil {
  1014  		return call.ReplyErrorOccurred(err.Error())
  1015  	}
  1016  	if err := image.BuildImageHierarchyMap(imageInfo, layerInfo, img.TopLayer()); err != nil {
  1017  		return call.ReplyErrorOccurred(err.Error())
  1018  	}
  1019  	b, err := json.Marshal(imageInfo)
  1020  	if err != nil {
  1021  		return call.ReplyErrorOccurred(err.Error())
  1022  	}
  1023  	return call.ReplyBuildImageHierarchyMap(string(b))
  1024  }
  1025  
  1026  // ImageTree returns the image tree string for the provided image name or ID
  1027  func (i *VarlinkAPI) ImageTree(call iopodman.VarlinkCall, nameOrID string, whatRequires bool) error {
  1028  	img, err := i.Runtime.ImageRuntime().NewFromLocal(nameOrID)
  1029  	if err != nil {
  1030  		return call.ReplyErrorOccurred(err.Error())
  1031  	}
  1032  
  1033  	tree, err := img.GenerateTree(whatRequires)
  1034  	if err != nil {
  1035  		return call.ReplyErrorOccurred(err.Error())
  1036  	}
  1037  	return call.ReplyImageTree(tree)
  1038  }