Merge pull request #988 from umohnani8/libpod-part2

Continue switching from libkpod to libpod
This commit is contained in:
Daniel J Walsh 2017-10-10 16:55:53 -04:00 committed by GitHub
commit 772f4b1515
18 changed files with 820 additions and 837 deletions

View file

@ -2,9 +2,9 @@ package main
import ( import (
"fmt" "fmt"
"github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/archive"
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats" "github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
"github.com/kubernetes-incubator/cri-o/libkpod"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -74,25 +74,22 @@ func formatJSON(output []diffOutputParams) (diffJSONOutput, error) {
} }
func diffCmd(c *cli.Context) error { func diffCmd(c *cli.Context) error {
if len(c.Args()) != 1 {
return errors.Errorf("container, layer, or image name must be specified: kpod diff [options [...]] ID-NAME")
}
if err := validateFlags(c, diffFlags); err != nil { if err := validateFlags(c, diffFlags); err != nil {
return err return err
} }
config, err := getConfig(c)
if err != nil { if len(c.Args()) != 1 {
return errors.Wrapf(err, "could not get config") return errors.Errorf("container, image, or layer name must be specified: kpod diff [options [...]] ID-NAME")
} }
server, err := libkpod.New(config) runtime, err := getRuntime(c)
if err != nil { if err != nil {
return errors.Wrapf(err, "could not get container server") return errors.Wrapf(err, "could not get runtime")
} }
defer server.Shutdown() defer runtime.Shutdown(false)
to := c.Args().Get(0) to := c.Args().Get(0)
changes, err := server.GetDiff("", to) changes, err := runtime.GetDiff("", to)
if err != nil { if err != nil {
return errors.Wrapf(err, "could not get changes for %q", to) return errors.Wrapf(err, "could not get changes for %q", to)
} }

View file

@ -6,12 +6,9 @@ import (
"strings" "strings"
"time" "time"
is "github.com/containers/image/storage"
"github.com/containers/image/types" "github.com/containers/image/types"
"github.com/containers/storage"
units "github.com/docker/go-units" units "github.com/docker/go-units"
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats" "github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
"github.com/kubernetes-incubator/cri-o/libpod/common"
"github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -45,7 +42,6 @@ type historyJSONParams struct {
// historyOptions stores cli flag values // historyOptions stores cli flag values
type historyOptions struct { type historyOptions struct {
image string
human bool human bool
noTrunc bool noTrunc bool
quiet bool quiet bool
@ -88,14 +84,12 @@ func historyCmd(c *cli.Context) error {
if err := validateFlags(c, historyFlags); err != nil { if err := validateFlags(c, historyFlags); err != nil {
return err return err
} }
config, err := getConfig(c)
runtime, err := getRuntime(c)
if err != nil { if err != nil {
return errors.Wrapf(err, "Could not get config") return errors.Wrapf(err, "Could not get config")
} }
store, err := getStore(config) defer runtime.Shutdown(false)
if err != nil {
return err
}
format := genHistoryFormat(c.Bool("quiet")) format := genHistoryFormat(c.Bool("quiet"))
if c.IsSet("format") { if c.IsSet("format") {
@ -112,13 +106,18 @@ func historyCmd(c *cli.Context) error {
imgName := args[0] imgName := args[0]
opts := historyOptions{ opts := historyOptions{
image: imgName,
human: c.BoolT("human"), human: c.BoolT("human"),
noTrunc: c.Bool("no-trunc"), noTrunc: c.Bool("no-trunc"),
quiet: c.Bool("quiet"), quiet: c.Bool("quiet"),
format: format, format: format,
} }
return generateHistoryOutput(store, opts)
history, layers, imageID, err := runtime.GetHistory(imgName)
if err != nil {
return errors.Wrapf(err, "error getting history of image %q", imgName)
}
return generateHistoryOutput(history, layers, imageID, opts)
} }
func genHistoryFormat(quiet bool) (format string) { func genHistoryFormat(quiet bool) (format string) {
@ -154,33 +153,6 @@ func (h *historyTemplateParams) headerMap() map[string]string {
return values return values
} }
// getHistory gets the history of an image and information about its layers
func getHistory(store storage.Store, image string) ([]v1.History, []types.BlobInfo, string, error) {
ref, err := is.Transport.ParseStoreReference(store, image)
if err != nil {
return nil, nil, "", errors.Wrapf(err, "error parsing reference to image %q", image)
}
img, err := is.Transport.GetStoreImage(store, ref)
if err != nil {
return nil, nil, "", errors.Wrapf(err, "no such image %q", image)
}
systemContext := common.GetSystemContext("")
src, err := ref.NewImage(systemContext)
if err != nil {
return nil, nil, "", errors.Wrapf(err, "error instantiating image %q", image)
}
oci, err := src.OCIConfig()
if err != nil {
return nil, nil, "", err
}
return oci.History, src.LayerInfos(), img.ID, nil
}
// getHistorytemplateOutput gets the modified history information to be printed in human readable format // getHistorytemplateOutput gets the modified history information to be printed in human readable format
func getHistoryTemplateOutput(history []v1.History, layers []types.BlobInfo, imageID string, opts historyOptions) (historyOutput []historyTemplateParams) { func getHistoryTemplateOutput(history []v1.History, layers []types.BlobInfo, imageID string, opts historyOptions) (historyOutput []historyTemplateParams) {
var ( var (
@ -251,11 +223,7 @@ func getHistoryJSONOutput(history []v1.History, layers []types.BlobInfo, imageID
} }
// generateHistoryOutput generates the history based on the format given // generateHistoryOutput generates the history based on the format given
func generateHistoryOutput(store storage.Store, opts historyOptions) error { func generateHistoryOutput(history []v1.History, layers []types.BlobInfo, imageID string, opts historyOptions) error {
history, layers, imageID, err := getHistory(store, opts.image)
if err != nil {
return errors.Wrapf(err, "error getting history of image %q", opts.image)
}
if len(history) == 0 { if len(history) == 0 {
return nil return nil
} }

View file

@ -1,17 +1,46 @@
package main package main
import ( import (
"fmt"
"reflect" "reflect"
"strings" "strings"
"time"
"github.com/containers/image/types"
"github.com/containers/storage" "github.com/containers/storage"
"github.com/docker/go-units"
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats" "github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
libpod "github.com/kubernetes-incubator/cri-o/libpod/images" "github.com/kubernetes-incubator/cri-o/libpod"
"github.com/kubernetes-incubator/cri-o/libpod/common"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
type imagesTemplateParams struct {
ID string
Name string
Digest digest.Digest
CreatedAt string
Size string
}
type imagesJSONParams struct {
ID string `json:"id"`
Name []string `json:"names"`
Digest digest.Digest `json:"digest"`
CreatedAt time.Time `json:"created"`
Size int64 `json:"size"`
}
type imagesOptions struct {
quiet bool
noHeading bool
noTrunc bool
digests bool
format string
}
var ( var (
imagesFlags = []cli.Flag{ imagesFlags = []cli.Flag{
cli.BoolFlag{ cli.BoolFlag{
@ -55,145 +84,88 @@ func imagesCmd(c *cli.Context) error {
if err := validateFlags(c, imagesFlags); err != nil { if err := validateFlags(c, imagesFlags); err != nil {
return err return err
} }
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "Could not get config")
}
store, err := getStore(config)
if err != nil {
return err
}
quiet := false runtime, err := getRuntime(c)
if c.IsSet("quiet") { if err != nil {
quiet = c.Bool("quiet") return errors.Wrapf(err, "Could not get runtime")
} }
noheading := false defer runtime.Shutdown(false)
if c.IsSet("noheading") {
noheading = c.Bool("noheading") var format string
}
truncate := true
if c.IsSet("no-trunc") {
truncate = !c.Bool("no-trunc")
}
digests := false
if c.IsSet("digests") {
digests = c.Bool("digests")
}
outputFormat := genImagesFormat(quiet, truncate, digests)
if c.IsSet("format") { if c.IsSet("format") {
outputFormat = c.String("format") format = c.String("format")
} else {
format = genImagesFormat(c.Bool("quiet"), c.Bool("noheading"), c.Bool("digests"))
} }
name := "" opts := imagesOptions{
quiet: c.Bool("quiet"),
noHeading: c.Bool("noheading"),
noTrunc: c.Bool("no-trunc"),
digests: c.Bool("digests"),
format: format,
}
var imageInput string
if len(c.Args()) == 1 { if len(c.Args()) == 1 {
name = c.Args().Get(0) imageInput = c.Args().Get(0)
} else if len(c.Args()) > 1 { }
if len(c.Args()) > 1 {
return errors.New("'kpod images' requires at most 1 argument") return errors.New("'kpod images' requires at most 1 argument")
} }
var params *libpod.FilterParams params, err := runtime.ParseImageFilter(imageInput, c.String("filter"))
if c.IsSet("filter") {
params, err = libpod.ParseFilter(store, c.String("filter"))
if err != nil { if err != nil {
return errors.Wrapf(err, "error parsing filter") return errors.Wrapf(err, "error parsing filter")
} }
} else {
params = nil
}
imageList, err := libpod.GetImagesMatchingFilter(store, params, name) // generate the different filters
labelFilter := generateImagesFilter(params, "label")
beforeImageFilter := generateImagesFilter(params, "before-image")
sinceImageFilter := generateImagesFilter(params, "since-image")
danglingFilter := generateImagesFilter(params, "dangling")
referenceFilter := generateImagesFilter(params, "reference")
imageInputFilter := generateImagesFilter(params, "image-input")
images, err := runtime.GetImages(params, labelFilter, beforeImageFilter, sinceImageFilter, danglingFilter, referenceFilter, imageInputFilter)
if err != nil { if err != nil {
return errors.Wrapf(err, "could not get list of images matching filter") return errors.Wrapf(err, "could not get list of images matching filter")
} }
return outputImages(store, imageList, truncate, digests, quiet, outputFormat, noheading) return generateImagesOutput(runtime, images, opts)
} }
func genImagesFormat(quiet, truncate, digests bool) (format string) { func genImagesFormat(quiet, noHeading, digests bool) (format string) {
if quiet { if quiet {
return formats.IDString return formats.IDString
} }
if truncate { format = "table {{.ID}}\t{{.Name}}\t"
format = "table {{ .ID | printf \"%-20.12s\" }} " if noHeading {
} else { format = "{{.ID}}\t{{.Name}}\t"
format = "table {{ .ID | printf \"%-64s\" }} "
} }
format += "{{ .Name | printf \"%-56s\" }} "
if digests { if digests {
format += "{{ .Digest | printf \"%-71s \"}} " format += "{{.Digest}}\t"
} }
format += "{{.CreatedAt}}\t{{.Size}}\t"
format += "{{ .CreatedAt | printf \"%-22s\" }} {{.Size}}"
return return
} }
func outputImages(store storage.Store, images []storage.Image, truncate, digests, quiet bool, outputFormat string, noheading bool) error { // imagesToGeneric creates an empty array of interfaces for output
imageOutput := []imageOutputParams{} func imagesToGeneric(templParams []imagesTemplateParams, JSONParams []imagesJSONParams) (genericParams []interface{}) {
if len(templParams) > 0 {
lastID := "" for _, v := range templParams {
for _, img := range images { genericParams = append(genericParams, interface{}(v))
if quiet && lastID == img.ID {
continue // quiet should not show the same ID multiple times
} }
createdTime := img.Created return
}
names := []string{""} for _, v := range JSONParams {
if len(img.Names) > 0 { genericParams = append(genericParams, interface{}(v))
names = img.Names }
return
} }
info, imageDigest, size, _ := libpod.InfoAndDigestAndSize(store, img) // generate the header based on the template provided
if info != nil { func (i *imagesTemplateParams) headerMap() map[string]string {
createdTime = info.Created
}
params := imageOutputParams{
ID: img.ID,
Name: names,
Digest: imageDigest,
CreatedAt: createdTime.Format("Jan 2, 2006 15:04"),
Size: libpod.FormattedSize(float64(size)),
}
imageOutput = append(imageOutput, params)
}
var out formats.Writer
switch outputFormat {
case formats.JSONString:
out = formats.JSONStructArray{Output: toGeneric(imageOutput)}
default:
if len(imageOutput) == 0 {
out = formats.StdoutTemplateArray{}
} else {
out = formats.StdoutTemplateArray{Output: toGeneric(imageOutput), Template: outputFormat, Fields: imageOutput[0].headerMap()}
}
}
formats.Writer(out).Out()
return nil
}
type imageOutputParams struct {
ID string `json:"id"`
Name []string `json:"names"`
Digest digest.Digest `json:"digest"`
CreatedAt string `json:"created"`
Size string `json:"size"`
}
func toGeneric(params []imageOutputParams) []interface{} {
genericParams := make([]interface{}, len(params))
for i, v := range params {
genericParams[i] = interface{}(v)
}
return genericParams
}
func (i *imageOutputParams) headerMap() map[string]string {
v := reflect.Indirect(reflect.ValueOf(i)) v := reflect.Indirect(reflect.ValueOf(i))
values := make(map[string]string) values := make(map[string]string)
@ -207,3 +179,152 @@ func (i *imageOutputParams) headerMap() map[string]string {
} }
return values return values
} }
// getImagesTemplateOutput returns the images information to be printed in human readable format
func getImagesTemplateOutput(runtime *libpod.Runtime, images []*storage.Image, opts imagesOptions) (imagesOutput []imagesTemplateParams) {
var (
lastID string
)
for _, img := range images {
if opts.quiet && lastID == img.ID {
continue // quiet should not show the same ID multiple times
}
createdTime := img.Created
imageID := img.ID
if !opts.noTrunc {
imageID = imageID[:idTruncLength]
}
imageName := "<none>"
if len(img.Names) > 0 {
imageName = img.Names[0]
}
info, imageDigest, size, _ := runtime.InfoAndDigestAndSize(*img)
if info != nil {
createdTime = info.Created
}
params := imagesTemplateParams{
ID: imageID,
Name: imageName,
Digest: imageDigest,
CreatedAt: units.HumanDuration(time.Since((createdTime))) + " ago",
Size: units.HumanSize(float64(size)),
}
imagesOutput = append(imagesOutput, params)
}
return
}
// getImagesJSONOutput returns the images information in its raw form
func getImagesJSONOutput(runtime *libpod.Runtime, images []*storage.Image) (imagesOutput []imagesJSONParams) {
for _, img := range images {
createdTime := img.Created
info, imageDigest, size, _ := runtime.InfoAndDigestAndSize(*img)
if info != nil {
createdTime = info.Created
}
params := imagesJSONParams{
ID: img.ID,
Name: img.Names,
Digest: imageDigest,
CreatedAt: createdTime,
Size: size,
}
imagesOutput = append(imagesOutput, params)
}
return
}
// generateImagesOutput generates the images based on the format provided
func generateImagesOutput(runtime *libpod.Runtime, images []*storage.Image, opts imagesOptions) error {
if len(images) == 0 {
return nil
}
var out formats.Writer
switch opts.format {
case formats.JSONString:
imagesOutput := getImagesJSONOutput(runtime, images)
out = formats.JSONStructArray{Output: imagesToGeneric([]imagesTemplateParams{}, imagesOutput)}
default:
imagesOutput := getImagesTemplateOutput(runtime, images, opts)
out = formats.StdoutTemplateArray{Output: imagesToGeneric(imagesOutput, []imagesJSONParams{}), Template: opts.format, Fields: imagesOutput[0].headerMap()}
}
return formats.Writer(out).Out()
}
// generateImagesFilter returns an ImageFilter based on filterType
// to add more filters, define a new case and write what the ImageFilter function should do
func generateImagesFilter(params *libpod.ImageFilterParams, filterType string) libpod.ImageFilter {
switch filterType {
case "label":
return func(image *storage.Image, info *types.ImageInspectInfo) bool {
if params == nil || params.Label == "" {
return true
}
pair := strings.SplitN(params.Label, "=", 2)
if val, ok := info.Labels[pair[0]]; ok {
if len(pair) == 2 && val == pair[1] {
return true
}
if len(pair) == 1 {
return true
}
}
return false
}
case "before-image":
return func(image *storage.Image, info *types.ImageInspectInfo) bool {
if params == nil || params.BeforeImage.IsZero() {
return true
}
return info.Created.Before(params.BeforeImage)
}
case "since-image":
return func(image *storage.Image, info *types.ImageInspectInfo) bool {
if params == nil || params.SinceImage.IsZero() {
return true
}
return info.Created.After(params.SinceImage)
}
case "dangling":
return func(image *storage.Image, info *types.ImageInspectInfo) bool {
if params == nil || params.Dangling == "" {
return true
}
if common.IsFalse(params.Dangling) && params.ImageName != "<none>" {
return true
}
if common.IsTrue(params.Dangling) && params.ImageName == "<none>" {
return true
}
return false
}
case "reference":
return func(image *storage.Image, info *types.ImageInspectInfo) bool {
if params == nil || params.ReferencePattern == "" {
return true
}
return libpod.MatchesReference(params.ImageName, params.ReferencePattern)
}
case "image-input":
return func(image *storage.Image, info *types.ImageInspectInfo) bool {
if params == nil || params.ImageInput == "" {
return true
}
return libpod.MatchesReference(params.ImageName, params.ImageInput)
}
default:
fmt.Println("invalid filter type", filterType)
return nil
}
}

View file

@ -5,7 +5,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/kubernetes-incubator/cri-o/libpod/images" "github.com/kubernetes-incubator/cri-o/libpod"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -91,9 +91,9 @@ func loadCmd(c *cli.Context) error {
output = os.Stdout output = os.Stdout
} }
src := images.DockerArchive + ":" + input src := libpod.DockerArchive + ":" + input
if err := runtime.PullImage(src, false, "", output); err != nil { if err := runtime.PullImage(src, false, "", output); err != nil {
src = images.OCIArchive + ":" + input src = libpod.OCIArchive + ":" + input
// generate full src name with specified image:tag // generate full src name with specified image:tag
if image != "" { if image != "" {
src = src + ":" + image src = src + ":" + image

View file

@ -3,8 +3,6 @@ package main
import ( import (
"fmt" "fmt"
"github.com/containers/storage"
"github.com/kubernetes-incubator/cri-o/libpod/images"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -32,95 +30,27 @@ func rmiCmd(c *cli.Context) error {
return err return err
} }
force := false runtime, err := getRuntime(c)
if c.IsSet("force") { if err != nil {
force = c.Bool("force") return errors.Wrapf(err, "could not get runtime")
} }
defer runtime.Shutdown(false)
args := c.Args() args := c.Args()
if len(args) == 0 { if len(args) == 0 {
return errors.Errorf("image name or ID must be specified") return errors.Errorf("image name or ID must be specified")
} }
config, err := getConfig(c) for _, arg := range args {
image, err := runtime.GetImage(arg)
if err != nil { if err != nil {
return errors.Wrapf(err, "Could not get config") return errors.Wrapf(err, "could not get image %q", arg)
} }
store, err := getStore(config) id, err := runtime.RemoveImage(image, c.Bool("force"))
if err != nil { if err != nil {
return err return errors.Wrapf(err, "error removing image %q", id)
}
for _, id := range args {
image, err := images.FindImage(store, id)
if err != nil {
return errors.Wrapf(err, "could not get image %q", id)
}
if image != nil {
ctrIDs, err := runningContainers(image, store)
if err != nil {
return errors.Wrapf(err, "error getting running containers for image %q", id)
}
if len(ctrIDs) > 0 && len(image.Names) <= 1 {
if force {
removeContainers(ctrIDs, store)
} else {
for ctrID := range ctrIDs {
return fmt.Errorf("Could not remove image %q (must force) - container %q is using its reference image", id, ctrID)
}
}
}
// If the user supplied an ID, we cannot delete the image if it is referred to by multiple tags
if images.MatchesID(image.ID, id) {
if len(image.Names) > 1 && !force {
return fmt.Errorf("unable to delete %s (must force) - image is referred to in multiple tags", image.ID)
}
// If it is forced, we have to untag the image so that it can be deleted
image.Names = image.Names[:0]
} else {
name, err2 := images.UntagImage(store, image, id)
if err2 != nil {
return err
}
fmt.Printf("untagged: %s\n", name)
}
if len(image.Names) > 1 {
continue
}
id, err := images.RemoveImage(image, store)
if err != nil {
return err
} }
fmt.Printf("%s\n", id) fmt.Printf("%s\n", id)
} }
}
return nil
}
// Returns a list of running containers associated with the given ImageReference
// TODO: replace this with something in libkpod
func runningContainers(image *storage.Image, store storage.Store) ([]string, error) {
ctrIDs := []string{}
containers, err := store.Containers()
if err != nil {
return nil, err
}
for _, ctr := range containers {
if ctr.ImageID == image.ID {
ctrIDs = append(ctrIDs, ctr.ID)
}
}
return ctrIDs, nil
}
// TODO: replace this with something in libkpod
func removeContainers(ctrIDs []string, store storage.Store) error {
for _, ctrID := range ctrIDs {
if err := store.DeleteContainer(ctrID); err != nil {
return errors.Wrapf(err, "could not remove container %q", ctrID)
}
}
return nil return nil
} }

View file

@ -5,7 +5,6 @@ import (
"os" "os"
"github.com/kubernetes-incubator/cri-o/libpod" "github.com/kubernetes-incubator/cri-o/libpod"
"github.com/kubernetes-incubator/cri-o/libpod/images"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -72,12 +71,12 @@ func saveCmd(c *cli.Context) error {
var dst string var dst string
switch c.String("format") { switch c.String("format") {
case images.OCIArchive: case libpod.OCIArchive:
dst = images.OCIArchive + ":" + output dst = libpod.OCIArchive + ":" + output
case images.DockerArchive: case libpod.DockerArchive:
fallthrough fallthrough
case "": case "":
dst = images.DockerArchive + ":" + output dst = libpod.DockerArchive + ":" + output
default: default:
return errors.Errorf("unknown format option %q", c.String("format")) return errors.Errorf("unknown format option %q", c.String("format"))
} }

View file

@ -8,9 +8,10 @@ import (
"text/template" "text/template"
"time" "time"
"github.com/docker/go-units"
tm "github.com/buger/goterm" tm "github.com/buger/goterm"
"github.com/kubernetes-incubator/cri-o/libkpod" "github.com/kubernetes-incubator/cri-o/libkpod"
"github.com/kubernetes-incubator/cri-o/libpod/images"
"github.com/kubernetes-incubator/cri-o/oci" "github.com/kubernetes-incubator/cri-o/oci"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -182,7 +183,7 @@ func outputStatsUsingFormatString(stats *libkpod.ContainerStats) {
} }
func combineHumanValues(a, b uint64) string { func combineHumanValues(a, b uint64) string {
return fmt.Sprintf("%s / %s", images.FormattedSize(float64(a)), images.FormattedSize(float64(b))) return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b)))
} }
func floatToPercentString(f float64) string { func floatToPercentString(f float64) string {

View file

@ -1,4 +1,4 @@
package images package libpod
import ( import (
"encoding/json" "encoding/json"
@ -11,12 +11,14 @@ import (
"github.com/containers/image/docker/reference" "github.com/containers/image/docker/reference"
is "github.com/containers/image/storage" is "github.com/containers/image/storage"
"github.com/containers/image/transports"
"github.com/containers/image/types" "github.com/containers/image/types"
"github.com/containers/storage" "github.com/containers/storage"
"github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/archive"
"github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/ioutils"
"github.com/kubernetes-incubator/cri-o/cmd/kpod/docker" "github.com/kubernetes-incubator/cri-o/cmd/kpod/docker"
"github.com/kubernetes-incubator/cri-o/libpod/common" "github.com/kubernetes-incubator/cri-o/libpod/common"
"github.com/kubernetes-incubator/cri-o/libpod/driver"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/image-spec/specs-go/v1"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1" ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
@ -34,6 +36,28 @@ const (
OCIv1ImageManifest = v1.MediaTypeImageManifest OCIv1ImageManifest = v1.MediaTypeImageManifest
) )
// Data handles the data used when inspecting a container
// nolint
type Data struct {
ID string
Tags []string
Digests []string
ManifestDigest digest.Digest
Comment string
Created *time.Time
Container string
Author string
Config ociv1.ImageConfig
Architecture string
OS string
Annotations map[string]string
CreatedBy string
Size uint
VirtualSize uint
GraphDriver driver.Data
RootFS ociv1.RootFS
}
// CopyData stores the basic data used when copying a container or image // CopyData stores the basic data used when copying a container or image
type CopyData struct { type CopyData struct {
store storage.Store store storage.Store
@ -360,13 +384,13 @@ func (c *CopyData) Save() error {
} }
// GetContainerCopyData gets the copy data for a container // GetContainerCopyData gets the copy data for a container
func GetContainerCopyData(store storage.Store, name string) (*CopyData, error) { func (r *Runtime) GetContainerCopyData(name string) (*CopyData, error) {
var data *CopyData var data *CopyData
var err error var err error
if name != "" { if name != "" {
data, err = openCopyData(store, name) data, err = openCopyData(r.store, name)
if os.IsNotExist(errors.Cause(err)) { if os.IsNotExist(errors.Cause(err)) {
data, err = importCopyData(store, name, "") data, err = r.importCopyData(r.store, name, "")
} }
} }
if err != nil { if err != nil {
@ -380,17 +404,17 @@ func GetContainerCopyData(store storage.Store, name string) (*CopyData, error) {
} }
// GetImageCopyData gets the copy data for an image // GetImageCopyData gets the copy data for an image
func GetImageCopyData(store storage.Store, image string) (*CopyData, error) { func (r *Runtime) GetImageCopyData(image string) (*CopyData, error) {
if image == "" { if image == "" {
return nil, errors.Errorf("image name must be specified") return nil, errors.Errorf("image name must be specified")
} }
img, err := FindImage(store, image) img, err := r.GetImage(image)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "error locating image %q for importing settings", image) return nil, errors.Wrapf(err, "error locating image %q for importing settings", image)
} }
systemContext := common.GetSystemContext("") systemContext := common.GetSystemContext("")
data, err := ImportCopyDataFromImage(store, systemContext, img.ID, "", "") data, err := r.ImportCopyDataFromImage(systemContext, img.ID, "", "")
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "error reading image") return nil, errors.Wrapf(err, "error reading image")
} }
@ -401,7 +425,7 @@ func GetImageCopyData(store storage.Store, image string) (*CopyData, error) {
} }
func importCopyData(store storage.Store, container, signaturePolicyPath string) (*CopyData, error) { func (r *Runtime) importCopyData(store storage.Store, container, signaturePolicyPath string) (*CopyData, error) {
if container == "" { if container == "" {
return nil, errors.Errorf("container name must be specified") return nil, errors.Errorf("container name must be specified")
} }
@ -413,7 +437,7 @@ func importCopyData(store storage.Store, container, signaturePolicyPath string)
systemContext := common.GetSystemContext(signaturePolicyPath) systemContext := common.GetSystemContext(signaturePolicyPath)
data, err := ImportCopyDataFromImage(store, systemContext, c.ImageID, container, c.ID) data, err := r.ImportCopyDataFromImage(systemContext, c.ImageID, container, c.ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -461,13 +485,13 @@ func openCopyData(store storage.Store, container string) (*CopyData, error) {
} }
// ImportCopyDataFromImage creates copy data for an image with the given parameters // ImportCopyDataFromImage creates copy data for an image with the given parameters
func ImportCopyDataFromImage(store storage.Store, systemContext *types.SystemContext, imageID, containerName, containerID string) (*CopyData, error) { func (r *Runtime) ImportCopyDataFromImage(systemContext *types.SystemContext, imageID, containerName, containerID string) (*CopyData, error) {
manifest := []byte{} manifest := []byte{}
config := []byte{} config := []byte{}
imageName := "" imageName := ""
if imageID != "" { if imageID != "" {
ref, err := is.Transport.ParseStoreReference(store, "@"+imageID) ref, err := is.Transport.ParseStoreReference(r.store, "@"+imageID)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "no such image %q", "@"+imageID) return nil, errors.Wrapf(err, "no such image %q", "@"+imageID)
} }
@ -484,7 +508,7 @@ func ImportCopyDataFromImage(store storage.Store, systemContext *types.SystemCon
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "error reading image manifest") return nil, errors.Wrapf(err, "error reading image manifest")
} }
if img, err3 := store.Image(imageID); err3 == nil { if img, err3 := r.store.Image(imageID); err3 == nil {
if len(img.Names) > 0 { if len(img.Names) > 0 {
imageName = img.Names[0] imageName = img.Names[0]
} }
@ -492,7 +516,7 @@ func ImportCopyDataFromImage(store storage.Store, systemContext *types.SystemCon
} }
data := &CopyData{ data := &CopyData{
store: store, store: r.store,
Type: containerType, Type: containerType,
FromImage: imageName, FromImage: imageName,
FromImageID: imageID, FromImageID: imageID,
@ -550,3 +574,91 @@ func (c *CopyData) MakeImageRef(manifestType string, compress archive.Compressio
} }
return ref, nil return ref, nil
} }
// GetData gets the Data for a container with the given name in the given store.
func (r *Runtime) GetData(name string) (*Data, error) {
img, err := r.GetImage(name)
if err != nil {
return nil, errors.Wrapf(err, "error reading image %q", name)
}
imgRef, err := r.GetImageRef("@" + img.ID)
if err != nil {
return nil, errors.Wrapf(err, "error reading image reference %q", img.ID)
}
defer imgRef.Close()
tags, digests, err := ParseImageNames(img.Names)
if err != nil {
return nil, errors.Wrapf(err, "error parsing image names for %q", name)
}
driverName, err := driver.GetDriverName(r.store)
if err != nil {
return nil, errors.Wrapf(err, "error reading name of storage driver")
}
topLayerID := img.TopLayer
driverMetadata, err := driver.GetDriverMetadata(r.store, topLayerID)
if err != nil {
return nil, errors.Wrapf(err, "error asking storage driver %q for metadata", driverName)
}
layer, err := r.store.Layer(topLayerID)
if err != nil {
return nil, errors.Wrapf(err, "error reading information about layer %q", topLayerID)
}
size, err := r.store.DiffSize(layer.Parent, layer.ID)
if err != nil {
return nil, errors.Wrapf(err, "error determining size of layer %q", layer.ID)
}
imgSize, err := imgRef.Size()
if err != nil {
return nil, errors.Wrapf(err, "error determining size of image %q", transports.ImageName(imgRef.Reference()))
}
manifest, manifestType, err := imgRef.Manifest()
if err != nil {
return nil, errors.Wrapf(err, "error reading manifest for image %q", img.ID)
}
manifestDigest := digest.Digest("")
if len(manifest) > 0 {
manifestDigest = digest.Canonical.FromBytes(manifest)
}
annotations := annotations(manifest, manifestType)
config, err := imgRef.OCIConfig()
if err != nil {
return nil, errors.Wrapf(err, "error reading image configuration for %q", img.ID)
}
historyComment := ""
historyCreatedBy := ""
if len(config.History) > 0 {
historyComment = config.History[len(config.History)-1].Comment
historyCreatedBy = config.History[len(config.History)-1].CreatedBy
}
return &Data{
ID: img.ID,
Tags: tags,
Digests: digests,
ManifestDigest: manifestDigest,
Comment: historyComment,
Created: config.Created,
Author: config.Author,
Config: config.Config,
Architecture: config.Architecture,
OS: config.OS,
Annotations: annotations,
CreatedBy: historyCreatedBy,
Size: uint(size),
VirtualSize: uint(size + imgSize),
GraphDriver: driver.Data{
Name: driverName,
Data: driverMetadata,
},
RootFS: config.RootFS,
}, nil
}

View file

@ -1,4 +1,4 @@
package images package libpod
import ( import (
"bytes" "bytes"

View file

@ -1,38 +1,37 @@
package libkpod package libpod
import ( import (
"github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/archive"
"github.com/kubernetes-incubator/cri-o/libpod/images"
"github.com/kubernetes-incubator/cri-o/libpod/layers" "github.com/kubernetes-incubator/cri-o/libpod/layers"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// GetDiff returns the differences between the two images, layers, or containers // GetDiff returns the differences between the two images, layers, or containers
func (c *ContainerServer) GetDiff(from, to string) ([]archive.Change, error) { func (r *Runtime) GetDiff(from, to string) ([]archive.Change, error) {
toLayer, err := c.getLayerID(to) toLayer, err := r.getLayerID(to)
if err != nil { if err != nil {
return nil, err return nil, err
} }
fromLayer := "" fromLayer := ""
if from != "" { if from != "" {
fromLayer, err = c.getLayerID(from) fromLayer, err = r.getLayerID(from)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
return c.Store().Changes(fromLayer, toLayer) return r.store.Changes(fromLayer, toLayer)
} }
// GetLayerID gets a full layer id given a full or partial id // GetLayerID gets a full layer id given a full or partial id
// If the id matches a container or image, the id of the top layer is returned // If the id matches a container or image, the id of the top layer is returned
// If the id matches a layer, the top layer id is returned // If the id matches a layer, the top layer id is returned
func (c *ContainerServer) getLayerID(id string) (string, error) { func (r *Runtime) getLayerID(id string) (string, error) {
var toLayer string var toLayer string
toImage, err := images.FindImage(c.store, id) toImage, err := r.GetImage(id)
if err != nil { if err != nil {
toCtr, err := c.store.Container(id) toCtr, err := r.store.Container(id)
if err != nil { if err != nil {
toLayer, err = layers.FullID(c.store, id) toLayer, err = layers.FullID(r.store, id)
if err != nil { if err != nil {
return "", errors.Errorf("layer, image, or container %s does not exist", id) return "", errors.Errorf("layer, image, or container %s does not exist", id)
} }
@ -45,8 +44,8 @@ func (c *ContainerServer) getLayerID(id string) (string, error) {
return toLayer, nil return toLayer, nil
} }
func (c *ContainerServer) getLayerParent(layerID string) (string, error) { func (r *Runtime) getLayerParent(layerID string) (string, error) {
layer, err := c.store.Layer(layerID) layer, err := r.store.Layer(layerID)
if err != nil { if err != nil {
return "", err return "", err
} }

View file

@ -1,208 +0,0 @@
package images
import (
"io"
"os"
"strings"
"syscall"
cp "github.com/containers/image/copy"
dockerarchive "github.com/containers/image/docker/archive"
"github.com/containers/image/docker/tarfile"
"github.com/containers/image/manifest"
ociarchive "github.com/containers/image/oci/archive"
"github.com/containers/image/signature"
is "github.com/containers/image/storage"
"github.com/containers/image/transports/alltransports"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/kubernetes-incubator/cri-o/libpod/common"
"github.com/pkg/errors"
)
const (
// DefaultRegistry is a prefix that we apply to an image name
// to check docker hub first for the image
DefaultRegistry = "docker://"
)
var (
// DockerArchive is the transport we prepend to an image name
// when saving to docker-archive
DockerArchive = dockerarchive.Transport.Name()
// OCIArchive is the transport we prepend to an image name
// when saving to oci-archive
OCIArchive = ociarchive.Transport.Name()
)
// CopyOptions contains the options given when pushing or pulling images
type CopyOptions struct {
// Compression specifies the type of compression which is applied to
// layer blobs. The default is to not use compression, but
// archive.Gzip is recommended.
Compression archive.Compression
// SignaturePolicyPath specifies an override location for the signature
// policy which should be used for verifying the new image as it is
// being written. Except in specific circumstances, no value should be
// specified, indicating that the shared, system-wide default policy
// should be used.
SignaturePolicyPath string
// ReportWriter is an io.Writer which will be used to log the writing
// of the new image.
ReportWriter io.Writer
// Store is the local storage store which holds the source image.
Store storage.Store
// DockerRegistryOptions encapsulates settings that affect how we
// connect or authenticate to a remote registry to which we want to
// push the image.
common.DockerRegistryOptions
// SigningOptions encapsulates settings that control whether or not we
// strip or add signatures to the image when pushing (uploading) the
// image to a registry.
common.SigningOptions
// Quiet suppresses the output when a push or pull happens
Quiet bool
}
// PushImage pushes the src image to the destination
func PushImage(srcName, destName string, options CopyOptions) error {
if srcName == "" || destName == "" {
return errors.Wrapf(syscall.EINVAL, "source and destination image names must be specified")
}
// Get the destination Image Reference
dest, err := alltransports.ParseImageName(destName)
if err != nil {
return errors.Wrapf(err, "error getting destination imageReference for %q", destName)
}
policyContext, err := common.GetPolicyContext(options.SignaturePolicyPath)
if err != nil {
return errors.Wrapf(err, "Could not get default policy context for signature policy path %q", options.SignaturePolicyPath)
}
defer policyContext.Destroy()
// Look up the image name and its layer, then build the imagePushData from
// the image
img, err := FindImage(options.Store, srcName)
if err != nil {
return errors.Wrapf(err, "error locating image %q for importing settings", srcName)
}
systemContext := common.GetSystemContext(options.SignaturePolicyPath)
cd, err := ImportCopyDataFromImage(options.Store, systemContext, img.ID, "", "")
if err != nil {
return err
}
// Give the image we're producing the same ancestors as its source image
cd.FromImage = cd.Docker.ContainerConfig.Image
cd.FromImageID = string(cd.Docker.Parent)
// Prep the layers and manifest for export
src, err := cd.MakeImageRef(manifest.GuessMIMEType(cd.Manifest), options.Compression, img.Names, img.TopLayer, nil)
if err != nil {
return errors.Wrapf(err, "error copying layers and metadata")
}
copyOptions := common.GetCopyOptions(options.ReportWriter, options.SignaturePolicyPath, nil, &options.DockerRegistryOptions, options.SigningOptions)
// Copy the image to the remote destination
err = cp.Image(policyContext, dest, src, copyOptions)
if err != nil {
return errors.Wrapf(err, "Error copying image to the remote destination")
}
return nil
}
// PullImage copies the image from the source to the destination
func PullImage(imgName string, allTags bool, options CopyOptions) error {
var (
images []string
output io.Writer
)
store := options.Store
sc := common.GetSystemContext(options.SignaturePolicyPath)
if options.Quiet {
output = nil
} else {
output = os.Stdout
}
srcRef, err := alltransports.ParseImageName(imgName)
if err != nil {
defaultName := DefaultRegistry + imgName
srcRef2, err2 := alltransports.ParseImageName(defaultName)
if err2 != nil {
return errors.Errorf("error parsing image name %q: %v", defaultName, err2)
}
srcRef = srcRef2
}
splitArr := strings.Split(imgName, ":")
archFile := splitArr[len(splitArr)-1]
// supports pulling from docker-archive, oci, and registries
if srcRef.Transport().Name() == DockerArchive {
tarSource := tarfile.NewSource(archFile)
manifest, err := tarSource.LoadTarManifest()
if err != nil {
return errors.Errorf("error retrieving manifest.json: %v", err)
}
// to pull all the images stored in one tar file
for i := range manifest {
if manifest[i].RepoTags != nil {
images = append(images, manifest[i].RepoTags[0])
} else {
// create an image object and use the hex value of the digest as the image ID
// for parsing the store reference
newImg, err := srcRef.NewImage(sc)
if err != nil {
return err
}
defer newImg.Close()
digest := newImg.ConfigInfo().Digest
if err := digest.Validate(); err == nil {
images = append(images, "@"+digest.Hex())
} else {
return errors.Wrapf(err, "error getting config info")
}
}
}
} else if srcRef.Transport().Name() == OCIArchive {
// retrieve the manifest from index.json to access the image name
manifest, err := ociarchive.LoadManifestDescriptor(srcRef)
if err != nil {
return errors.Wrapf(err, "error loading manifest for %q", srcRef)
}
if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" {
return errors.Errorf("error, archive doesn't have a name annotation. Cannot store image with no name")
}
images = append(images, manifest.Annotations["org.opencontainers.image.ref.name"])
} else {
images = append(images, imgName)
}
policy, err := signature.DefaultPolicy(sc)
if err != nil {
return err
}
policyContext, err := signature.NewPolicyContext(policy)
if err != nil {
return err
}
defer policyContext.Destroy()
copyOptions := common.GetCopyOptions(output, "", nil, nil, common.SigningOptions{})
for _, image := range images {
destRef, err := is.Transport.ParseStoreReference(store, image)
if err != nil {
return errors.Errorf("error parsing dest reference name: %v", err)
}
if err = cp.Image(policyContext, destRef, srcRef, copyOptions); err != nil {
return errors.Errorf("error loading image %q: %v", image, err)
}
}
return nil
}

View file

@ -1,288 +0,0 @@
package images
import (
"fmt"
"strings"
"time"
is "github.com/containers/image/storage"
"github.com/containers/image/transports"
"github.com/containers/image/types"
"github.com/containers/storage"
"github.com/kubernetes-incubator/cri-o/libpod/common"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
// FilterParams contains the filter options that may be given when outputting images
type FilterParams struct {
dangling string
label string
beforeImage time.Time
sinceImage time.Time
referencePattern string
}
// ParseFilter takes a set of images and a filter string as input, and returns the
func ParseFilter(store storage.Store, filter string) (*FilterParams, error) {
images, err := store.Images()
if err != nil {
return nil, err
}
params := new(FilterParams)
filterStrings := strings.Split(filter, ",")
for _, param := range filterStrings {
pair := strings.SplitN(param, "=", 2)
switch strings.TrimSpace(pair[0]) {
case "dangling":
if common.IsValidBool(pair[1]) {
params.dangling = pair[1]
} else {
return nil, fmt.Errorf("invalid filter: '%s=[%s]'", pair[0], pair[1])
}
case "label":
params.label = pair[1]
case "before":
if img, err := findImageInSlice(images, pair[1]); err == nil {
info, err := getImageInspectInfo(store, img)
if err != nil {
return nil, err
}
params.beforeImage = info.Created
} else {
return nil, fmt.Errorf("no such id: %s", pair[0])
}
case "since":
if img, err := findImageInSlice(images, pair[1]); err == nil {
info, err := getImageInspectInfo(store, img)
if err != nil {
return nil, err
}
params.sinceImage = info.Created
} else {
return nil, fmt.Errorf("no such id: %s``", pair[0])
}
case "reference":
params.referencePattern = pair[1]
default:
return nil, fmt.Errorf("invalid filter: '%s'", pair[0])
}
}
return params, nil
}
func matchesFilter(store storage.Store, image storage.Image, name string, params *FilterParams) bool {
if params == nil {
return true
}
info, err := getImageInspectInfo(store, image)
if err != nil {
return false
}
if params.dangling != "" && !matchesDangling(name, params.dangling) {
return false
} else if params.label != "" && !matchesLabel(info, store, params.label) {
return false
} else if !params.beforeImage.IsZero() && !matchesBeforeImage(info, name, params) {
return false
} else if !params.sinceImage.IsZero() && !matchesSinceImage(info, name, params) {
return false
} else if params.referencePattern != "" && !MatchesReference(name, params.referencePattern) {
return false
}
return true
}
func matchesDangling(name string, dangling string) bool {
if common.IsFalse(dangling) && name != "<none>" {
return true
} else if common.IsTrue(dangling) && name == "<none>" {
return true
}
return false
}
func matchesLabel(info *types.ImageInspectInfo, store storage.Store, label string) bool {
pair := strings.SplitN(label, "=", 2)
for key, value := range info.Labels {
if key == pair[0] {
if len(pair) == 2 {
if value == pair[1] {
return true
}
} else {
return false
}
}
}
return false
}
// Returns true if the image was created since the filter image. Returns
// false otherwise
func matchesBeforeImage(info *types.ImageInspectInfo, name string, params *FilterParams) bool {
return info.Created.Before(params.beforeImage)
}
// Returns true if the image was created since the filter image. Returns
// false otherwise
func matchesSinceImage(info *types.ImageInspectInfo, name string, params *FilterParams) bool {
return info.Created.After(params.sinceImage)
}
// MatchesID returns true if argID is a full or partial match for id
func MatchesID(id, argID string) bool {
return strings.HasPrefix(argID, id)
}
// MatchesReference returns true if argName is a full or partial match for name
// Partial matches will register only if they match the most specific part of the name available
// For example, take the image docker.io/library/redis:latest
// redis, library,redis, docker.io/library/redis, redis:latest, etc. will match
// But redis:alpine, ry/redis, library, and io/library/redis will not
func MatchesReference(name, argName string) bool {
if argName == "" {
return false
}
splitName := strings.Split(name, ":")
// If the arg contains a tag, we handle it differently than if it does not
if strings.Contains(argName, ":") {
splitArg := strings.Split(argName, ":")
return strings.HasSuffix(splitName[0], splitArg[0]) && (splitName[1] == splitArg[1])
}
return strings.HasSuffix(splitName[0], argName)
}
// FormattedSize returns a human-readable formatted size for the image
func FormattedSize(size float64) string {
suffixes := [5]string{"B", "KB", "MB", "GB", "TB"}
count := 0
for size >= 1024 && count < 4 {
size /= 1024
count++
}
return fmt.Sprintf("%.4g %s", size, suffixes[count])
}
// FindImage searches for a *storage.Image with a matching the given name or ID in the given store.
func FindImage(store storage.Store, image string) (*storage.Image, error) {
var img *storage.Image
ref, err := is.Transport.ParseStoreReference(store, image)
if err == nil {
img, err = is.Transport.GetStoreImage(store, ref)
}
if err != nil {
img2, err2 := store.Image(image)
if err2 != nil {
if ref == nil {
return nil, errors.Wrapf(err, "error parsing reference to image %q", image)
}
return nil, errors.Wrapf(err, "unable to locate image %q", image)
}
img = img2
}
return img, nil
}
// FindImageRef searches for and returns a new types.Image matching the given name or ID in the given store.
func FindImageRef(store storage.Store, image string) (types.Image, error) {
img, err := FindImage(store, image)
if err != nil {
return nil, errors.Wrapf(err, "unable to locate image %q", image)
}
ref, err := is.Transport.ParseStoreReference(store, "@"+img.ID)
if err != nil {
return nil, errors.Wrapf(err, "error parsing reference to image %q", img.ID)
}
imgRef, err := ref.NewImage(nil)
if err != nil {
return nil, errors.Wrapf(err, "error reading image %q", img.ID)
}
return imgRef, nil
}
func findImageInSlice(images []storage.Image, ref string) (storage.Image, error) {
for _, image := range images {
if MatchesID(image.ID, ref) {
return image, nil
}
for _, name := range image.Names {
if MatchesReference(name, ref) {
return image, nil
}
}
}
return storage.Image{}, errors.New("could not find image")
}
// InfoAndDigestAndSize returns the inspection info and size of the image in the given
// store and the digest of its manifest, if it has one, or "" if it doesn't.
func InfoAndDigestAndSize(store storage.Store, img storage.Image) (*types.ImageInspectInfo, digest.Digest, int64, error) {
imgRef, err := FindImageRef(store, "@"+img.ID)
if err != nil {
return nil, "", -1, errors.Wrapf(err, "error reading image %q", img.ID)
}
defer imgRef.Close()
return infoAndDigestAndSize(imgRef)
}
func infoAndDigestAndSize(imgRef types.Image) (*types.ImageInspectInfo, digest.Digest, int64, error) {
imgSize, err := imgRef.Size()
if err != nil {
return nil, "", -1, errors.Wrapf(err, "error reading size of image %q", transports.ImageName(imgRef.Reference()))
}
manifest, _, err := imgRef.Manifest()
if err != nil {
return nil, "", -1, errors.Wrapf(err, "error reading manifest for image %q", transports.ImageName(imgRef.Reference()))
}
manifestDigest := digest.Digest("")
if len(manifest) > 0 {
manifestDigest = digest.Canonical.FromBytes(manifest)
}
info, err := imgRef.Inspect()
if err != nil {
return nil, "", -1, errors.Wrapf(err, "error inspecting image %q", transports.ImageName(imgRef.Reference()))
}
return info, manifestDigest, imgSize, nil
}
// GetImagesMatchingFilter returns a slice of all images in the store that match the provided FilterParams.
// Images with more than one name matching the filter will be in the slice once for each name
func GetImagesMatchingFilter(store storage.Store, filter *FilterParams, argName string) ([]storage.Image, error) {
images, err := store.Images()
filteredImages := []storage.Image{}
if err != nil {
return nil, err
}
for _, image := range images {
names := []string{}
if len(image.Names) > 0 {
names = image.Names
} else {
names = append(names, "<none>")
}
for _, name := range names {
if (filter == nil && argName == "") || (filter != nil && matchesFilter(store, image, name, filter)) || MatchesReference(name, argName) {
newImage := image
newImage.Names = []string{name}
filteredImages = append(filteredImages, newImage)
}
}
}
return filteredImages, nil
}
func getImageInspectInfo(store storage.Store, image storage.Image) (*types.ImageInspectInfo, error) {
storeRef, err := is.Transport.ParseStoreReference(store, "@"+image.ID)
if err != nil {
return nil, err
}
img, err := storeRef.NewImage(nil)
if err != nil {
return nil, err
}
defer img.Close()
return img.Inspect()
}

View file

@ -5,7 +5,9 @@ import (
"time" "time"
"github.com/containers/image/docker/reference" "github.com/containers/image/docker/reference"
is "github.com/containers/image/storage"
"github.com/containers/image/transports" "github.com/containers/image/transports"
"github.com/containers/image/types"
"github.com/containers/storage" "github.com/containers/storage"
"github.com/kubernetes-incubator/cri-o/libpod/driver" "github.com/kubernetes-incubator/cri-o/libpod/driver"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
@ -162,3 +164,40 @@ func GetData(store storage.Store, name string) (*Data, error) {
RootFS: config.RootFS, RootFS: config.RootFS,
}, nil }, nil
} }
// FindImage searches for a *storage.Image with a matching the given name or ID in the given store.
func FindImage(store storage.Store, image string) (*storage.Image, error) {
var img *storage.Image
ref, err := is.Transport.ParseStoreReference(store, image)
if err == nil {
img, err = is.Transport.GetStoreImage(store, ref)
}
if err != nil {
img2, err2 := store.Image(image)
if err2 != nil {
if ref == nil {
return nil, errors.Wrapf(err, "error parsing reference to image %q", image)
}
return nil, errors.Wrapf(err, "unable to locate image %q", image)
}
img = img2
}
return img, nil
}
// FindImageRef searches for and returns a new types.Image matching the given name or ID in the given store.
func FindImageRef(store storage.Store, image string) (types.Image, error) {
img, err := FindImage(store, image)
if err != nil {
return nil, errors.Wrapf(err, "unable to locate image %q", image)
}
ref, err := is.Transport.ParseStoreReference(store, "@"+img.ID)
if err != nil {
return nil, errors.Wrapf(err, "error parsing reference to image %q", img.ID)
}
imgRef, err := ref.NewImage(nil)
if err != nil {
return nil, errors.Wrapf(err, "error reading image %q", img.ID)
}
return imgRef, nil
}

View file

@ -1,35 +0,0 @@
package images
import (
"github.com/containers/storage"
"github.com/pkg/errors"
)
// UntagImage removes the tag from the given image
func UntagImage(store storage.Store, image *storage.Image, imgArg string) (string, error) {
// Remove name from image.Names and set the new names
newNames := []string{}
removedName := ""
for _, name := range image.Names {
if MatchesReference(name, imgArg) || MatchesID(imgArg, image.ID) {
removedName = name
continue
}
newNames = append(newNames, name)
}
if removedName != "" {
if err := store.SetNames(image.ID, newNames); err != nil {
return "", errors.Wrapf(err, "error removing name %q from image %q", removedName, image.ID)
}
}
return removedName, nil
}
// RemoveImage removes the given image from storage
func RemoveImage(image *storage.Image, store storage.Store) (string, error) {
_, err := store.DeleteImage(image.ID, true)
if err != nil {
return "", errors.Wrapf(err, "could not remove image %q", image.ID)
}
return image.ID, nil
}

View file

@ -1,6 +1,7 @@
package libpod package libpod
import ( import (
"github.com/containers/storage"
spec "github.com/opencontainers/runtime-spec/specs-go" spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -99,11 +100,11 @@ func (r *Runtime) LookupContainer(idOrName string) (*Container, error) {
return r.state.LookupContainer(idOrName) return r.state.LookupContainer(idOrName)
} }
// Containers retrieves all containers from the state // GetContainers retrieves all containers from the state
// Filters can be provided which will determine what containers are included in // Filters can be provided which will determine what containers are included in
// the output. Multiple filters are handled by ANDing their output, so only // the output. Multiple filters are handled by ANDing their output, so only
// containers matching all filters are returned // containers matching all filters are returned
func (r *Runtime) Containers(filters ...ContainerFilter) ([]*Container, error) { func (r *Runtime) GetContainers(filters ...ContainerFilter) ([]*Container, error) {
r.lock.RLock() r.lock.RLock()
defer r.lock.RUnlock() defer r.lock.RUnlock()
@ -131,3 +132,29 @@ func (r *Runtime) Containers(filters ...ContainerFilter) ([]*Container, error) {
return ctrsFiltered, nil return ctrsFiltered, nil
} }
// getContainersWithImage returns a list of containers referencing imageID
func (r *Runtime) getContainersWithImage(imageID string) ([]storage.Container, error) {
var matchingContainers []storage.Container
containers, err := r.store.Containers()
if err != nil {
return nil, err
}
for _, ctr := range containers {
if ctr.ImageID == imageID {
matchingContainers = append(matchingContainers, ctr)
}
}
return matchingContainers, nil
}
// removeMultipleContainers deletes a list of containers from the store
func (r *Runtime) removeMultipleContainers(containers []storage.Container) error {
for _, ctr := range containers {
if err := r.store.DeleteContainer(ctr.ID); err != nil {
return errors.Wrapf(err, "could not remove container %q", ctr)
}
}
return nil
}

View file

@ -1,24 +1,29 @@
package libpod package libpod
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"strings" "strings"
"syscall" "syscall"
"time"
cp "github.com/containers/image/copy" cp "github.com/containers/image/copy"
dockerarchive "github.com/containers/image/docker/archive" dockerarchive "github.com/containers/image/docker/archive"
"github.com/containers/image/docker/reference"
"github.com/containers/image/docker/tarfile" "github.com/containers/image/docker/tarfile"
"github.com/containers/image/manifest" "github.com/containers/image/manifest"
ociarchive "github.com/containers/image/oci/archive" ociarchive "github.com/containers/image/oci/archive"
"github.com/containers/image/signature" "github.com/containers/image/signature"
is "github.com/containers/image/storage" is "github.com/containers/image/storage"
"github.com/containers/image/transports"
"github.com/containers/image/transports/alltransports" "github.com/containers/image/transports/alltransports"
"github.com/containers/image/types" "github.com/containers/image/types"
"github.com/containers/storage" "github.com/containers/storage"
"github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/archive"
"github.com/kubernetes-incubator/cri-o/libpod/common" "github.com/kubernetes-incubator/cri-o/libpod/common"
"github.com/kubernetes-incubator/cri-o/libpod/images" digest "github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -60,10 +65,21 @@ type CopyOptions struct {
// Image API // Image API
// ImageFilterParams contains the filter options that may be given when outputting images
type ImageFilterParams struct {
Dangling string
Label string
BeforeImage time.Time
SinceImage time.Time
ReferencePattern string
ImageName string
ImageInput string
}
// ImageFilter is a function to determine whether an image is included in // ImageFilter is a function to determine whether an image is included in
// command output. Images to be outputted are tested using the function. A true // command output. Images to be outputted are tested using the function. A true
// return will include the image, a false return will exclude it. // return will include the image, a false return will exclude it.
type ImageFilter func(*storage.Image) bool type ImageFilter func(*storage.Image, *types.ImageInspectInfo) bool
// PullImage pulls an image from configured registries // PullImage pulls an image from configured registries
// By default, only the latest tag (or a specific tag if requested) will be // By default, only the latest tag (or a specific tag if requested) will be
@ -75,7 +91,7 @@ func (r *Runtime) PullImage(imgName string, allTags bool, signaturePolicyPath st
defer r.lock.Unlock() defer r.lock.Unlock()
if !r.valid { if !r.valid {
return fmt.Errorf("runtime is not valid") return ErrRuntimeStopped
} }
// PullImage copies the image from the source to the destination // PullImage copies the image from the source to the destination
@ -178,7 +194,7 @@ func (r *Runtime) PushImage(source string, destination string, options CopyOptio
defer r.lock.Unlock() defer r.lock.Unlock()
if !r.valid { if !r.valid {
return fmt.Errorf("runtime is not valid") return ErrRuntimeStopped
} }
// PushImage pushes the src image to the destination // PushImage pushes the src image to the destination
@ -209,7 +225,7 @@ func (r *Runtime) PushImage(source string, destination string, options CopyOptio
if err != nil { if err != nil {
return errors.Wrapf(err, "error locating image %q for importing settings", source) return errors.Wrapf(err, "error locating image %q for importing settings", source)
} }
cd, err := images.ImportCopyDataFromImage(r.store, r.imageContext, img.ID, "", "") cd, err := r.ImportCopyDataFromImage(r.imageContext, img.ID, "", "")
if err != nil { if err != nil {
return err return err
} }
@ -239,7 +255,7 @@ func (r *Runtime) TagImage(image *storage.Image, tag string) error {
defer r.lock.Unlock() defer r.lock.Unlock()
if !r.valid { if !r.valid {
return fmt.Errorf("runtime is not valid") return ErrRuntimeStopped
} }
tags, err := r.store.Names(image.ID) tags, err := r.store.Names(image.ID)
@ -256,17 +272,17 @@ func (r *Runtime) TagImage(image *storage.Image, tag string) error {
} }
// UntagImage removes a tag from the given image // UntagImage removes a tag from the given image
func (r *Runtime) UntagImage(image *storage.Image, tag string) error { func (r *Runtime) UntagImage(image *storage.Image, tag string) (string, error) {
r.lock.Lock() r.lock.Lock()
defer r.lock.Unlock() defer r.lock.Unlock()
if !r.valid { if !r.valid {
return fmt.Errorf("runtime is not valid") return "", ErrRuntimeStopped
} }
tags, err := r.store.Names(image.ID) tags, err := r.store.Names(image.ID)
if err != nil { if err != nil {
return err return "", err
} }
for i, key := range tags { for i, key := range tags {
if key == tag { if key == tag {
@ -275,21 +291,49 @@ func (r *Runtime) UntagImage(image *storage.Image, tag string) error {
break break
} }
} }
return r.store.SetNames(image.ID, tags) if err = r.store.SetNames(image.ID, tags); err != nil {
return "", err
}
return tag, nil
} }
// RemoveImage deletes an image from local storage // RemoveImage deletes an image from local storage
// Images being used by running containers cannot be removed // Images being used by running containers can only be removed if force=true
func (r *Runtime) RemoveImage(image *storage.Image) error { func (r *Runtime) RemoveImage(image *storage.Image, force bool) (string, error) {
r.lock.Lock() r.lock.Lock()
defer r.lock.Unlock() defer r.lock.Unlock()
if !r.valid { if !r.valid {
return fmt.Errorf("runtime is not valid") return "", ErrRuntimeStopped
} }
_, err := r.store.DeleteImage(image.ID, false) containersWithImage, err := r.getContainersWithImage(image.ID)
return err if err != nil {
return "", errors.Wrapf(err, "error getting containers for image %q", image.ID)
}
if len(containersWithImage) > 0 && len(image.Names) <= 1 {
if force {
if err := r.removeMultipleContainers(containersWithImage); err != nil {
return "", err
}
} else {
for _, ctr := range containersWithImage {
return "", fmt.Errorf("Could not remove image %q (must force) - container %q is using its reference image", image.ID, ctr.ImageID)
}
}
}
if len(image.Names) > 1 && !force {
return "", fmt.Errorf("unable to delete %s (must force) - image is referred to in multiple tags", image.ID)
}
// If it is forced, we have to untag the image so that it can be deleted
image.Names = image.Names[:0]
_, err = r.store.DeleteImage(image.ID, true)
if err != nil {
return "", err
}
return image.ID, nil
} }
// GetImage retrieves an image matching the given name or hash from system // GetImage retrieves an image matching the given name or hash from system
@ -300,7 +344,7 @@ func (r *Runtime) GetImage(image string) (*storage.Image, error) {
defer r.lock.Unlock() defer r.lock.Unlock()
if !r.valid { if !r.valid {
return nil, fmt.Errorf("runtime is not valid") return nil, ErrRuntimeStopped
} }
return r.getImage(image) return r.getImage(image)
} }
@ -330,9 +374,13 @@ func (r *Runtime) GetImageRef(image string) (types.Image, error) {
defer r.lock.Unlock() defer r.lock.Unlock()
if !r.valid { if !r.valid {
return nil, fmt.Errorf("runtime is not valid") return nil, ErrRuntimeStopped
}
return r.getImageRef(image)
} }
func (r *Runtime) getImageRef(image string) (types.Image, error) {
img, err := r.getImage(image) img, err := r.getImage(image)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "unable to locate image %q", image) return nil, errors.Wrapf(err, "unable to locate image %q", image)
@ -352,11 +400,280 @@ func (r *Runtime) GetImageRef(image string) (types.Image, error) {
// Filters can be provided which will determine which images are included in the // Filters can be provided which will determine which images are included in the
// output. Multiple filters are handled by ANDing their output, so only images // output. Multiple filters are handled by ANDing their output, so only images
// matching all filters are included // matching all filters are included
func (r *Runtime) GetImages(filter ...ImageFilter) ([]*storage.Image, error) { func (r *Runtime) GetImages(params *ImageFilterParams, filters ...ImageFilter) ([]*storage.Image, error) {
return nil, ErrNotImplemented r.lock.RLock()
defer r.lock.RUnlock()
if !r.valid {
return nil, ErrRuntimeStopped
}
images, err := r.store.Images()
if err != nil {
return nil, err
}
var imagesFiltered []*storage.Image
for _, img := range images {
info, err := r.getImageInspectInfo(img)
if err != nil {
return nil, err
}
var names []string
if len(img.Names) > 0 {
names = img.Names
} else {
names = append(names, "<none>")
}
for _, name := range names {
include := true
if params != nil {
params.ImageName = name
}
for _, filter := range filters {
include = include && filter(&img, info)
}
if include {
newImage := img
newImage.Names = []string{name}
imagesFiltered = append(imagesFiltered, &newImage)
}
}
}
return imagesFiltered, nil
}
// GetHistory gets the history of an image and information about its layers
func (r *Runtime) GetHistory(image string) ([]ociv1.History, []types.BlobInfo, string, error) {
r.lock.RLock()
defer r.lock.RUnlock()
if !r.valid {
return nil, nil, "", ErrRuntimeStopped
}
img, err := r.getImage(image)
if err != nil {
return nil, nil, "", errors.Wrapf(err, "no such image %q", image)
}
src, err := r.getImageRef(image)
if err != nil {
return nil, nil, "", errors.Wrapf(err, "error instantiating image %q", image)
}
oci, err := src.OCIConfig()
if err != nil {
return nil, nil, "", err
}
return oci.History, src.LayerInfos(), img.ID, nil
} }
// ImportImage imports an OCI format image archive into storage as an image // ImportImage imports an OCI format image archive into storage as an image
func (r *Runtime) ImportImage(path string) (*storage.Image, error) { func (r *Runtime) ImportImage(path string) (*storage.Image, error) {
return nil, ErrNotImplemented return nil, ErrNotImplemented
} }
// GetImageInspectInfo returns the inspect information of an image
func (r *Runtime) GetImageInspectInfo(image storage.Image) (*types.ImageInspectInfo, error) {
r.lock.RLock()
defer r.lock.RUnlock()
if !r.valid {
return nil, ErrRuntimeStopped
}
return r.getImageInspectInfo(image)
}
func (r *Runtime) getImageInspectInfo(image storage.Image) (*types.ImageInspectInfo, error) {
img, err := r.getImageRef(image.ID)
if err != nil {
return nil, err
}
return img.Inspect()
}
// ParseImageFilter takes a set of images and a filter string as input, and returns the libpod.ImageFilterParams struct
func (r *Runtime) ParseImageFilter(imageInput, filter string) (*ImageFilterParams, error) {
r.lock.RLock()
defer r.lock.RUnlock()
if !r.valid {
return nil, ErrRuntimeStopped
}
if filter == "" && imageInput == "" {
return nil, nil
}
var params ImageFilterParams
params.ImageInput = imageInput
if filter == "" && imageInput != "" {
return &params, nil
}
images, err := r.store.Images()
if err != nil {
return nil, err
}
filterStrings := strings.Split(filter, ",")
for _, param := range filterStrings {
pair := strings.SplitN(param, "=", 2)
switch strings.TrimSpace(pair[0]) {
case "dangling":
if common.IsValidBool(pair[1]) {
params.Dangling = pair[1]
} else {
return nil, fmt.Errorf("invalid filter: '%s=[%s]'", pair[0], pair[1])
}
case "label":
params.Label = pair[1]
case "before":
if img, err := findImageInSlice(images, pair[1]); err == nil {
info, err := r.GetImageInspectInfo(img)
if err != nil {
return nil, err
}
params.BeforeImage = info.Created
} else {
return nil, fmt.Errorf("no such id: %s", pair[0])
}
case "since":
if img, err := findImageInSlice(images, pair[1]); err == nil {
info, err := r.GetImageInspectInfo(img)
if err != nil {
return nil, err
}
params.SinceImage = info.Created
} else {
return nil, fmt.Errorf("no such id: %s``", pair[0])
}
case "reference":
params.ReferencePattern = pair[1]
default:
return nil, fmt.Errorf("invalid filter: '%s'", pair[0])
}
}
return &params, nil
}
// InfoAndDigestAndSize returns the inspection info and size of the image in the given
// store and the digest of its manifest, if it has one, or "" if it doesn't.
func (r *Runtime) InfoAndDigestAndSize(img storage.Image) (*types.ImageInspectInfo, digest.Digest, int64, error) {
r.lock.RLock()
defer r.lock.RUnlock()
if !r.valid {
return nil, "", -1, ErrRuntimeStopped
}
imgRef, err := r.getImageRef("@" + img.ID)
if err != nil {
return nil, "", -1, errors.Wrapf(err, "error reading image %q", img.ID)
}
defer imgRef.Close()
return infoAndDigestAndSize(imgRef)
}
func infoAndDigestAndSize(imgRef types.Image) (*types.ImageInspectInfo, digest.Digest, int64, error) {
imgSize, err := imgRef.Size()
if err != nil {
return nil, "", -1, errors.Wrapf(err, "error reading size of image %q", transports.ImageName(imgRef.Reference()))
}
manifest, _, err := imgRef.Manifest()
if err != nil {
return nil, "", -1, errors.Wrapf(err, "error reading manifest for image %q", transports.ImageName(imgRef.Reference()))
}
manifestDigest := digest.Digest("")
if len(manifest) > 0 {
manifestDigest = digest.Canonical.FromBytes(manifest)
}
info, err := imgRef.Inspect()
if err != nil {
return nil, "", -1, errors.Wrapf(err, "error inspecting image %q", transports.ImageName(imgRef.Reference()))
}
return info, manifestDigest, imgSize, nil
}
// MatchesID returns true if argID is a full or partial match for id
func MatchesID(id, argID string) bool {
return strings.HasPrefix(argID, id)
}
// MatchesReference returns true if argName is a full or partial match for name
// Partial matches will register only if they match the most specific part of the name available
// For example, take the image docker.io/library/redis:latest
// redis, library/redis, docker.io/library/redis, redis:latest, etc. will match
// But redis:alpine, ry/redis, library, and io/library/redis will not
func MatchesReference(name, argName string) bool {
if argName == "" {
return false
}
splitName := strings.Split(name, ":")
// If the arg contains a tag, we handle it differently than if it does not
if strings.Contains(argName, ":") {
splitArg := strings.Split(argName, ":")
return strings.HasSuffix(splitName[0], splitArg[0]) && (splitName[1] == splitArg[1])
}
return strings.HasSuffix(splitName[0], argName)
}
// ParseImageNames parses the names we've stored with an image into a list of
// tagged references and a list of references which contain digests.
func ParseImageNames(names []string) (tags, digests []string, err error) {
for _, name := range names {
if named, err := reference.ParseNamed(name); err == nil {
if digested, ok := named.(reference.Digested); ok {
canonical, err := reference.WithDigest(named, digested.Digest())
if err == nil {
digests = append(digests, canonical.String())
}
} else {
if reference.IsNameOnly(named) {
named = reference.TagNameOnly(named)
}
if tagged, ok := named.(reference.Tagged); ok {
namedTagged, err := reference.WithTag(named, tagged.Tag())
if err == nil {
tags = append(tags, namedTagged.String())
}
}
}
}
}
return tags, digests, nil
}
func annotations(manifest []byte, manifestType string) map[string]string {
annotations := make(map[string]string)
switch manifestType {
case ociv1.MediaTypeImageManifest:
var m ociv1.Manifest
if err := json.Unmarshal(manifest, &m); err == nil {
for k, v := range m.Annotations {
annotations[k] = v
}
}
}
return annotations
}
func findImageInSlice(images []storage.Image, ref string) (storage.Image, error) {
for _, image := range images {
if MatchesID(image.ID, ref) {
return image, nil
}
for _, name := range image.Names {
if MatchesReference(name, ref) {
return image, nil
}
}
}
return storage.Image{}, errors.New("could not find image")
}

View file

@ -35,7 +35,7 @@ function teardown() {
echo "$output" echo "$output"
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
inspectOutput="$output" inspectOutput="$output"
run ${KPOD_BINARY} $KPOD_OPTIONS images --quiet ${IMAGE} run ${KPOD_BINARY} $KPOD_OPTIONS images --no-trunc --quiet ${IMAGE}
echo "$output" echo "$output"
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[ "$output" = "$inspectOutput" ] [ "$output" = "$inspectOutput" ]

View file

@ -2,7 +2,7 @@
load helpers load helpers
IMAGE="docker.io/library/alpine:latest" IMAGE="alpine:latest"
function teardown() { function teardown() {
cleanup_test cleanup_test
@ -17,7 +17,7 @@ function teardown() {
run ${KPOD_BINARY} ${KPOD_OPTIONS} inspect foobar:latest run ${KPOD_BINARY} ${KPOD_OPTIONS} inspect foobar:latest
echo "$output" echo "$output"
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi foobar:latest run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi --force foobar:latest
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
} }
@ -26,10 +26,12 @@ function teardown() {
echo "$output" echo "$output"
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
run ${KPOD_BINARY} ${KPOD_OPTIONS} tag $IMAGE foobar run ${KPOD_BINARY} ${KPOD_OPTIONS} tag $IMAGE foobar
echo "$output"
[ "$status" -eq 0 ]
run ${KPOD_BINARY} ${KPOD_OPTIONS} inspect foobar:latest run ${KPOD_BINARY} ${KPOD_OPTIONS} inspect foobar:latest
echo "$output" echo "$output"
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi foobar:latest run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi --force foobar:latest
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
} }
@ -38,9 +40,11 @@ function teardown() {
echo "$output" echo "$output"
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
run ${KPOD_BINARY} ${KPOD_OPTIONS} tag $IMAGE foobar:v run ${KPOD_BINARY} ${KPOD_OPTIONS} tag $IMAGE foobar:v
echo "$output"
[ "$status" -eq 0 ]
run ${KPOD_BINARY} ${KPOD_OPTIONS} inspect foobar:v run ${KPOD_BINARY} ${KPOD_OPTIONS} inspect foobar:v
echo "$output" echo "$output"
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi foobar:v run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi --force foobar:v
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
} }