mirror of
https://github.com/containers/podman.git
synced 2025-12-03 19:59:39 +08:00
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>
1021 lines
29 KiB
Go
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))
|
|
}
|