From 39a7a773a653176e294382bc6301275fd57aff6b Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 23 Apr 2018 13:32:41 -0500 Subject: varlink images implement varlink image functions for working with libpod with the exception of a couple due to incompletions on the libpod side of things (build). also, created a first pass at a libpodpy package which will stand as a client to working with libpod's varlink methods using python. Signed-off-by: baude Closes: #669 Approved by: baude --- pkg/util/utils.go | 66 +++++++++++ pkg/varlinkapi/config.go | 16 ++- pkg/varlinkapi/containers.go | 2 +- pkg/varlinkapi/images.go | 274 ++++++++++++++++++++++++++++++++++++++----- pkg/varlinkapi/system.go | 2 +- pkg/varlinkapi/util.go | 10 ++ 6 files changed, 330 insertions(+), 40 deletions(-) create mode 100644 pkg/varlinkapi/util.go (limited to 'pkg') diff --git a/pkg/util/utils.go b/pkg/util/utils.go index edcf63f80..1bbfe30d3 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/containers/image/types" + "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "golang.org/x/crypto/ssh/terminal" ) @@ -54,3 +55,68 @@ func StringInSlice(s string, sl []string) bool { } return false } + +// GetImageConfig converts the --change flag values in the format "CMD=/bin/bash USER=example" +// to a type v1.ImageConfig +func GetImageConfig(changes []string) (v1.ImageConfig, error) { + // USER=value | EXPOSE=value | ENV=value | ENTRYPOINT=value | + // CMD=value | VOLUME=value | WORKDIR=value | LABEL=key=value | STOPSIGNAL=value + + var ( + user string + env []string + entrypoint []string + cmd []string + workingDir string + stopSignal string + ) + + exposedPorts := make(map[string]struct{}) + volumes := make(map[string]struct{}) + labels := make(map[string]string) + + for _, ch := range changes { + pair := strings.Split(ch, "=") + if len(pair) == 1 { + return v1.ImageConfig{}, errors.Errorf("no value given for instruction %q", ch) + } + switch pair[0] { + case "USER": + user = pair[1] + case "EXPOSE": + var st struct{} + exposedPorts[pair[1]] = st + case "ENV": + env = append(env, pair[1]) + case "ENTRYPOINT": + entrypoint = append(entrypoint, pair[1]) + case "CMD": + cmd = append(cmd, pair[1]) + case "VOLUME": + var st struct{} + volumes[pair[1]] = st + case "WORKDIR": + workingDir = pair[1] + case "LABEL": + if len(pair) == 3 { + labels[pair[1]] = pair[2] + } else { + labels[pair[1]] = "" + } + case "STOPSIGNAL": + stopSignal = pair[1] + } + } + + return v1.ImageConfig{ + User: user, + ExposedPorts: exposedPorts, + Env: env, + Entrypoint: entrypoint, + Cmd: cmd, + Volumes: volumes, + WorkingDir: workingDir, + Labels: labels, + StopSignal: stopSignal, + }, nil +} diff --git a/pkg/varlinkapi/config.go b/pkg/varlinkapi/config.go index 167270f09..3c6a3311c 100644 --- a/pkg/varlinkapi/config.go +++ b/pkg/varlinkapi/config.go @@ -1,14 +1,18 @@ package varlinkapi -import "github.com/projectatomic/libpod/cmd/podman/ioprojectatomicpodman" +import ( + ioprojectatomicpodman "github.com/projectatomic/libpod/cmd/podman/varlink" + "github.com/urfave/cli" +) // LibpodAPI is the basic varlink struct for libpod type LibpodAPI struct { + Cli *cli.Context ioprojectatomicpodman.VarlinkInterface } -var ( - lp = LibpodAPI{} - // VarlinkLibpod instantiation - VarlinkLibpod = ioprojectatomicpodman.VarlinkNew(&lp) -) +// New creates a new varlink client +func New(cli *cli.Context) *ioprojectatomicpodman.VarlinkInterface { + lp := LibpodAPI{Cli: cli} + return ioprojectatomicpodman.VarlinkNew(&lp) +} diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index e58cab497..bd406dda2 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -1,7 +1,7 @@ package varlinkapi import ( - "github.com/projectatomic/libpod/cmd/podman/ioprojectatomicpodman" + ioprojectatomicpodman "github.com/projectatomic/libpod/cmd/podman/varlink" ) // ListContainers ... diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 1de54e43b..29fe803ae 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -1,75 +1,285 @@ package varlinkapi import ( - "github.com/projectatomic/libpod/cmd/podman/ioprojectatomicpodman" + "encoding/json" + "fmt" + + "github.com/containers/image/docker" + "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/projectatomic/libpod/cmd/podman/libpodruntime" + ioprojectatomicpodman "github.com/projectatomic/libpod/cmd/podman/varlink" + "github.com/projectatomic/libpod/libpod/image" + sysreg "github.com/projectatomic/libpod/pkg/registries" + "github.com/projectatomic/libpod/pkg/util" ) -// ListImages ... +// ListImages lists all the images in the store +// It requires no inputs. func (i *LibpodAPI) ListImages(call ioprojectatomicpodman.VarlinkCall) error { - return call.ReplyMethodNotImplemented("ListImages") + runtime, err := libpodruntime.GetRuntime(i.Cli) + if err != nil { + return call.ReplyRuntimeError(err.Error()) + } + images, err := runtime.ImageRuntime().GetImages() + if err != nil { + return call.ReplyErrorOccurred(fmt.Sprintf("unable to get list of images %q", err)) + } + var imageList []ioprojectatomicpodman.ImageInList + for _, image := range images { + //size, _:= image.Size(getContext()) + labels, _ := image.Labels(getContext()) + containers, _ := image.Containers() + + i := ioprojectatomicpodman.ImageInList{ + Id: image.ID(), + ParentId: image.Parent, + RepoTags: image.Names(), + RepoDigests: image.RepoDigests(), + Created: image.Created().String(), + //Size: size, + VirtualSize: image.VirtualSize, + Containers: int64(len(containers)), + Labels: labels, + } + imageList = append(imageList, i) + } + return call.ReplyListImages(imageList) } // BuildImage ... +// TODO Waiting for buildah to be vendored into libpod to do this only one func (i *LibpodAPI) BuildImage(call ioprojectatomicpodman.VarlinkCall) error { return call.ReplyMethodNotImplemented("BuildImage") } // CreateImage ... +// TODO With Pull being added, should we skip Create? func (i *LibpodAPI) CreateImage(call ioprojectatomicpodman.VarlinkCall) error { return call.ReplyMethodNotImplemented("CreateImage") } -// InspectImage ... -func (i *LibpodAPI) InspectImage(call ioprojectatomicpodman.VarlinkCall) error { - return call.ReplyMethodNotImplemented("InspectImage") +// 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 ioprojectatomicpodman.VarlinkCall, name string) error { + runtime, err := libpodruntime.GetRuntime(i.Cli) + if err != nil { + return call.ReplyRuntimeError(err.Error()) + } + newImage, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + return call.ReplyImageNotFound(name) + } + inspectInfo, err := newImage.Inspect(getContext()) + b, err := json.Marshal(inspectInfo) + if err != nil { + return call.ReplyErrorOccurred(fmt.Sprintf("unable to serialize")) + } + return call.ReplyInspectImage(string(b)) } -// HistoryImage ... -func (i *LibpodAPI) HistoryImage(call ioprojectatomicpodman.VarlinkCall) error { - return call.ReplyMethodNotImplemented("HistoryImage") +// HistoryImage returns the history of the image's layers +// Requires an image or name +func (i *LibpodAPI) HistoryImage(call ioprojectatomicpodman.VarlinkCall, name string) error { + runtime, err := libpodruntime.GetRuntime(i.Cli) + if err != nil { + return call.ReplyRuntimeError(err.Error()) + } + newImage, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + return call.ReplyImageNotFound(name) + } + history, layerInfos, err := newImage.History(getContext()) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + var histories []ioprojectatomicpodman.ImageHistory + for i, h := range history { + imageHistory := ioprojectatomicpodman.ImageHistory{ + Id: newImage.ID(), + Created: h.Created.String(), + CreatedBy: h.CreatedBy, + Tags: newImage.Names(), + Size: layerInfos[i].Size, + Comment: h.Comment, + } + histories = append(histories, imageHistory) + } + return call.ReplyHistoryImage(histories) } -// PushImage ... -func (i *LibpodAPI) PushImage(call ioprojectatomicpodman.VarlinkCall) error { - return call.ReplyMethodNotImplemented("PushImage") +// PushImage pushes an local image to registry +// TODO We need to add options for signing, credentials, and tls +func (i *LibpodAPI) PushImage(call ioprojectatomicpodman.VarlinkCall, name, tag string, tlsVerify bool) error { + runtime, err := libpodruntime.GetRuntime(i.Cli) + if err != nil { + return call.ReplyRuntimeError(err.Error()) + } + newImage, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + return call.ReplyImageNotFound(err.Error()) + } + destname := name + if tag != "" { + destname = tag + } + + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerInsecureSkipTLSVerify: !tlsVerify, + } + + so := image.SigningOptions{} + + if err := newImage.PushImage(getContext(), destname, "", "", "", nil, false, so, &dockerRegistryOptions); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyPushImage() } -// TagImage ... -func (i *LibpodAPI) TagImage(call ioprojectatomicpodman.VarlinkCall) error { - return call.ReplyMethodNotImplemented("TagImage") +// TagImage accepts an image name and tag as strings and tags an image in the local store. +func (i *LibpodAPI) TagImage(call ioprojectatomicpodman.VarlinkCall, name, tag string) error { + runtime, err := libpodruntime.GetRuntime(i.Cli) + if err != nil { + return call.ReplyRuntimeError(err.Error()) + } + newImage, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + return call.ReplyImageNotFound(name) + } + if err := newImage.TagImage(tag); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyTagImage() } -// RemoveImage ... -func (i *LibpodAPI) RemoveImage(call ioprojectatomicpodman.VarlinkCall) error { - return call.ReplyMethodNotImplemented("RemoveImage") +// 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 ioprojectatomicpodman.VarlinkCall, name string, force bool) error { + runtime, err := libpodruntime.GetRuntime(i.Cli) + if err != nil { + return call.ReplyRuntimeError(err.Error()) + } + newImage, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + return call.ReplyImageNotFound(name) + } + if err := newImage.Remove(force); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyRemoveImage() } -// SearchImage ... -func (i *LibpodAPI) SearchImage(call ioprojectatomicpodman.VarlinkCall) error { - return call.ReplyMethodNotImplemented("SearchImage") +// SearchImage 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) SearchImage(call ioprojectatomicpodman.VarlinkCall, name string, limit int64) error { + sc := image.GetSystemContext("", "", false) + registries, err := sysreg.GetRegistries() + if err != nil { + return call.ReplyErrorOccurred(fmt.Sprintf("unable to get system registries: %q", err)) + } + var imageResults []ioprojectatomicpodman.ImageSearch + for _, reg := range registries { + results, err := docker.SearchRegistry(getContext(), sc, reg, name, int(limit)) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + for _, result := range results { + i := ioprojectatomicpodman.ImageSearch{ + Description: result.Description, + Is_official: result.IsOfficial, + Is_automated: result.IsAutomated, + Name: result.Name, + Star_count: int64(result.StarCount), + } + imageResults = append(imageResults, i) + } + } + return call.ReplySearchImage(imageResults) } -// DeleteUnusedImages ... +// DeleteUnusedImages deletes any images that do not have containers associated with it. +// TODO Filters are not implemented func (i *LibpodAPI) DeleteUnusedImages(call ioprojectatomicpodman.VarlinkCall) error { - return call.ReplyMethodNotImplemented("DeleteUnusedImages") + runtime, err := libpodruntime.GetRuntime(i.Cli) + if err != nil { + return call.ReplyRuntimeError(err.Error()) + } + images, err := 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(false); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + deletedImages = append(deletedImages, img.ID()) + } + } + return call.ReplyDeleteUnusedImages(deletedImages) } // CreateFromContainer ... +// TODO This must wait until buildah is properly vendored into libpod func (i *LibpodAPI) CreateFromContainer(call ioprojectatomicpodman.VarlinkCall) error { return call.ReplyMethodNotImplemented("CreateFromContainer") } -// ImportImage ... -func (i *LibpodAPI) ImportImage(call ioprojectatomicpodman.VarlinkCall) error { - return call.ReplyMethodNotImplemented("ImportImage") +// ImportImage imports an image from a tarball to the image store +func (i *LibpodAPI) ImportImage(call ioprojectatomicpodman.VarlinkCall, source, reference, message string, changes []string) error { + runtime, err := libpodruntime.GetRuntime(i.Cli) + if err != nil { + return call.ReplyRuntimeError(err.Error()) + } + configChanges, err := util.GetImageConfig(changes) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + history := []v1.History{ + {Comment: message}, + } + config := v1.Image{ + Config: configChanges, + History: history, + } + newImage, err := runtime.ImageRuntime().Import(getContext(), source, reference, nil, image.SigningOptions{}, config) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyImportImage(newImage.ID()) } -// ExportImage ... -func (i *LibpodAPI) ExportImage(call ioprojectatomicpodman.VarlinkCall) error { - return call.ReplyMethodNotImplemented("ExportImage") +// ExportImage exports an image to the provided destination +// destination must have the transport type!! +func (i *LibpodAPI) ExportImage(call ioprojectatomicpodman.VarlinkCall, name, destination string, compress bool) error { + runtime, err := libpodruntime.GetRuntime(i.Cli) + if err != nil { + return call.ReplyRuntimeError(err.Error()) + } + newImage, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + return call.ReplyImageNotFound(name) + } + if err := newImage.PushImage(getContext(), destination, "", "", "", nil, compress, image.SigningOptions{}, &image.DockerRegistryOptions{}); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyExportImage() } -// PullImage ... -func (i *LibpodAPI) PullImage(call ioprojectatomicpodman.VarlinkCall) error { - return call.ReplyMethodNotImplemented("PullImage") +// PullImage pulls an image from a registry to the image store. +// TODO This implementation is incomplete +func (i *LibpodAPI) PullImage(call ioprojectatomicpodman.VarlinkCall, name string) error { + runtime, err := libpodruntime.GetRuntime(i.Cli) + if err != nil { + return call.ReplyRuntimeError(err.Error()) + } + newImage, err := runtime.ImageRuntime().New(getContext(), name, "", "", nil, &image.DockerRegistryOptions{}, image.SigningOptions{}, true, false) + if err != nil { + return call.ReplyErrorOccurred(fmt.Sprintf("unable to pull %s", name)) + } + return call.ReplyPullImage(newImage.ID()) } diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index c343f1245..976dfc682 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -1,7 +1,7 @@ package varlinkapi import ( - "github.com/projectatomic/libpod/cmd/podman/ioprojectatomicpodman" + ioprojectatomicpodman "github.com/projectatomic/libpod/cmd/podman/varlink" "github.com/projectatomic/libpod/libpod" ) diff --git a/pkg/varlinkapi/util.go b/pkg/varlinkapi/util.go new file mode 100644 index 000000000..ff0fb6ecb --- /dev/null +++ b/pkg/varlinkapi/util.go @@ -0,0 +1,10 @@ +package varlinkapi + +import ( + "context" +) + +// getContext returns a non-nil, empty context +func getContext() context.Context { + return context.TODO() +} -- cgit v1.2.3-54-g00ecf