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