Files
podman/pkg/varlinkapi/images.go
Jhon Honce d924494f56 Initial commit on compatible API
Signed-off-by: Jhon Honce <jhonce@redhat.com>

Create service command

Use cd cmd/service && go build .

$ systemd-socket-activate -l 8081 cmd/service/service &
$ curl http://localhost:8081/v1.24/images/json

Signed-off-by: Jhon Honce <jhonce@redhat.com>

Correct Makefile

Signed-off-by: Jhon Honce <jhonce@redhat.com>

Two more stragglers

Signed-off-by: Jhon Honce <jhonce@redhat.com>

Report errors back as http headers

Signed-off-by: Jhon Honce <jhonce@redhat.com>

Split out handlers, updated output

Output aligned to docker structures

Signed-off-by: Jhon Honce <jhonce@redhat.com>

Refactored routing, added more endpoints and types

* Encapsulated all the routing information in the handler_* files.
* Added more serviceapi/types, including podman additions. See Info

Signed-off-by: Jhon Honce <jhonce@redhat.com>

Cleaned up code, implemented info content

* Move Content-Type check into serviceHandler
* Custom 404 handler showing the url, mostly for debugging
* Refactored images: better method names and explicit http codes
* Added content to /info
* Added podman fields to Info struct
* Added Container struct

Signed-off-by: Jhon Honce <jhonce@redhat.com>

Add a bunch of endpoints

containers: stop, pause, unpause, wait, rm
images: tag, rmi, create (pull only)

Signed-off-by: baude <bbaude@redhat.com>

Add even more handlers

* Add serviceapi/Error() to improve error handling
* Better support for API return payloads
* Renamed unimplemented to unsupported these are generic endpoints
  we don't intend to ever support.  Swarm broken out since it uses
  different HTTP codes to signal that the node is not in a swarm.
* Added more types
* API Version broken out so it can be validated in the future

Signed-off-by: Jhon Honce <jhonce@redhat.com>

Refactor to introduce ServiceWriter

Signed-off-by: Jhon Honce <jhonce@redhat.com>

populate pods endpoints

/libpod/pods/..

exists, kill, pause, prune, restart, remove, start, stop, unpause

Signed-off-by: baude <bbaude@redhat.com>

Add components to Version, fix Error body

Signed-off-by: Jhon Honce <jhonce@redhat.com>

Add images pull output, fix swarm routes

* docker-py tests/integration/api_client_test.py pass 100%
* docker-py tests/integration/api_image_test.py pass 4/16
+ Test failures include services podman does not support

Signed-off-by: Jhon Honce <jhonce@redhat.com>

pods endpoint submission 2

add create and others; only top and stats is left.

Signed-off-by: baude <bbaude@redhat.com>

Update pull image to work from empty registry

Signed-off-by: Jhon Honce <jhonce@redhat.com>

pod create and container create

first pass at pod and container create.  the container create does not
quite work yet but it is very close.  pod create needs a partial
rewrite.  also broken off the DELETE (rm/rmi) to specific handler funcs.

Signed-off-by: baude <bbaude@redhat.com>

Add docker-py demos, GET .../containers/json

* Update serviceapi/types to reflect libpod not podman
* Refactored removeImage() to provide non-streaming return

Signed-off-by: Jhon Honce <jhonce@redhat.com>

create container part2

finished minimal config needed for create container.  started demo.py
for upcoming talk

Signed-off-by: baude <bbaude@redhat.com>

Stop server after honoring request

* Remove casting for method calls
* Improve WriteResponse()
* Update Container API type to match docker API

Signed-off-by: Jhon Honce <jhonce@redhat.com>

fix namespace assumptions

cleaned up namespace issues with libpod.

Signed-off-by: baude <bbaude@redhat.com>

wip

Signed-off-by: baude <bbaude@redhat.com>

Add sliding window when shutting down server

* Added a Timeout rather than closing down service on each call
* Added gorilla/schema dependency for Decode'ing query parameters
* Improved error handling
* Container logs returned and multiplexed for stdout and stderr
  * .../containers/{name}/logs?stdout=True&stderr=True
* Container stats
  * .../containers/{name}/stats

Signed-off-by: Jhon Honce <jhonce@redhat.com>

