mirror of
https://github.com/containers/podman.git
synced 2026-06-07 23:36:09 -04: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>
1083 lines
30 KiB
Go
1083 lines
30 KiB
Go
// +build remoteclient
|
|
|
|
package adapter
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/containers/buildah/imagebuildah"
|
|
"github.com/containers/buildah/pkg/formats"
|
|
"github.com/containers/image/v5/docker/reference"
|
|
"github.com/containers/image/v5/types"
|
|
"github.com/containers/libpod/cmd/podman/cliconfig"
|
|
"github.com/containers/libpod/cmd/podman/remoteclientconfig"
|
|
iopodman "github.com/containers/libpod/cmd/podman/varlink"
|
|
"github.com/containers/libpod/libpod"
|
|
"github.com/containers/libpod/libpod/define"
|
|
"github.com/containers/libpod/libpod/events"
|
|
"github.com/containers/libpod/libpod/image"
|
|
"github.com/containers/libpod/pkg/util"
|
|
"github.com/containers/libpod/utils"
|
|
"github.com/containers/storage/pkg/archive"
|
|
"github.com/opencontainers/go-digest"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/varlink/go/varlink"
|
|
v1 "k8s.io/api/core/v1"
|
|
)
|
|
|
|
// ImageRuntime is wrapper for image runtime
|
|
type RemoteImageRuntime struct{}
|
|
|
|
// RemoteRuntime describes a wrapper runtime struct
|
|
type RemoteRuntime struct {
|
|
Conn *varlink.Connection
|
|
Remote bool
|
|
cmd cliconfig.MainFlags
|
|
config io.Reader
|
|
}
|
|
|
|
// LocalRuntime describes a typical libpod runtime
|
|
type LocalRuntime struct {
|
|
*RemoteRuntime
|
|
}
|
|
|
|
// GetRuntimeNoStore returns a LocalRuntime struct with the actual runtime embedded in it
|
|
// The nostore is ignored
|
|
func GetRuntimeNoStore(ctx context.Context, c *cliconfig.PodmanCommand) (*LocalRuntime, error) {
|
|
return GetRuntime(ctx, c)
|
|
}
|
|
|
|
// GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it
|
|
func GetRuntime(ctx context.Context, c *cliconfig.PodmanCommand) (*LocalRuntime, error) {
|
|
var (
|
|
customConfig bool
|
|
err error
|
|
f *os.File
|
|
)
|
|
runtime := RemoteRuntime{
|
|
Remote: true,
|
|
cmd: c.GlobalFlags,
|
|
}
|
|
configPath := remoteclientconfig.GetConfigFilePath()
|
|
// Check if the basedir for configPath exists and if not, create it.
|
|
if _, err := os.Stat(filepath.Dir(configPath)); os.IsNotExist(err) {
|
|
if mkdirErr := os.MkdirAll(filepath.Dir(configPath), 0750); mkdirErr != nil {
|
|
return nil, mkdirErr
|
|
}
|
|
}
|
|
if len(c.GlobalFlags.RemoteConfigFilePath) > 0 {
|
|
configPath = c.GlobalFlags.RemoteConfigFilePath
|
|
customConfig = true
|
|
}
|
|
|
|
f, err = os.Open(configPath)
|
|
if err != nil {
|
|
// If user does not explicitly provide a configuration file path and we cannot
|
|
// find a default, no error should occur.
|
|
if os.IsNotExist(err) && !customConfig {
|
|
logrus.Debugf("unable to load configuration file at %s", configPath)
|
|
runtime.config = nil
|
|
} else {
|
|
return nil, errors.Wrapf(err, "unable to load configuration file at %s", configPath)
|
|
}
|
|
} else {
|
|
// create the io reader for the remote client
|
|
runtime.config = bufio.NewReader(f)
|
|
}
|
|
conn, err := runtime.Connect()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
runtime.Conn = conn
|
|
return &LocalRuntime{
|
|
&runtime,
|
|
}, nil
|
|
}
|
|
|
|
// DeferredShutdown is a bogus wrapper for compaat with the libpod
|
|
// runtime and should only be run when a defer is being used
|
|
func (r RemoteRuntime) DeferredShutdown(force bool) {
|
|
if err := r.Shutdown(force); err != nil {
|
|
logrus.Error("unable to shutdown runtime")
|
|
}
|
|
}
|
|
|
|
// RuntimeConfig is a bogus wrapper for compat with the libpod runtime
|
|
type RuntimeConfig struct {
|
|
// CGroupManager is the CGroup Manager to use
|
|
// Valid values are "cgroupfs" and "systemd"
|
|
CgroupManager string
|
|
}
|
|
|
|
// Shutdown is a bogus wrapper for compat with the libpod runtime
|
|
func (r *RemoteRuntime) GetConfig() (*RuntimeConfig, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// Shutdown is a bogus wrapper for compat with the libpod runtime
|
|
func (r RemoteRuntime) Shutdown(force bool) error {
|
|
return nil
|
|
}
|
|
|
|
// ContainerImage
|
|
type ContainerImage struct {
|
|
remoteImage
|
|
}
|
|
|
|
type remoteImage struct {
|
|
ID string
|
|
Labels map[string]string
|
|
RepoTags []string
|
|
RepoDigests []string
|
|
Parent string
|
|
Size int64
|
|
Created time.Time
|
|
InputName string
|
|
Names []string
|
|
Digest digest.Digest
|
|
Digests []digest.Digest
|
|
isParent bool
|
|
Runtime *LocalRuntime
|
|
TopLayer string
|
|
ReadOnly bool
|
|
NamesHistory []string
|
|
}
|
|
|
|
// Container ...
|
|
type Container struct {
|
|
remoteContainer
|
|
}
|
|
|
|
// remoteContainer ....
|
|
type remoteContainer struct {
|
|
Runtime *LocalRuntime
|
|
config *libpod.ContainerConfig
|
|
state *libpod.ContainerState
|
|
}
|
|
|
|
// Pod ...
|
|
type Pod struct {
|
|
remotepod
|
|
}
|
|
|
|
type remotepod struct {
|
|
config *libpod.PodConfig
|
|
state *libpod.PodInspectState
|
|
containers []libpod.PodContainerInfo
|
|
Runtime *LocalRuntime
|
|
}
|
|
|
|
type VolumeFilter func(*Volume) bool
|
|
|
|
// Volume is embed for libpod volumes
|
|
type Volume struct {
|
|
remoteVolume
|
|
}
|
|
|
|
type remoteVolume struct {
|
|
Runtime *LocalRuntime
|
|
config *libpod.VolumeConfig
|
|
}
|
|
|
|
// GetImages returns a slice of containerimages over a varlink connection
|
|
func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) {
|
|
return r.getImages(false)
|
|
}
|
|
|
|
// GetRWImages returns a slice of read/write containerimages over a varlink connection
|
|
func (r *LocalRuntime) GetRWImages() ([]*ContainerImage, error) {
|
|
return r.getImages(true)
|
|
}
|
|
|
|
func (r *LocalRuntime) GetFilteredImages(filters []string, rwOnly bool) ([]*ContainerImage, error) {
|
|
var newImages []*ContainerImage
|
|
images, err := iopodman.ListImagesWithFilters().Call(r.Conn, filters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, i := range images {
|
|
if rwOnly && i.ReadOnly {
|
|
continue
|
|
}
|
|
name := i.Id
|
|
if len(i.RepoTags) > 1 {
|
|
name = i.RepoTags[0]
|
|
}
|
|
newImage, err := imageInListToContainerImage(i, name, r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newImages = append(newImages, newImage)
|
|
}
|
|
return newImages, nil
|
|
}
|
|
func (r *LocalRuntime) getImages(rwOnly bool) ([]*ContainerImage, error) {
|
|
var newImages []*ContainerImage
|
|
images, err := iopodman.ListImages().Call(r.Conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, i := range images {
|
|
if rwOnly && i.ReadOnly {
|
|
continue
|
|
}
|
|
name := i.Id
|
|
if len(i.RepoTags) > 1 {
|
|
name = i.RepoTags[0]
|
|
}
|
|
newImage, err := imageInListToContainerImage(i, name, r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newImages = append(newImages, newImage)
|
|
}
|
|
return newImages, nil
|
|
}
|
|
|
|
func imageInListToContainerImage(i iopodman.Image, name string, runtime *LocalRuntime) (*ContainerImage, error) {
|
|
created, err := time.ParseInLocation(time.RFC3339, i.Created, time.UTC)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var digests []digest.Digest
|
|
for _, d := range i.Digests {
|
|
digests = append(digests, digest.Digest(d))
|
|
}
|
|
ri := remoteImage{
|
|
InputName: name,
|
|
ID: i.Id,
|
|
Digest: digest.Digest(i.Digest),
|
|
Digests: digests,
|
|
Labels: i.Labels,
|
|
RepoTags: i.RepoTags,
|
|
RepoDigests: i.RepoTags,
|
|
Parent: i.ParentId,
|
|
Size: i.Size,
|
|
Created: created,
|
|
Names: i.RepoTags,
|
|
isParent: i.IsParent,
|
|
Runtime: runtime,
|
|
TopLayer: i.TopLayer,
|
|
ReadOnly: i.ReadOnly,
|
|
NamesHistory: i.History,
|
|
}
|
|
return &ContainerImage{ri}, nil
|
|
}
|
|
|
|
// NewImageFromLocal returns a container image representation of a image over varlink
|
|
func (r *LocalRuntime) NewImageFromLocal(name string) (*ContainerImage, error) {
|
|
img, err := iopodman.GetImage().Call(r.Conn, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return imageInListToContainerImage(img, name, r)
|
|
|
|
}
|
|
|
|
// LoadFromArchiveReference creates an image from a local archive
|
|
func (r *LocalRuntime) LoadFromArchiveReference(ctx context.Context, srcRef types.ImageReference, signaturePolicyPath string, writer io.Writer) ([]*ContainerImage, error) {
|
|
var iid string
|
|
reply, err := iopodman.PullImage().Send(r.Conn, varlink.More, srcRef.DockerReference().String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for {
|
|
responses, flags, err := reply()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, line := range responses.Logs {
|
|
fmt.Print(line)
|
|
}
|
|
iid = responses.Id
|
|
if flags&varlink.Continues == 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
newImage, err := r.NewImageFromLocal(iid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []*ContainerImage{newImage}, nil
|
|
}
|
|
|
|
// New calls into local storage to look for an image in local storage or to pull it
|
|
func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *image.DockerRegistryOptions, signingoptions image.SigningOptions, label *string, pullType util.PullType) (*ContainerImage, error) {
|
|
var iid string
|
|
if label != nil {
|
|
return nil, errors.New("the remote client function does not support checking a remote image for a label")
|
|
}
|
|
reply, err := iopodman.PullImage().Send(r.Conn, varlink.More, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for {
|
|
responses, flags, err := reply()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, line := range responses.Logs {
|
|
fmt.Print(line)
|
|
}
|
|
iid = responses.Id
|
|
if flags&varlink.Continues == 0 {
|
|
break
|
|
}
|
|
}
|
|
newImage, err := r.NewImageFromLocal(iid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newImage, nil
|
|
}
|
|
|
|
// IsParent goes through the layers in the store and checks if i.TopLayer is
|
|
// the parent of any other layer in store. Double check that image with that
|
|
// layer exists as well.
|
|
func (ci *ContainerImage) IsParent(context.Context) (bool, error) {
|
|
return ci.remoteImage.isParent, nil
|
|
}
|
|
|
|
// ID returns the image ID as a string
|
|
func (ci *ContainerImage) ID() string {
|
|
return ci.remoteImage.ID
|
|
}
|
|
|
|
// Names returns a string array of names associated with the image
|
|
func (ci *ContainerImage) Names() []string {
|
|
return ci.remoteImage.Names
|
|
}
|
|
|
|
// NamesHistory returns a string array of names previously associated with the image
|
|
func (ci *ContainerImage) NamesHistory() []string {
|
|
return ci.remoteImage.NamesHistory
|
|
}
|
|
|
|
// Created returns the time the image was created
|
|
func (ci *ContainerImage) Created() time.Time {
|
|
return ci.remoteImage.Created
|
|
}
|
|
|
|
// IsReadOnly returns whether the image is ReadOnly
|
|
func (ci *ContainerImage) IsReadOnly() bool {
|
|
return ci.remoteImage.ReadOnly
|
|
}
|
|
|
|
// Size returns the size of the image
|
|
func (ci *ContainerImage) Size(ctx context.Context) (*uint64, error) {
|
|
usize := uint64(ci.remoteImage.Size)
|
|
return &usize, nil
|
|
}
|
|
|
|
// Digest returns the image's digest
|
|
func (ci *ContainerImage) Digest() digest.Digest {
|
|
return ci.remoteImage.Digest
|
|
}
|
|
|
|
// Digests returns the image's digests
|
|
func (ci *ContainerImage) Digests() []digest.Digest {
|
|
return append([]digest.Digest{}, ci.remoteImage.Digests...)
|
|
}
|
|
|
|
// Labels returns a map of the image's labels
|
|
func (ci *ContainerImage) Labels(ctx context.Context) (map[string]string, error) {
|
|
return ci.remoteImage.Labels, nil
|
|
}
|
|
|
|
// Dangling returns a bool if the image is "dangling"
|
|
func (ci *ContainerImage) Dangling() bool {
|
|
return len(ci.Names()) == 0
|
|
}
|
|
|
|
// TopLayer returns an images top layer as a string
|
|
func (ci *ContainerImage) TopLayer() string {
|
|
return ci.remoteImage.TopLayer
|
|
}
|
|
|
|
// TagImage ...
|
|
func (ci *ContainerImage) TagImage(tag string) error {
|
|
_, err := iopodman.TagImage().Call(ci.Runtime.Conn, ci.ID(), tag)
|
|
return err
|
|
}
|
|
|
|
// UntagImage removes a single tag from an image
|
|
func (ci *ContainerImage) UntagImage(tag string) error {
|
|
_, err := iopodman.UntagImage().Call(ci.Runtime.Conn, ci.ID(), tag)
|
|
return err
|
|
}
|
|
|
|
// RemoveImage calls varlink to remove an image
|
|
func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, force bool) (*image.ImageDeleteResponse, error) {
|
|
ir := image.ImageDeleteResponse{}
|
|
response, err := iopodman.RemoveImageWithResponse().Call(r.Conn, img.InputName, force)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ir.Deleted = response.Deleted
|
|
ir.Untagged = append(ir.Untagged, response.Untagged...)
|
|
return &ir, nil
|
|
}
|
|
|
|
// History returns the history of an image and its layers
|
|
func (ci *ContainerImage) History(ctx context.Context) ([]*image.History, error) {
|
|
var imageHistories []*image.History
|
|
|
|
reply, err := iopodman.HistoryImage().Call(ci.Runtime.Conn, ci.InputName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, h := range reply {
|
|
created, err := time.ParseInLocation(time.RFC3339, h.Created, time.UTC)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ih := image.History{
|
|
ID: h.Id,
|
|
Created: &created,
|
|
CreatedBy: h.CreatedBy,
|
|
Size: h.Size,
|
|
Comment: h.Comment,
|
|
}
|
|
imageHistories = append(imageHistories, &ih)
|
|
}
|
|
return imageHistories, nil
|
|
}
|
|
|
|
// PruneImages is the wrapper call for a remote-client to prune images
|
|
func (r *LocalRuntime) PruneImages(ctx context.Context, all bool, filter []string) ([]string, error) {
|
|
return iopodman.ImagesPrune().Call(r.Conn, all, filter)
|
|
}
|
|
|
|
// Export is a wrapper to container export to a tarfile
|
|
func (r *LocalRuntime) Export(name string, path string) error {
|
|
tempPath, err := iopodman.ExportContainer().Call(r.Conn, name, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return r.GetFileFromRemoteHost(tempPath, path, true)
|
|
}
|
|
|
|
func (r *LocalRuntime) GetFileFromRemoteHost(remoteFilePath, outputPath string, delete bool) error {
|
|
outputFile, err := os.Create(outputPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer outputFile.Close()
|
|
|
|
writer := bufio.NewWriter(outputFile)
|
|
defer writer.Flush()
|
|
|
|
reply, err := iopodman.ReceiveFile().Send(r.Conn, varlink.Upgrade, remoteFilePath, delete)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
length, _, err := reply()
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to get file length for transfer")
|
|
}
|
|
|
|
reader := r.Conn.Reader
|
|
if _, err := io.CopyN(writer, reader, length); err != nil {
|
|
return errors.Wrap(err, "file transfer failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Import implements the remote calls required to import a container image to the store
|
|
func (r *LocalRuntime) Import(ctx context.Context, source, reference string, changes []string, history string, quiet bool) (string, error) {
|
|
// First we send the file to the host
|
|
tempFile, err := r.SendFileOverVarlink(source)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return iopodman.ImportImage().Call(r.Conn, strings.TrimRight(tempFile, ":"), reference, history, changes, true)
|
|
}
|
|
|
|
func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, options imagebuildah.BuildOptions, dockerfiles []string) (string, reference.Canonical, error) {
|
|
buildOptions := iopodman.BuildOptions{
|
|
AddHosts: options.CommonBuildOpts.AddHost,
|
|
CgroupParent: options.CommonBuildOpts.CgroupParent,
|
|
CpuPeriod: int64(options.CommonBuildOpts.CPUPeriod),
|
|
CpuQuota: options.CommonBuildOpts.CPUQuota,
|
|
CpuShares: int64(options.CommonBuildOpts.CPUShares),
|
|
CpusetCpus: options.CommonBuildOpts.CPUSetMems,
|
|
CpusetMems: options.CommonBuildOpts.CPUSetMems,
|
|
Memory: options.CommonBuildOpts.Memory,
|
|
MemorySwap: options.CommonBuildOpts.MemorySwap,
|
|
ShmSize: options.CommonBuildOpts.ShmSize,
|
|
Ulimit: options.CommonBuildOpts.Ulimit,
|
|
Volume: options.CommonBuildOpts.Volumes,
|
|
}
|
|
|
|
buildinfo := iopodman.BuildInfo{
|
|
AdditionalTags: options.AdditionalTags,
|
|
Annotations: options.Annotations,
|
|
BuildArgs: options.Args,
|
|
BuildOptions: buildOptions,
|
|
CniConfigDir: options.CNIConfigDir,
|
|
CniPluginDir: options.CNIPluginPath,
|
|
Compression: string(options.Compression),
|
|
DefaultsMountFilePath: options.DefaultMountsFilePath,
|
|
Dockerfiles: dockerfiles,
|
|
// Err: string(options.Err),
|
|
ForceRmIntermediateCtrs: options.ForceRmIntermediateCtrs,
|
|
Iidfile: options.IIDFile,
|
|
Label: options.Labels,
|
|
Layers: options.Layers,
|
|
Nocache: options.NoCache,
|
|
// Out:
|
|
Output: options.Output,
|
|
OutputFormat: options.OutputFormat,
|
|
PullPolicy: options.PullPolicy.String(),
|
|
Quiet: options.Quiet,
|
|
RemoteIntermediateCtrs: options.RemoveIntermediateCtrs,
|
|
// ReportWriter:
|
|
RuntimeArgs: options.RuntimeArgs,
|
|
Squash: options.Squash,
|
|
}
|
|
// tar the file
|
|
outputFile, err := ioutil.TempFile("", "varlink_tar_send")
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
defer outputFile.Close()
|
|
defer os.Remove(outputFile.Name())
|
|
|
|
// Create the tarball of the context dir to a tempfile
|
|
if err := utils.TarToFilesystem(options.ContextDirectory, outputFile); err != nil {
|
|
return "", nil, err
|
|
}
|
|
// Send the context dir tarball over varlink.
|
|
tempFile, err := r.SendFileOverVarlink(outputFile.Name())
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
buildinfo.ContextDir = tempFile
|
|
|
|
reply, err := iopodman.BuildImage().Send(r.Conn, varlink.More, buildinfo)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
for {
|
|
responses, flags, err := reply()
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
for _, line := range responses.Logs {
|
|
fmt.Print(line)
|
|
}
|
|
if flags&varlink.Continues == 0 {
|
|
break
|
|
}
|
|
}
|
|
return "", nil, err
|
|
}
|
|
|
|
// SendFileOverVarlink sends a file over varlink in an upgraded connection
|
|
func (r *LocalRuntime) SendFileOverVarlink(source string) (string, error) {
|
|
fs, err := os.Open(source)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
fileInfo, err := fs.Stat()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
logrus.Debugf("sending %s over varlink connection", source)
|
|
reply, err := iopodman.SendFile().Send(r.Conn, varlink.Upgrade, "", int64(fileInfo.Size()))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
_, _, err = reply()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
reader := bufio.NewReader(fs)
|
|
_, err = reader.WriteTo(r.Conn.Writer)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
logrus.Debugf("file transfer complete for %s", source)
|
|
r.Conn.Writer.Flush()
|
|
|
|
// All was sent, wait for the ACK from the server
|
|
tempFile, err := r.Conn.Reader.ReadString(':')
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// r.Conn is kaput at this point due to the upgrade
|
|
if err := r.RemoteRuntime.RefreshConnection(); err != nil {
|
|
return "", err
|
|
|
|
}
|
|
|
|
return strings.Replace(tempFile, ":", "", -1), nil
|
|
}
|
|
|
|
// GetAllVolumes retrieves all the volumes
|
|
func (r *LocalRuntime) GetAllVolumes() ([]*libpod.Volume, error) {
|
|
return nil, define.ErrNotImplemented
|
|
}
|
|
|
|
// RemoveVolume removes a volumes
|
|
func (r *LocalRuntime) RemoveVolume(ctx context.Context, v *libpod.Volume, force, prune bool) error {
|
|
return define.ErrNotImplemented
|
|
}
|
|
|
|
// GetContainers retrieves all containers from the state
|
|
// Filters can be provided which will determine what containers are included in
|
|
// the output. Multiple filters are handled by ANDing their output, so only
|
|
// containers matching all filters are returned
|
|
func (r *LocalRuntime) GetContainers(filters ...libpod.ContainerFilter) ([]*libpod.Container, error) {
|
|
return nil, define.ErrNotImplemented
|
|
}
|
|
|
|
// RemoveContainer removes the given container
|
|
// If force is specified, the container will be stopped first
|
|
// Otherwise, RemoveContainer will return an error if the container is running
|
|
func (r *LocalRuntime) RemoveContainer(ctx context.Context, c *libpod.Container, force, volumes bool) error {
|
|
return define.ErrNotImplemented
|
|
}
|
|
|
|
// CreateVolume creates a volume over a varlink connection for the remote client
|
|
func (r *LocalRuntime) CreateVolume(ctx context.Context, c *cliconfig.VolumeCreateValues, labels, opts map[string]string) (string, error) {
|
|
cvOpts := iopodman.VolumeCreateOpts{
|
|
Options: opts,
|
|
Labels: labels,
|
|
}
|
|
if len(c.InputArgs) > 0 {
|
|
cvOpts.VolumeName = c.InputArgs[0]
|
|
}
|
|
|
|
if c.Flag("driver").Changed {
|
|
cvOpts.Driver = c.Driver
|
|
}
|
|
|
|
return iopodman.VolumeCreate().Call(r.Conn, cvOpts)
|
|
}
|
|
|
|
// RemoveVolumes removes volumes over a varlink connection for the remote client
|
|
func (r *LocalRuntime) RemoveVolumes(ctx context.Context, c *cliconfig.VolumeRmValues) ([]string, map[string]error, error) {
|
|
rmOpts := iopodman.VolumeRemoveOpts{
|
|
All: c.All,
|
|
Force: c.Force,
|
|
Volumes: c.InputArgs,
|
|
}
|
|
success, failures, err := iopodman.VolumeRemove().Call(r.Conn, rmOpts)
|
|
stringsToErrors := make(map[string]error)
|
|
for k, v := range failures {
|
|
stringsToErrors[k] = errors.New(v)
|
|
}
|
|
return success, stringsToErrors, err
|
|
}
|
|
|
|
func (r *LocalRuntime) Push(ctx context.Context, srcName, destination, manifestMIMEType, authfile, digestfile, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions image.SigningOptions, dockerRegistryOptions *image.DockerRegistryOptions, additionalDockerArchiveTags []reference.NamedTagged) error {
|
|
|
|
reply, err := iopodman.PushImage().Send(r.Conn, varlink.More, srcName, destination, forceCompress, manifestMIMEType, signingOptions.RemoveSignatures, signingOptions.SignBy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for {
|
|
responses, flags, err := reply()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, line := range responses.Logs {
|
|
fmt.Print(line)
|
|
}
|
|
if flags&varlink.Continues == 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// InspectVolumes returns a slice of volumes based on an arg list or --all
|
|
func (r *LocalRuntime) InspectVolumes(ctx context.Context, c *cliconfig.VolumeInspectValues) ([]*libpod.InspectVolumeData, error) {
|
|
var (
|
|
inspectData []*libpod.InspectVolumeData
|
|
volumes []string
|
|
)
|
|
|
|
if c.All {
|
|
allVolumes, err := r.Volumes(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, vol := range allVolumes {
|
|
volumes = append(volumes, vol.Name())
|
|
}
|
|
} else {
|
|
for _, arg := range c.InputArgs {
|
|
volumes = append(volumes, arg)
|
|
}
|
|
}
|
|
|
|
for _, vol := range volumes {
|
|
jsonString, err := iopodman.InspectVolume().Call(r.Conn, vol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
inspectJSON := new(libpod.InspectVolumeData)
|
|
if err := json.Unmarshal([]byte(jsonString), inspectJSON); err != nil {
|
|
return nil, errors.Wrapf(err, "error unmarshalling inspect JSON for volume %s", vol)
|
|
}
|
|
inspectData = append(inspectData, inspectJSON)
|
|
}
|
|
|
|
return inspectData, nil
|
|
}
|
|
|
|
// Volumes returns a slice of adapter.volumes based on information about libpod
|
|
// volumes over a varlink connection
|
|
func (r *LocalRuntime) Volumes(ctx context.Context) ([]*Volume, error) {
|
|
reply, err := iopodman.GetVolumes().Call(r.Conn, []string{}, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return varlinkVolumeToVolume(r, reply), nil
|
|
}
|
|
|
|
func varlinkVolumeToVolume(r *LocalRuntime, volumes []iopodman.Volume) []*Volume {
|
|
var vols []*Volume
|
|
for _, v := range volumes {
|
|
volumeConfig := libpod.VolumeConfig{
|
|
Name: v.Name,
|
|
Labels: v.Labels,
|
|
MountPoint: v.MountPoint,
|
|
Driver: v.Driver,
|
|
Options: v.Options,
|
|
}
|
|
n := remoteVolume{
|
|
Runtime: r,
|
|
config: &volumeConfig,
|
|
}
|
|
newVol := Volume{
|
|
n,
|
|
}
|
|
vols = append(vols, &newVol)
|
|
}
|
|
return vols
|
|
}
|
|
|
|
// PruneVolumes removes all unused volumes from the remote system
|
|
func (r *LocalRuntime) PruneVolumes(ctx context.Context) ([]string, []error) {
|
|
var errs []error
|
|
prunedNames, prunedErrors, err := iopodman.VolumesPrune().Call(r.Conn)
|
|
if err != nil {
|
|
return []string{}, []error{err}
|
|
}
|
|
// We need to transform the string results of the error into actual error types
|
|
for _, e := range prunedErrors {
|
|
errs = append(errs, errors.New(e))
|
|
}
|
|
return prunedNames, errs
|
|
}
|
|
|
|
// SaveImage is a wrapper function for saving an image to the local filesystem
|
|
func (r *LocalRuntime) SaveImage(ctx context.Context, c *cliconfig.SaveValues) error {
|
|
source := c.InputArgs[0]
|
|
additionalTags := c.InputArgs[1:]
|
|
|
|
options := iopodman.ImageSaveOptions{
|
|
Name: source,
|
|
Format: c.Format,
|
|
Output: c.Output,
|
|
MoreTags: additionalTags,
|
|
Quiet: c.Quiet,
|
|
Compress: c.Compress,
|
|
}
|
|
reply, err := iopodman.ImageSave().Send(r.Conn, varlink.More, options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var fetchfile string
|
|
for {
|
|
responses, flags, err := reply()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(responses.Id) > 0 {
|
|
fetchfile = responses.Id
|
|
}
|
|
for _, line := range responses.Logs {
|
|
fmt.Print(line)
|
|
}
|
|
if flags&varlink.Continues == 0 {
|
|
break
|
|
}
|
|
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
outputToDir := false
|
|
outfile := c.Output
|
|
var outputFile *os.File
|
|
// If the result is supposed to be a dir, then we need to put the tarfile
|
|
// from the host in a temporary file
|
|
if options.Format != "oci-archive" && options.Format != "docker-archive" {
|
|
outputToDir = true
|
|
outputFile, err = ioutil.TempFile("", "saveimage_tempfile")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
outfile = outputFile.Name()
|
|
defer outputFile.Close()
|
|
defer os.Remove(outputFile.Name())
|
|
}
|
|
// We now need to fetch the tarball result back to the more system
|
|
if err := r.GetFileFromRemoteHost(fetchfile, outfile, true); err != nil {
|
|
return err
|
|
}
|
|
|
|
// If the result is a tarball, we're done
|
|
// If it is a dir, we need to untar the temporary file into the dir
|
|
if outputToDir {
|
|
if err := utils.UntarToFileSystem(c.Output, outputFile, &archive.TarOptions{}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// LoadImage loads a container image from a remote client's filesystem
|
|
func (r *LocalRuntime) LoadImage(ctx context.Context, name string, cli *cliconfig.LoadValues) (string, error) {
|
|
var names string
|
|
remoteTempFile, err := r.SendFileOverVarlink(cli.Input)
|
|
if err != nil {
|
|
return "", nil
|
|
}
|
|
more := varlink.More
|
|
if cli.Quiet {
|
|
more = 0
|
|
}
|
|
reply, err := iopodman.LoadImage().Send(r.Conn, uint64(more), name, remoteTempFile, cli.Quiet, true)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for {
|
|
responses, flags, err := reply()
|
|
if err != nil {
|
|
logrus.Error(err)
|
|
return "", err
|
|
}
|
|
for _, line := range responses.Logs {
|
|
fmt.Print(line)
|
|
}
|
|
names = responses.Id
|
|
if flags&varlink.Continues == 0 {
|
|
break
|
|
}
|
|
}
|
|
return names, nil
|
|
}
|
|
|
|
// IsImageNotFound checks if the error indicates that no image was found.
|
|
func IsImageNotFound(err error) bool {
|
|
if errors.Cause(err) == image.ErrNoSuchImage {
|
|
return true
|
|
}
|
|
switch err.(type) {
|
|
case *iopodman.ImageNotFound:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// HealthCheck executes a container's healthcheck over a varlink connection
|
|
func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (string, error) {
|
|
return iopodman.HealthCheckRun().Call(r.Conn, c.InputArgs[0])
|
|
}
|
|
|
|
// Events monitors libpod/podman events over a varlink connection
|
|
func (r *LocalRuntime) Events(c *cliconfig.EventValues) error {
|
|
var more uint64
|
|
if c.Stream {
|
|
more = uint64(varlink.More)
|
|
}
|
|
reply, err := iopodman.GetEvents().Send(r.Conn, more, c.Filter, c.Since, c.Until)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to obtain events")
|
|
}
|
|
|
|
w := bufio.NewWriter(os.Stdout)
|
|
var tmpl *template.Template
|
|
if c.Format != formats.JSONString {
|
|
template, err := template.New("events").Parse(c.Format)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tmpl = template
|
|
}
|
|
|
|
for {
|
|
returnedEvent, flags, err := reply()
|
|
if err != nil {
|
|
// When the error handling is back into podman, we can flip this to a better way to check
|
|
// for problems. For now, this works.
|
|
return err
|
|
}
|
|
if returnedEvent.Time == "" && returnedEvent.Status == "" && returnedEvent.Type == "" {
|
|
// We got a blank event return, signals end of stream in certain cases
|
|
break
|
|
}
|
|
eTime, err := time.Parse(time.RFC3339Nano, returnedEvent.Time)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to parse time of event %s", returnedEvent.Time)
|
|
}
|
|
eType, err := events.StringToType(returnedEvent.Type)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
eStatus, err := events.StringToStatus(returnedEvent.Status)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
event := events.Event{
|
|
ID: returnedEvent.Id,
|
|
Image: returnedEvent.Image,
|
|
Name: returnedEvent.Name,
|
|
Status: eStatus,
|
|
Time: eTime,
|
|
Type: eType,
|
|
}
|
|
if c.Format == formats.JSONString {
|
|
jsonStr, err := event.ToJSONString()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to format json")
|
|
}
|
|
if _, err := w.Write([]byte(jsonStr)); err != nil {
|
|
return err
|
|
}
|
|
} else if len(c.Format) > 0 {
|
|
if err := tmpl.Execute(w, event); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if _, err := w.Write([]byte(event.ToHumanReadable())); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if _, err := w.Write([]byte("\n")); err != nil {
|
|
return err
|
|
}
|
|
if err := w.Flush(); err != nil {
|
|
return err
|
|
}
|
|
if flags&varlink.Continues == 0 {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Diff ...
|
|
func (r *LocalRuntime) Diff(c *cliconfig.DiffValues, to string) ([]archive.Change, error) {
|
|
var changes []archive.Change
|
|
reply, err := iopodman.Diff().Call(r.Conn, to)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, change := range reply {
|
|
changes = append(changes, archive.Change{Path: change.Path, Kind: stringToChangeType(change.ChangeType)})
|
|
}
|
|
return changes, nil
|
|
}
|
|
|
|
func stringToChangeType(change string) archive.ChangeType {
|
|
switch change {
|
|
case "A":
|
|
return archive.ChangeAdd
|
|
case "D":
|
|
return archive.ChangeDelete
|
|
default:
|
|
logrus.Errorf("'%s' is unknown archive type", change)
|
|
fallthrough
|
|
case "C":
|
|
return archive.ChangeModify
|
|
}
|
|
}
|
|
|
|
// GenerateKube creates kubernetes email from containers and pods
|
|
func (r *LocalRuntime) GenerateKube(c *cliconfig.GenerateKubeValues) (*v1.Pod, *v1.Service, error) {
|
|
var (
|
|
pod v1.Pod
|
|
service v1.Service
|
|
)
|
|
reply, err := iopodman.GenerateKube().Call(r.Conn, c.InputArgs[0], c.Service)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "unable to create kubernetes YAML")
|
|
}
|
|
if err := json.Unmarshal([]byte(reply.Pod), &pod); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
err = json.Unmarshal([]byte(reply.Service), &service)
|
|
return &pod, &service, err
|
|
}
|
|
|
|
// GetContainersByContext looks up containers based on the cli input of all, latest, or a list
|
|
func (r *LocalRuntime) GetContainersByContext(all bool, latest bool, namesOrIDs []string) ([]*Container, error) {
|
|
var containers []*Container
|
|
cids, err := iopodman.GetContainersByContext().Call(r.Conn, all, latest, namesOrIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, cid := range cids {
|
|
ctr, err := r.LookupContainer(cid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
containers = append(containers, ctr)
|
|
}
|
|
return containers, nil
|
|
}
|
|
|
|
// GetVersion returns version information from service
|
|
func (r *LocalRuntime) GetVersion() (define.Version, error) {
|
|
version, goVersion, gitCommit, built, osArch, apiVersion, err := iopodman.GetVersion().Call(r.Conn)
|
|
if err != nil {
|
|
return define.Version{}, errors.Wrapf(err, "Unable to obtain server version information")
|
|
}
|
|
|
|
var buildTime int64
|
|
if built != "" {
|
|
t, err := time.Parse(time.RFC3339, built)
|
|
if err != nil {
|
|
return define.Version{}, nil
|
|
}
|
|
buildTime = t.Unix()
|
|
}
|
|
|
|
return define.Version{
|
|
RemoteAPIVersion: apiVersion,
|
|
Version: version,
|
|
GoVersion: goVersion,
|
|
GitCommit: gitCommit,
|
|
Built: buildTime,
|
|
OsArch: osArch,
|
|
}, nil
|
|
}
|