Files
podman/pkg/adapter/runtime_remote.go
Matthew Heon 03da8b641d Rewrite backend for remote 'volume inspect'
We need to use the new Inspect() endpoint instead of trying to
JSON the actual volume structs. Currently, the output seems
completely nonsensical; it seems like we're JSONing the struct
for the Varlink connection itself? This should restore sanity and
match the format of remote and local inspect on volumes.

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2019-10-21 14:37:27 -04:00

1030 lines
28 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/v4/docker/reference"
"github.com/containers/image/v4/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
isParent bool
Runtime *LocalRuntime
TopLayer string
ReadOnly bool
}
// 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) 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
}
ri := remoteImage{
InputName: name,
ID: i.Id,
Digest: digest.Digest(i.Digest),
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,
}
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
}
// 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
}
// 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
}
// RemoveImage calls varlink to remove an image
func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, force bool) (string, error) {
return iopodman.RemoveImage().Call(r.Conn, img.InputName, force)
}
// 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) ([]string, error) {
return iopodman.ImagesPrune().Call(r.Conn, all)
}
// 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 transer 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) 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 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 err
}
// Send the context dir tarball over varlink.
tempFile, err := r.SendFileOverVarlink(outputFile.Name())
if err != nil {
return err
}
buildinfo.ContextDir = tempFile
reply, err := iopodman.BuildImage().Send(r.Conn, varlink.More, buildinfo)
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
}
// 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
}