Improve error handling

* Add check for at least one std stream required for /containers/{id}/logs
* Add check for state in /containers/{id}/top
* Fill in more fields for /info
* Fixed error checking in service start code

Signed-off-by: Jhon Honce <jhonce@redhat.com>

get rest  of image tests for pass

Signed-off-by: baude <bbaude@redhat.com>

linting our content

Signed-off-by: baude <bbaude@redhat.com>

more linting

Signed-off-by: baude <bbaude@redhat.com>

more linting

Signed-off-by: baude <bbaude@redhat.com>

pruning

Signed-off-by: baude <bbaude@redhat.com>

[CI:DOCS]apiv2 pods

migrate from using args in the url to using a json struct in body for
pod create.

Signed-off-by: baude <bbaude@redhat.com>

fix handler_images prune

prune's api changed slightly to deal with filters.

Signed-off-by: baude <bbaude@redhat.com>

[CI:DOCS]enabled base container create tests

enabling the base container create tests which allow us to get more into
the stop, kill, etc tests. many new tests now pass.

Signed-off-by: baude <bbaude@redhat.com>

serviceapi errors: append error message to API message

I dearly hope this is not breaking any other tests but debugging
"Internal Server Error" is not helpful to any user.  In case, it
breaks tests, we can rever the commit - that's why it's a small one.

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>

serviceAPI: add containers/prune endpoint

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>

add `service` make target

Also remove the non-functional sub-Makefile.

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>

add make targets for testing the service

 * `sudo make run-service` for running the service.

 * `DOCKERPY_TEST="tests/integration/api_container_test.py::ListContainersTest" \
 	make run-docker-py-tests`
   for running a specific tests.  Run all tests by leaving the env
   variable empty.

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>

Split handlers and server packages

The files were split to help contain bloat. The api/server package will
contain all code related to the functioning of the server while
api/handlers will have all the code related to implementing the end
points.

api/server/register_* will contain the methods for registering
endpoints.  Additionally, they will have the comments for generating the
swagger spec file.

See api/handlers/version.go for a small example handler,
api/handlers/containers.go contains much more complex handlers.

Signed-off-by: Jhon Honce <jhonce@redhat.com>

[CI:DOCS]enabled more tests

Signed-off-by: baude <bbaude@redhat.com>

[CI:DOCS]libpod endpoints

small refactor for libpod inclusion and began adding endpoints.

Signed-off-by: baude <bbaude@redhat.com>

Implement /build and /events

* Include crypto libraries for future ssh work

Signed-off-by: Jhon Honce <jhonce@redhat.com>

[CI:DOCS]more image implementations

convert from using for to query structs among other changes including
new endpoints.

Signed-off-by: baude <bbaude@redhat.com>

[CI:DOCS]add bindings for golang

Signed-off-by: baude <bbaude@redhat.com>

[CI:DOCS]add volume endpoints for libpod

create, inspect, ls, prune, and rm

Signed-off-by: baude <bbaude@redhat.com>

[CI:DOCS]apiv2 healthcheck enablement

wire up container healthchecks for the api.

Signed-off-by: baude <bbaude@redhat.com>

[CI:DOCS]Add mount endpoints

via the api, allow ability to mount a container and list container
mounts.

Signed-off-by: baude <bbaude@redhat.com>

[CI:DOCS]Add search endpoint

add search endpoint with golang bindings

Signed-off-by: baude <bbaude@redhat.com>

[CI:DOCS]more apiv2 development

misc population of methods, etc

Signed-off-by: baude <bbaude@redhat.com>

rebase cleanup and epoch reset

Signed-off-by: baude <bbaude@redhat.com>

[CI:DOCS]add more network endpoints

also, add some initial error handling and convenience functions for
standard endpoints.

Signed-off-by: baude <bbaude@redhat.com>

[CI:DOCS]use helper funcs for bindings

use the methods developed to make writing bindings less duplicative and
easier to use.

Signed-off-by: baude <bbaude@redhat.com>

[CI:DOCS]add return info for prereview

begin to add return info and status codes for errors so that we can
review the apiv2

Signed-off-by: baude <bbaude@redhat.com>

[CI:DOCS]first pass at adding swagger docs for api

