Files
podman/pkg/varlinkapi/images.go
Brent Baude 241326a9a8 Podman V2 birth
remote podman v1 and replace with podman v2.

Signed-off-by: Brent Baude <bbaude@redhat.com>
2020-04-16 15:53:58 -05:00

1038 lines
30 KiB
Go

// +build varlink
package varlinkapi
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"github.com/containers/buildah"
"github.com/containers/buildah/imagebuildah"
dockerarchive "github.com/containers/image/v5/docker/archive"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/channelwriter"
"github.com/containers/libpod/pkg/util"
iopodman "github.com/containers/libpod/pkg/varlink"
"github.com/containers/libpod/utils"
"github.com/containers/storage/pkg/archive"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// ListImagesWithFilters returns a list of images that have been filtered
func (i *VarlinkAPI) ListImagesWithFilters(call iopodman.VarlinkCall, filters []string) error {
images, err := i.Runtime.ImageRuntime().GetImagesWithFilters(filters)
if err != nil {
return call.ReplyErrorOccurred(fmt.Sprintf("unable to get list of images %q", err))
}
imageList, err := imagesToImageList(images)
if err != nil {
return call.ReplyErrorOccurred("unable to parse response " + err.Error())
}
return call.ReplyListImagesWithFilters(imageList)
}
// imagesToImageList converts a slice of Images to an imagelist for varlink responses
func imagesToImageList(images []*image.Image) ([]iopodman.Image, error) {
var imageList []iopodman.Image
for _, img := range images {
labels, _ := img.Labels(getContext())
containers, _ := img.Containers()
repoDigests, err := img.RepoDigests()
if err != nil {
return nil, err
}
size, _ := img.Size(getContext())
isParent, err := img.IsParent(context.TODO())
if err != nil {
return nil, err
}
i := iopodman.Image{
Id: img.ID(),
Digest: string(img.Digest()),
ParentId: img.Parent,
RepoTags: img.Names(),
RepoDigests: repoDigests,
Created: img.Created().Format(time.RFC3339),
Size: int64(*size),
VirtualSize: img.VirtualSize,
Containers: int64(len(containers)),
Labels: labels,
IsParent: isParent,
ReadOnly: img.IsReadOnly(),
History: img.NamesHistory(),
}
imageList = append(imageList, i)
}
return imageList, nil
}
// ListImages lists all the images in the store
// It requires no inputs.
func (i *VarlinkAPI) ListImages(call iopodman.VarlinkCall) error {
images, err := i.Runtime.ImageRuntime().GetImages()
if err != nil {
return call.ReplyErrorOccurred("unable to get list of images " + err.Error())
}
imageList, err := imagesToImageList(images)
if err != nil {
return call.ReplyErrorOccurred("unable to parse response " + err.Error())
}
return call.ReplyListImages(imageList)
}
// GetImage returns a single image in the form of a Image
func (i *VarlinkAPI) GetImage(call iopodman.VarlinkCall, id string) error {
newImage, err := i.Runtime.ImageRuntime().NewFromLocal(id)
if err != nil {
return call.ReplyImageNotFound(id, err.Error())
}
labels, err := newImage.Labels(getContext())
if err != nil {
return err
}
containers, err := newImage.Containers()
if err != nil {
return err
}
repoDigests, err := newImage.RepoDigests()
if err != nil {
return err
}
size, err := newImage.Size(getContext())
if err != nil {
return err
}
il := iopodman.Image{
Id: newImage.ID(),
ParentId: newImage.Parent,
RepoTags: newImage.Names(),
RepoDigests: repoDigests,
Created: newImage.Created().Format(time.RFC3339),
Size: int64(*size),
VirtualSize: newImage.VirtualSize,
Containers: int64(len(containers)),
Labels: labels,
TopLayer: newImage.TopLayer(),
ReadOnly: newImage.IsReadOnly(),
History: newImage.NamesHistory(),
}
return call.ReplyGetImage(il)
}
// BuildImage ...
func (i *VarlinkAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildInfo) error {
var (
namespace []buildah.NamespaceOption
imageID string
err error
)
contextDir := config.ContextDir
newContextDir, err := ioutil.TempDir("", "buildTarball")
if err != nil {
call.ReplyErrorOccurred("unable to create tempdir")
}
logrus.Debugf("created new context dir at %s", newContextDir)
reader, err := os.Open(contextDir)
if err != nil {
logrus.Errorf("failed to open the context dir tar file %s", contextDir)
return call.ReplyErrorOccurred(fmt.Sprintf("unable to open context dir tar file %s", contextDir))
}
defer reader.Close()
if err := archive.Untar(reader, newContextDir, &archive.TarOptions{}); err != nil {
logrus.Errorf("fail to untar the context dir tarball (%s) to the context dir (%s)", contextDir, newContextDir)
return call.ReplyErrorOccurred(fmt.Sprintf("unable to untar context dir %s", contextDir))
}
logrus.Debugf("untar of %s successful", contextDir)
defer func() {
if err := os.Remove(contextDir); err != nil {
logrus.Errorf("unable to delete file '%s': %q", contextDir, err)
}
if err := os.RemoveAll(newContextDir); err != nil {
logrus.Errorf("unable to delete directory '%s': %q", newContextDir, err)
}
}()
systemContext := types.SystemContext{}
// All output (stdout, stderr) is captured in output as well
var output bytes.Buffer
commonOpts := &buildah.CommonBuildOptions{
AddHost: config.BuildOptions.AddHosts,
CgroupParent: config.BuildOptions.CgroupParent,
CPUPeriod: uint64(config.BuildOptions.CpuPeriod),
CPUQuota: config.BuildOptions.CpuQuota,
CPUSetCPUs: config.BuildOptions.CpusetCpus,
CPUSetMems: config.BuildOptions.CpusetMems,
Memory: config.BuildOptions.Memory,
MemorySwap: config.BuildOptions.MemorySwap,
ShmSize: config.BuildOptions.ShmSize,
Ulimit: config.BuildOptions.Ulimit,
Volumes: config.BuildOptions.Volume,
}
options := imagebuildah.BuildOptions{
AddCapabilities: config.AddCapabilities,
AdditionalTags: config.AdditionalTags,
Annotations: config.Annotations,
Architecture: config.Architecture,
Args: config.BuildArgs,
CNIConfigDir: config.CniConfigDir,
CNIPluginPath: config.CniPluginDir,
CommonBuildOpts: commonOpts,
Compression: stringCompressionToArchiveType(config.Compression),
ContextDirectory: newContextDir,
DefaultMountsFilePath: config.DefaultsMountFilePath,
Devices: config.Devices,
Err: &output,
ForceRmIntermediateCtrs: config.ForceRmIntermediateCtrs,
IIDFile: config.Iidfile,
Labels: config.Label,
Layers: config.Layers,
NamespaceOptions: namespace,
NoCache: config.Nocache,
OS: config.Os,
Out: &output,
Output: config.Output,
OutputFormat: config.OutputFormat,
PullPolicy: stringPullPolicyToType(config.PullPolicy),
Quiet: config.Quiet,
RemoveIntermediateCtrs: config.RemoteIntermediateCtrs,
ReportWriter: &output,
RuntimeArgs: config.RuntimeArgs,
SignBy: config.SignBy,
Squash: config.Squash,
SystemContext: &systemContext,
Target: config.Target,
TransientMounts: config.TransientMounts,
}
if call.WantsMore() {
call.Continues = true
} else {
return call.ReplyErrorOccurred("endpoint requires a more connection")
}
var newPathDockerFiles []string
for _, d := range config.Dockerfiles {
if strings.HasPrefix(d, "http://") ||
strings.HasPrefix(d, "https://") ||
strings.HasPrefix(d, "git://") ||
strings.HasPrefix(d, "github.com/") {
newPathDockerFiles = append(newPathDockerFiles, d)
continue
}
base := filepath.Base(d)
newPathDockerFiles = append(newPathDockerFiles, filepath.Join(newContextDir, base))
}
c := make(chan error)
go func() {
iid, _, err := i.Runtime.Build(getContext(), options, newPathDockerFiles...)
imageID = iid
c <- err
close(c)
}()
var log []string
done := false
for {
outputLine, err := output.ReadString('\n')
if err == nil {
log = append(log, outputLine)
if call.WantsMore() {
// we want to reply with what we have
br := iopodman.MoreResponse{
Logs: log,
}
call.ReplyBuildImage(br)
log = []string{}
}
continue
} else if err == io.EOF {
select {
case err := <-c:
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
done = true
default:
if call.WantsMore() {
time.Sleep(1 * time.Second)
break
}
}
} else {
return call.ReplyErrorOccurred(err.Error())
}
if done {
break
}
}
call.Continues = false
br := iopodman.MoreResponse{
Logs: log,
Id: imageID,
}
return call.ReplyBuildImage(br)
}
// InspectImage returns an image's inspect information as a string that can be serialized.
// Requires an image ID or name
func (i *VarlinkAPI) InspectImage(call iopodman.VarlinkCall, name string) error {
newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
if err != nil {
return call.ReplyImageNotFound(name, err.Error())
}
inspectInfo, err := newImage.Inspect(getContext())
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
b, err := json.Marshal(inspectInfo)
if err != nil {
return call.ReplyErrorOccurred(fmt.Sprintf("unable to serialize"))
}
return call.ReplyInspectImage(string(b))
}
// HistoryImage returns the history of the image's layers
// Requires an image or name
func (i *VarlinkAPI) HistoryImage(call iopodman.VarlinkCall, name string) error {
newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
if err != nil {
return call.ReplyImageNotFound(name, err.Error())
}
history, err := newImage.History(getContext())
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
var histories []iopodman.ImageHistory
for _, hist := range history {
imageHistory := iopodman.ImageHistory{
Id: hist.ID,
Created: hist.Created.Format(time.RFC3339),
CreatedBy: hist.CreatedBy,
Tags: newImage.Names(),
Size: hist.Size,
Comment: hist.Comment,
}
histories = append(histories, imageHistory)
}
return call.ReplyHistoryImage(histories)
}
// PushImage pushes an local image to registry
func (i *VarlinkAPI) PushImage(call iopodman.VarlinkCall, name, tag string, compress bool, format string, removeSignatures bool, signBy string) error {
var (
manifestType string
)
newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
if err != nil {
return call.ReplyImageNotFound(name, err.Error())
}
destname := name
if tag != "" {
destname = tag
}
dockerRegistryOptions := image.DockerRegistryOptions{}
if format != "" {
switch format {
case "oci": // nolint
manifestType = v1.MediaTypeImageManifest
case "v2s1":
manifestType = manifest.DockerV2Schema1SignedMediaType
case "v2s2", "docker":
manifestType = manifest.DockerV2Schema2MediaType
default:
return call.ReplyErrorOccurred(fmt.Sprintf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", format))
}
}
so := image.SigningOptions{
RemoveSignatures: removeSignatures,
SignBy: signBy,
}
if call.WantsMore() {
call.Continues = true
}
output := bytes.NewBuffer([]byte{})
c := make(chan error)
go func() {
writer := bytes.NewBuffer([]byte{})
err := newImage.PushImageToHeuristicDestination(getContext(), destname, manifestType, "", "", "", writer, compress, so, &dockerRegistryOptions, nil)
if err != nil {
c <- err
}
_, err = io.CopyBuffer(output, writer, nil)
c <- err
close(c)
}()
// TODO When pull output gets fixed for the remote client, we need to look into how we can turn below
// into something re-usable. it is in build too
var log []string
done := false
for {
line, err := output.ReadString('\n')
if err == nil {
log = append(log, line)
continue
} else if err == io.EOF {
select {
case err := <-c:
if err != nil {
logrus.Errorf("reading of output during push failed for %s", newImage.ID())
return call.ReplyErrorOccurred(err.Error())
}
done = true
default:
if !call.WantsMore() {
break
}
br := iopodman.MoreResponse{
Logs: log,
Id: newImage.ID(),
}
call.ReplyPushImage(br)
log = []string{}
}
} else {
return call.ReplyErrorOccurred(err.Error())
}
if done {
break
}
}
call.Continues = false
br := iopodman.MoreResponse{
Logs: log,
Id: newImage.ID(),
}
return call.ReplyPushImage(br)
}
// TagImage accepts an image name and tag as strings and tags an image in the local store.
func (i *VarlinkAPI) TagImage(call iopodman.VarlinkCall, name, tag string) error {
newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
if err != nil {
return call.ReplyImageNotFound(name, err.Error())
}
if err := newImage.TagImage(tag); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyTagImage(newImage.ID())
}
// UntagImage accepts an image name and tag as strings and removes the tag from the local store.
func (i *VarlinkAPI) UntagImage(call iopodman.VarlinkCall, name, tag string) error {
newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
if err != nil {
return call.ReplyImageNotFound(name, err.Error())
}
if err := newImage.UntagImage(tag); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyUntagImage(newImage.ID())
}
// RemoveImage accepts a image name or ID as a string and force bool to determine if it should
// remove the image even if being used by stopped containers
func (i *VarlinkAPI) RemoveImage(call iopodman.VarlinkCall, name string, force bool) error {
ctx := getContext()
newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
if err != nil {
return call.ReplyImageNotFound(name, err.Error())
}
_, err = i.Runtime.RemoveImage(ctx, newImage, force)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyRemoveImage(newImage.ID())
}
// RemoveImageWithResponse accepts an image name and force bool. It returns details about what
// was done in removeimageresponse struct.
func (i *VarlinkAPI) RemoveImageWithResponse(call iopodman.VarlinkCall, name string, force bool) error {
ir := iopodman.RemoveImageResponse{}
ctx := getContext()
newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
if err != nil {
return call.ReplyImageNotFound(name, err.Error())
}
response, err := i.Runtime.RemoveImage(ctx, newImage, force)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
ir.Untagged = append(ir.Untagged, response.Untagged...)
ir.Deleted = response.Deleted
return call.ReplyRemoveImageWithResponse(ir)
}
// SearchImages searches all registries configured in /etc/containers/registries.conf for an image
// Requires an image name and a search limit as int
func (i *VarlinkAPI) SearchImages(call iopodman.VarlinkCall, query string, limit *int64, filter iopodman.ImageSearchFilter) error {
// Transform all arguments to proper types first
argLimit := 0
argIsOfficial := types.OptionalBoolUndefined
argIsAutomated := types.OptionalBoolUndefined
if limit != nil {
argLimit = int(*limit)
}
if filter.Is_official != nil {
argIsOfficial = types.NewOptionalBool(*filter.Is_official)
}
if filter.Is_automated != nil {
argIsAutomated = types.NewOptionalBool(*filter.Is_automated)
}
// Transform a SearchFilter the backend can deal with
sFilter := image.SearchFilter{
IsOfficial: argIsOfficial,
IsAutomated: argIsAutomated,
Stars: int(filter.Star_count),
}
searchOptions := image.SearchOptions{
Limit: argLimit,
Filter: sFilter,
}
results, err := image.SearchImages(query, searchOptions)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
var imageResults []iopodman.ImageSearchResult
for _, result := range results {
i := iopodman.ImageSearchResult{
Registry: result.Index,
Description: result.Description,
Is_official: result.Official == "[OK]",
Is_automated: result.Automated == "[OK]",
Name: result.Name,
Star_count: int64(result.Stars),
}
imageResults = append(imageResults, i)
}
return call.ReplySearchImages(imageResults)
}
// DeleteUnusedImages deletes any images that do not have containers associated with it.
// TODO Filters are not implemented
func (i *VarlinkAPI) DeleteUnusedImages(call iopodman.VarlinkCall) error {
images, err := i.Runtime.ImageRuntime().GetImages()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
var deletedImages []string
for _, img := range images {
containers, err := img.Containers()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
if len(containers) == 0 {
if err := img.Remove(context.TODO(), false); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
deletedImages = append(deletedImages, img.ID())
}
}
return call.ReplyDeleteUnusedImages(deletedImages)
}
// Commit ...
func (i *VarlinkAPI) Commit(call iopodman.VarlinkCall, name, imageName string, changes []string, author, message string, pause bool, manifestType string) error {
var (
newImage *image.Image
log []string
mimeType string
)
output := channelwriter.NewChannelWriter()
channelClose := func() {
if err := output.Close(); err != nil {
logrus.Errorf("failed to close channel writer: %q", err)
}
}
defer channelClose()
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
rtc, err := i.Runtime.GetConfig()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
switch manifestType {
case "oci", "": // nolint
mimeType = buildah.OCIv1ImageManifest
case "docker":
mimeType = manifest.DockerV2Schema2MediaType
default:
return call.ReplyErrorOccurred(fmt.Sprintf("unrecognized image format %q", manifestType))
}
coptions := buildah.CommitOptions{
SignaturePolicyPath: rtc.Engine.SignaturePolicyPath,
ReportWriter: output,
SystemContext: sc,
PreferredManifestType: mimeType,
}
options := libpod.ContainerCommitOptions{
CommitOptions: coptions,
Pause: pause,
Message: message,
Changes: changes,
Author: author,
}
if call.WantsMore() {
call.Continues = true
}
c := make(chan error)
go func() {
newImage, err = ctr.Commit(getContext(), imageName, options)
if err != nil {
c <- err
}
c <- nil
close(c)
}()
// reply is the func being sent to the output forwarder. in this case it is replying
// with a more response struct
reply := func(br iopodman.MoreResponse) error {
return call.ReplyCommit(br)
}
log, err = forwardOutput(log, c, call.WantsMore(), output, reply)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
call.Continues = false
br := iopodman.MoreResponse{
Logs: log,
Id: newImage.ID(),
}
return call.ReplyCommit(br)
}
// ImportImage imports an image from a tarball to the image store
func (i *VarlinkAPI) ImportImage(call iopodman.VarlinkCall, source, reference, message string, changes []string, delete bool) error {
configChanges, err := util.GetImageConfig(changes)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
history := []v1.History{
{Comment: message},
}
config := v1.Image{
Config: configChanges.ImageConfig,
History: history,
}
newImage, err := i.Runtime.ImageRuntime().Import(getContext(), source, reference, nil, image.SigningOptions{}, config)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
if delete {
if err := os.Remove(source); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
}
return call.ReplyImportImage(newImage.ID())
}
// ExportImage exports an image to the provided destination
// destination must have the transport type!!
func (i *VarlinkAPI) ExportImage(call iopodman.VarlinkCall, name, destination string, compress bool, tags []string) error {
newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
if err != nil {
return call.ReplyImageNotFound(name, err.Error())
}
additionalTags, err := image.GetAdditionalTags(tags)
if err != nil {
return err
}
if err := newImage.PushImageToHeuristicDestination(getContext(), destination, "", "", "", "", nil, compress, image.SigningOptions{}, &image.DockerRegistryOptions{}, additionalTags); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyExportImage(newImage.ID())
}
// PullImage pulls an image from a registry to the image store.
func (i *VarlinkAPI) PullImage(call iopodman.VarlinkCall, name string, creds iopodman.AuthConfig) error {
var (
imageID string
err error
)
dockerRegistryOptions := image.DockerRegistryOptions{
DockerRegistryCreds: &types.DockerAuthConfig{
Username: creds.Username,
Password: creds.Password,
},
}
so := image.SigningOptions{}
if call.WantsMore() {
call.Continues = true
}
output := channelwriter.NewChannelWriter()
channelClose := func() {
if err := output.Close(); err != nil {
logrus.Errorf("failed to close channel writer: %q", err)
}
}
defer channelClose()
c := make(chan error)
defer close(c)
go func() {
var foundError bool
if strings.HasPrefix(name, dockerarchive.Transport.Name()+":") {
srcRef, err := alltransports.ParseImageName(name)
if err != nil {
c <- errors.Wrapf(err, "error parsing %q", name)
}
newImage, err := i.Runtime.ImageRuntime().LoadFromArchiveReference(getContext(), srcRef, "", output)
if err != nil {
foundError = true
c <- errors.Wrapf(err, "error pulling image from %q", name)
} else {
imageID = newImage[0].ID()
}
} else {
newImage, err := i.Runtime.ImageRuntime().New(getContext(), name, "", "", output, &dockerRegistryOptions, so, nil, util.PullImageMissing)
if err != nil {
foundError = true
c <- errors.Wrapf(err, "unable to pull %s", name)
} else {
imageID = newImage.ID()
}
}
if !foundError {
c <- nil
}
}()
var log []string
reply := func(br iopodman.MoreResponse) error {
return call.ReplyPullImage(br)
}
log, err = forwardOutput(log, c, call.WantsMore(), output, reply)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
call.Continues = false
br := iopodman.MoreResponse{
Logs: log,
Id: imageID,
}
return call.ReplyPullImage(br)
}
// ImageExists returns bool as to whether the input image exists in local storage
func (i *VarlinkAPI) ImageExists(call iopodman.VarlinkCall, name string) error {
_, err := i.Runtime.ImageRuntime().NewFromLocal(name)
if errors.Cause(err) == image.ErrNoSuchImage {
return call.ReplyImageExists(1)
}
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyImageExists(0)
}
// ContainerRunlabel ...
func (i *VarlinkAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.Runlabel) error {
ctx := getContext()
dockerRegistryOptions := image.DockerRegistryOptions{}
stdErr := os.Stderr
stdOut := os.Stdout
stdIn := os.Stdin
runLabel, imageName, err := GetRunlabel(input.Label, input.Image, ctx, i.Runtime, input.Pull, "", dockerRegistryOptions, input.Authfile, "", nil)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
if runLabel == "" {
return call.ReplyErrorOccurred(fmt.Sprintf("%s does not contain the label %s", input.Image, input.Label))
}
cmd, env, err := GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs, "")
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
if err := utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyContainerRunlabel()
}
// ImagesPrune ....
func (i *VarlinkAPI) ImagesPrune(call iopodman.VarlinkCall, all bool, filter []string) error {
prunedImages, err := i.Runtime.ImageRuntime().PruneImages(context.TODO(), all, []string{})
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyImagesPrune(prunedImages)
}
// ImageSave ....
func (i *VarlinkAPI) ImageSave(call iopodman.VarlinkCall, options iopodman.ImageSaveOptions) error {
newImage, err := i.Runtime.ImageRuntime().NewFromLocal(options.Name)
if err != nil {
if errors.Cause(err) == define.ErrNoSuchImage {
return call.ReplyImageNotFound(options.Name, err.Error())
}
return call.ReplyErrorOccurred(err.Error())
}
// Determine if we are dealing with a tarball or dir
var output string
outputToDir := false
if options.Format == "oci-archive" || options.Format == "docker-archive" {
tempfile, err := ioutil.TempFile("", "varlink_send")
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
output = tempfile.Name()
tempfile.Close()
} else {
var err error
outputToDir = true
output, err = ioutil.TempDir("", "varlink_send")
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
}
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
if call.WantsMore() {
call.Continues = true
}
saveOutput := bytes.NewBuffer([]byte{})
c := make(chan error)
go func() {
err := newImage.Save(getContext(), options.Name, options.Format, output, options.MoreTags, options.Quiet, options.Compress)
c <- err
close(c)
}()
var log []string
done := false
for {
line, err := saveOutput.ReadString('\n')
if err == nil {
log = append(log, line)
continue
} else if err == io.EOF {
select {
case err := <-c:
if err != nil {
logrus.Errorf("reading of output during save failed for %s", newImage.ID())
return call.ReplyErrorOccurred(err.Error())
}
done = true
default:
if !call.WantsMore() {
break
}
br := iopodman.MoreResponse{
Logs: log,
}
call.ReplyImageSave(br)
log = []string{}
}
} else {
return call.ReplyErrorOccurred(err.Error())
}
if done {
break
}
}
call.Continues = false
sendfile := output
// Image has been saved to `output`
if outputToDir {
// If the output is a directory, we need to tar up the directory to send it back
// Create a tempfile for the directory tarball
outputFile, err := ioutil.TempFile("", "varlink_save_dir")
if err != nil {
return err
}
defer outputFile.Close()
if err := utils.TarToFilesystem(output, outputFile); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
sendfile = outputFile.Name()
}
br := iopodman.MoreResponse{
Logs: log,
Id: sendfile,
}
return call.ReplyPushImage(br)
}
// LoadImage ...
func (i *VarlinkAPI) LoadImage(call iopodman.VarlinkCall, name, inputFile string, deleteInputFile, quiet bool) error {
var (
names string
writer io.Writer
err error
)
if !quiet {
writer = os.Stderr
}
if call.WantsMore() {
call.Continues = true
}
output := bytes.NewBuffer([]byte{})
c := make(chan error)
go func() {
names, err = i.Runtime.LoadImage(getContext(), name, inputFile, writer, "")
c <- err
close(c)
}()
var log []string
done := false
for {
line, err := output.ReadString('\n')
if err == nil {
log = append(log, line)
continue
} else if err == io.EOF {
select {
case err := <-c:
if err != nil {
logrus.Error(err)
return call.ReplyErrorOccurred(err.Error())
}
done = true
default:
if !call.WantsMore() {
break
}
br := iopodman.MoreResponse{
Logs: log,
}
call.ReplyLoadImage(br)
log = []string{}
}
} else {
return call.ReplyErrorOccurred(err.Error())
}
if done {
break
}
}
call.Continues = false
br := iopodman.MoreResponse{
Logs: log,
Id: names,
}
if deleteInputFile {
if err := os.Remove(inputFile); err != nil {
logrus.Errorf("unable to delete input file %s", inputFile)
}
}
return call.ReplyLoadImage(br)
}
// Diff ...
func (i *VarlinkAPI) Diff(call iopodman.VarlinkCall, name string) error {
var response []iopodman.DiffInfo
changes, err := i.Runtime.GetDiff("", name)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
for _, change := range changes {
response = append(response, iopodman.DiffInfo{Path: change.Path, ChangeType: change.Kind.String()})
}
return call.ReplyDiff(response)
}
// GetLayersMapWithImageInfo is a development only endpoint to obtain layer information for an image.
func (i *VarlinkAPI) GetLayersMapWithImageInfo(call iopodman.VarlinkCall) error {
layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime())
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
b, err := json.Marshal(layerInfo)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyGetLayersMapWithImageInfo(string(b))
}
// BuildImageHierarchyMap ...
func (i *VarlinkAPI) BuildImageHierarchyMap(call iopodman.VarlinkCall, name string) error {
img, err := i.Runtime.ImageRuntime().NewFromLocal(name)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
imageInfo := &image.InfoImage{
ID: img.ID(),
Tags: img.Names(),
}
layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime())
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
if err := image.BuildImageHierarchyMap(imageInfo, layerInfo, img.TopLayer()); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
b, err := json.Marshal(imageInfo)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyBuildImageHierarchyMap(string(b))
}
// ImageTree returns the image tree string for the provided image name or ID
func (i *VarlinkAPI) ImageTree(call iopodman.VarlinkCall, nameOrID string, whatRequires bool) error {
img, err := i.Runtime.ImageRuntime().NewFromLocal(nameOrID)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
tree, err := img.GenerateTree(whatRequires)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyImageTree(tree)
}