Signed-off-by: baude <bbaude@redhat.com>
2020-01-10 09:41:39 -06:00

1021 lines
29 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/cmd/podman/shared"
iopodman "github.com/containers/libpod/cmd/podman/varlink"
"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"
"github.com/containers/libpod/utils"
"github.com/containers/storage/pkg/archive"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// ListImagesWithFilters returns a list of images that have been filtered
func (i *LibpodAPI) 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(fmt.Sprintf("unable to parse response", err))
}
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 _, image := range images {
labels, _ := image.Labels(getContext())
containers, _ := image.Containers()
repoDigests, err := image.RepoDigests()
if err != nil {
return nil, err
}
size, _ := image.Size(getContext())
isParent, err := image.IsParent(context.TODO())
if err != nil {
return nil, err
}
i := iopodman.Image{
Id: image.ID(),
Digest: string(image.Digest()),
ParentId: image.Parent,
RepoTags: image.Names(),
RepoDigests: repoDigests,
Created: image.Created().Format(time.RFC3339),
Size: int64(*size),
VirtualSize: image.VirtualSize,
Containers: int64(len(containers)),
Labels: labels,
IsParent: isParent,
ReadOnly: image.IsReadOnly(),
History: image.NamesHistory(),
}
imageList = append(imageList, i)
}
return imageList, nil
}
// ListImages lists all the images in the store
// It requires no inputs.
func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error {
images, err := i.Runtime.ImageRuntime().GetImages()
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(fmt.Sprintf("unable to parse response", err))
}
return call.ReplyListImages(imageList)
}
// GetImage returns a single image in the form of a Image
func (i *LibpodAPI) 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 *LibpodAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildInfo) error {
var (
namespace []buildah.NamespaceOption
err error
)
systemContext := types.SystemContext{}
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)
}
}()
// 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,
}
hostNetwork := buildah.NamespaceOption{
Name: string(specs.NetworkNamespace),
Host: true,
}
namespace = append(namespace, hostNetwork)
options := imagebuildah.BuildOptions{
CommonBuildOpts: commonOpts,
AdditionalTags: config.AdditionalTags,
Annotations: config.Annotations,
Args: config.BuildArgs,
CNIConfigDir: config.CniConfigDir,
CNIPluginPath: config.CniPluginDir,
Compression: stringCompressionToArchiveType(config.Compression),
ContextDirectory: newContextDir,
DefaultMountsFilePath: config.DefaultsMountFilePath,
Err: &output,
ForceRmIntermediateCtrs: config.ForceRmIntermediateCtrs,
IIDFile: config.Iidfile,
Labels: config.Label,
Layers: config.Layers,
NoCache: config.Nocache,
Out: &output,
Output: config.Output,
NamespaceOptions: namespace,
OutputFormat: config.OutputFormat,
PullPolicy: stringPullPolicyToType(config.PullPolicy),
Quiet: config.Quiet,
RemoveIntermediateCtrs: config.RemoteIntermediateCtrs,
ReportWriter: &output,
RuntimeArgs: config.RuntimeArgs,
Squash: config.Squash,
SystemContext: &systemContext,
}
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() {
_, _, err := i.Runtime.Build(getContext(), options, newPathDockerFiles...)
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
newImage, err := i.Runtime.ImageRuntime().NewFromLocal(config.Output)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
br := iopodman.MoreResponse{
Logs: log,
Id: newImage.ID(),
}
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 *LibpodAPI) 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 *LibpodAPI) 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 *LibpodAPI) 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 *LibpodAPI) 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 *LibpodAPI) 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 *LibpodAPI) 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 *LibpodAPI) 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 *LibpodAPI) 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 *LibpodAPI) 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 *LibpodAPI) 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.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.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 *LibpodAPI) 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 *LibpodAPI) 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 *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string) error {
var (
imageID string
err error
)
dockerRegistryOptions := image.DockerRegistryOptions{}
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 *LibpodAPI) 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 *LibpodAPI) 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 := shared.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 := shared.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 *LibpodAPI) 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 *LibpodAPI) 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 *LibpodAPI) 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 *LibpodAPI) 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 *LibpodAPI) 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 *LibpodAPI) 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))
}