Continue switching from libkpod to libpod

Refactored rmi, images, diff, and history.
Made fixes to kpod images in the way it was handing the templates as well as printing the image names

Signed-off-by: umohnani8 <umohnani@redhat.com>
This commit is contained in:
umohnani8 2017-09-21 15:21:56 -04:00
parent cfd7aec1c3
commit 356df5d18e
18 changed files with 820 additions and 837 deletions

View file

@ -2,9 +2,9 @@ package main
import (
"fmt"
"github.com/containers/storage/pkg/archive"
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
"github.com/kubernetes-incubator/cri-o/libkpod"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
@ -74,25 +74,22 @@ func formatJSON(output []diffOutputParams) (diffJSONOutput, 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 {
return err
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "could not get config")
if len(c.Args()) != 1 {
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 {
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)
changes, err := server.GetDiff("", to)
changes, err := runtime.GetDiff("", to)
if err != nil {
return errors.Wrapf(err, "could not get changes for %q", to)
}

View file

@ -6,12 +6,9 @@ import (
"strings"
"time"
is "github.com/containers/image/storage"
"github.com/containers/image/types"
"github.com/containers/storage"
units "github.com/docker/go-units"
"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/pkg/errors"
"github.com/urfave/cli"
@ -45,7 +42,6 @@ type historyJSONParams struct {
// historyOptions stores cli flag values
type historyOptions struct {
image string
human bool
noTrunc bool
quiet bool
@ -88,14 +84,12 @@ func historyCmd(c *cli.Context) error {
if err := validateFlags(c, historyFlags); err != nil {
return err
}
config, err := getConfig(c)
runtime, err := getRuntime(c)
if err != nil {
return errors.Wrapf(err, "Could not get config")
}
store, err := getStore(config)
if err != nil {
return err
}
defer runtime.Shutdown(false)
format := genHistoryFormat(c.Bool("quiet"))
if c.IsSet("format") {
@ -112,13 +106,18 @@ func historyCmd(c *cli.Context) error {
imgName := args[0]
opts := historyOptions{
image: imgName,
human: c.BoolT("human"),
noTrunc: c.Bool("no-trunc"),
quiet: c.Bool("quiet"),
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) {
@ -154,33 +153,6 @@ func (h *historyTemplateParams) headerMap() map[string]string {
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
func getHistoryTemplateOutput(history []v1.History, layers []types.BlobInfo, imageID string, opts historyOptions) (historyOutput []historyTemplateParams) {
var (
@ -251,11 +223,7 @@ func getHistoryJSONOutput(history []v1.History, layers []types.BlobInfo, imageID
}
// generateHistoryOutput generates the history based on the format given
func generateHistoryOutput(store storage.Store, 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)
}
func generateHistoryOutput(history []v1.History, layers []types.BlobInfo, imageID string, opts historyOptions) error {
if len(history) == 0 {
return nil
}

View file

@ -1,17 +1,46 @@
package main
import (
"fmt"
"reflect"
"strings"
"time"
"github.com/containers/image/types"
"github.com/containers/storage"
"github.com/docker/go-units"
"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"
"github.com/pkg/errors"
"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 (
imagesFlags = []cli.Flag{
cli.BoolFlag{
@ -55,145 +84,88 @@ func imagesCmd(c *cli.Context) error {
if err := validateFlags(c, imagesFlags); err != nil {
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
if c.IsSet("quiet") {
quiet = c.Bool("quiet")
runtime, err := getRuntime(c)
if err != nil {
return errors.Wrapf(err, "Could not get runtime")
}
noheading := false
if c.IsSet("noheading") {
noheading = c.Bool("noheading")
}
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)
defer runtime.Shutdown(false)
var format string
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 {
name = c.Args().Get(0)
} else if len(c.Args()) > 1 {
imageInput = c.Args().Get(0)
}
if len(c.Args()) > 1 {
return errors.New("'kpod images' requires at most 1 argument")
}
var params *libpod.FilterParams
if c.IsSet("filter") {
params, err = libpod.ParseFilter(store, c.String("filter"))
if err != nil {
return errors.Wrapf(err, "error parsing filter")
}
} else {
params = nil
params, err := runtime.ParseImageFilter(imageInput, c.String("filter"))
if err != nil {
return errors.Wrapf(err, "error parsing filter")
}
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 {
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 {
return formats.IDString
}
if truncate {
format = "table {{ .ID | printf \"%-20.12s\" }} "
} else {
format = "table {{ .ID | printf \"%-64s\" }} "
format = "table {{.ID}}\t{{.Name}}\t"
if noHeading {
format = "{{.ID}}\t{{.Name}}\t"
}
format += "{{ .Name | printf \"%-56s\" }} "
if digests {
format += "{{ .Digest | printf \"%-71s \"}} "
format += "{{.Digest}}\t"
}
format += "{{ .CreatedAt | printf \"%-22s\" }} {{.Size}}"
format += "{{.CreatedAt}}\t{{.Size}}\t"
return
}
func outputImages(store storage.Store, images []storage.Image, truncate, digests, quiet bool, outputFormat string, noheading bool) error {
imageOutput := []imageOutputParams{}
lastID := ""
for _, img := range images {
if quiet && lastID == img.ID {
continue // quiet should not show the same ID multiple times
// imagesToGeneric creates an empty array of interfaces for output
func imagesToGeneric(templParams []imagesTemplateParams, JSONParams []imagesJSONParams) (genericParams []interface{}) {
if len(templParams) > 0 {
for _, v := range templParams {
genericParams = append(genericParams, interface{}(v))
}
createdTime := img.Created
names := []string{""}
if len(img.Names) > 0 {
names = img.Names
}
info, imageDigest, size, _ := libpod.InfoAndDigestAndSize(store, img)
if info != nil {
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)
return
}
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()}
}
for _, v := range JSONParams {
genericParams = append(genericParams, interface{}(v))
}
formats.Writer(out).Out()
return nil
return
}
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 {
// generate the header based on the template provided
func (i *imagesTemplateParams) headerMap() map[string]string {
v := reflect.Indirect(reflect.ValueOf(i))
values := make(map[string]string)
@ -207,3 +179,152 @@ func (i *imageOutputParams) headerMap() map[string]string {
}
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"
"os"
"github.com/kubernetes-incubator/cri-o/libpod/images"
"github.com/kubernetes-incubator/cri-o/libpod"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
@ -91,9 +91,9 @@ func loadCmd(c *cli.Context) error {
output = os.Stdout
}
src := images.DockerArchive + ":" + input
src := libpod.DockerArchive + ":" + input
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
if image != "" {
src = src + ":" + image

View file

@ -3,8 +3,6 @@ package main
import (
"fmt"
"github.com/containers/storage"
"github.com/kubernetes-incubator/cri-o/libpod/images"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
@ -32,95 +30,27 @@ func rmiCmd(c *cli.Context) error {
return err
}
force := false
if c.IsSet("force") {
force = c.Bool("force")
runtime, err := getRuntime(c)
if err != nil {
return errors.Wrapf(err, "could not get runtime")
}
defer runtime.Shutdown(false)
args := c.Args()
if len(args) == 0 {
return errors.Errorf("image name or ID must be specified")
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "Could not get config")
}
store, err := getStore(config)
if err != nil {
return err
}
for _, id := range args {
image, err := images.FindImage(store, id)
for _, arg := range args {
image, err := runtime.GetImage(arg)
if err != nil {
return errors.Wrapf(err, "could not get image %q", id)
return errors.Wrapf(err, "could not get image %q", arg)
}
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)
}
}
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)
id, err := runtime.RemoveImage(image, c.Bool("force"))
if err != nil {
return errors.Wrapf(err, "error removing image %q", id)
}
fmt.Printf("%s\n", id)
}
return nil
}

View file

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

View file

@ -8,9 +8,10 @@ import (
"text/template"
"time"
"github.com/docker/go-units"
tm "github.com/buger/goterm"
"github.com/kubernetes-incubator/cri-o/libkpod"
"github.com/kubernetes-incubator/cri-o/libpod/images"
"github.com/kubernetes-incubator/cri-o/oci"
"github.com/pkg/errors"
"github.com/urfave/cli"
@ -182,7 +183,7 @@ func outputStatsUsingFormatString(stats *libkpod.ContainerStats) {
}
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 {

View file

@ -1,4 +1,4 @@
package images
package libpod
import (
"encoding/json"
@ -11,12 +11,14 @@ import (
"github.com/containers/image/docker/reference"
is "github.com/containers/image/storage"
"github.com/containers/image/transports"
"github.com/containers/image/types"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/docker/docker/pkg/ioutils"
"github.com/kubernetes-incubator/cri-o/cmd/kpod/docker"
"github.com/kubernetes-incubator/cri-o/libpod/common"
"github.com/kubernetes-incubator/cri-o/libpod/driver"
digest "github.com/opencontainers/go-digest"
"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
)
// 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
type CopyData struct {
store storage.Store
@ -360,13 +384,13 @@ func (c *CopyData) Save() error {
}
// 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 err error
if name != "" {
data, err = openCopyData(store, name)
data, err = openCopyData(r.store, name)
if os.IsNotExist(errors.Cause(err)) {
data, err = importCopyData(store, name, "")
data, err = r.importCopyData(r.store, name, "")
}
}
if err != nil {
@ -380,17 +404,17 @@ func GetContainerCopyData(store storage.Store, name string) (*CopyData, error) {
}
// 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 == "" {
return nil, errors.Errorf("image name must be specified")
}
img, err := FindImage(store, image)
img, err := r.GetImage(image)
if err != nil {
return nil, errors.Wrapf(err, "error locating image %q for importing settings", image)
}
systemContext := common.GetSystemContext("")
data, err := ImportCopyDataFromImage(store, systemContext, img.ID, "", "")
data, err := r.ImportCopyDataFromImage(systemContext, img.ID, "", "")
if err != nil {
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 == "" {
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)
data, err := ImportCopyDataFromImage(store, systemContext, c.ImageID, container, c.ID)
data, err := r.ImportCopyDataFromImage(systemContext, c.ImageID, container, c.ID)
if err != nil {
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
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{}
config := []byte{}
imageName := ""
if imageID != "" {
ref, err := is.Transport.ParseStoreReference(store, "@"+imageID)
ref, err := is.Transport.ParseStoreReference(r.store, "@"+imageID)
if err != nil {
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 {
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 {
imageName = img.Names[0]
}
@ -492,7 +516,7 @@ func ImportCopyDataFromImage(store storage.Store, systemContext *types.SystemCon
}
data := &CopyData{
store: store,
store: r.store,
Type: containerType,
FromImage: imageName,
FromImageID: imageID,
@ -550,3 +574,91 @@ func (c *CopyData) MakeImageRef(manifestType string, compress archive.Compressio
}
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 (
"bytes"

View file

@ -1,38 +1,37 @@
package libkpod
package libpod
import (
"github.com/containers/storage/pkg/archive"
"github.com/kubernetes-incubator/cri-o/libpod/images"
"github.com/kubernetes-incubator/cri-o/libpod/layers"
"github.com/pkg/errors"
)
// GetDiff returns the differences between the two images, layers, or containers
func (c *ContainerServer) GetDiff(from, to string) ([]archive.Change, error) {
toLayer, err := c.getLayerID(to)
func (r *Runtime) GetDiff(from, to string) ([]archive.Change, error) {
toLayer, err := r.getLayerID(to)
if err != nil {
return nil, err
}
fromLayer := ""
if from != "" {
fromLayer, err = c.getLayerID(from)
fromLayer, err = r.getLayerID(from)
if err != nil {
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
// 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
func (c *ContainerServer) getLayerID(id string) (string, error) {
func (r *Runtime) getLayerID(id string) (string, error) {
var toLayer string
toImage, err := images.FindImage(c.store, id)
toImage, err := r.GetImage(id)
if err != nil {
toCtr, err := c.store.Container(id)
toCtr, err := r.store.Container(id)
if err != nil {
toLayer, err = layers.FullID(c.store, id)
toLayer, err = layers.FullID(r.store, id)
if err != nil {
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
}
func (c *ContainerServer) getLayerParent(layerID string) (string, error) {
layer, err := c.store.Layer(layerID)
func (r *Runtime) getLayerParent(layerID string) (string, error) {
layer, err := r.store.Layer(layerID)
if err != nil {
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"
"github.com/containers/image/docker/reference"
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/driver"
digest "github.com/opencontainers/go-digest"
@ -162,3 +164,40 @@ func GetData(store storage.Store, name string) (*Data, error) {
RootFS: config.RootFS,
}, 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
import (
"github.com/containers/storage"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
@ -99,11 +100,11 @@ func (r *Runtime) LookupContainer(idOrName string) (*Container, error) {
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
// the output. Multiple filters are handled by ANDing their output, so only
// 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()
defer r.lock.RUnlock()
@ -131,3 +132,29 @@ func (r *Runtime) Containers(filters ...ContainerFilter) ([]*Container, error) {
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
import (
"encoding/json"
"fmt"
"io"
"strings"
"syscall"
"time"
cp "github.com/containers/image/copy"
dockerarchive "github.com/containers/image/docker/archive"
"github.com/containers/image/docker/reference"
"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"
"github.com/containers/image/transports/alltransports"
"github.com/containers/image/types"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"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"
)
@ -60,10 +65,21 @@ type CopyOptions struct {
// 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
// command output. Images to be outputted are tested using the function. A true
// 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
// 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()
if !r.valid {
return fmt.Errorf("runtime is not valid")
return ErrRuntimeStopped
}
// 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()
if !r.valid {
return fmt.Errorf("runtime is not valid")
return ErrRuntimeStopped
}
// 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 {
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 {
return err
}
@ -239,7 +255,7 @@ func (r *Runtime) TagImage(image *storage.Image, tag string) error {
defer r.lock.Unlock()
if !r.valid {
return fmt.Errorf("runtime is not valid")
return ErrRuntimeStopped
}
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
func (r *Runtime) UntagImage(image *storage.Image, tag string) error {
func (r *Runtime) UntagImage(image *storage.Image, tag string) (string, error) {
r.lock.Lock()
defer r.lock.Unlock()
if !r.valid {
return fmt.Errorf("runtime is not valid")
return "", ErrRuntimeStopped
}
tags, err := r.store.Names(image.ID)
if err != nil {
return err
return "", err
}
for i, key := range tags {
if key == tag {
@ -275,21 +291,49 @@ func (r *Runtime) UntagImage(image *storage.Image, tag string) error {
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
// Images being used by running containers cannot be removed
func (r *Runtime) RemoveImage(image *storage.Image) error {
// Images being used by running containers can only be removed if force=true
func (r *Runtime) RemoveImage(image *storage.Image, force bool) (string, error) {
r.lock.Lock()
defer r.lock.Unlock()
if !r.valid {
return fmt.Errorf("runtime is not valid")
return "", ErrRuntimeStopped
}
_, err := r.store.DeleteImage(image.ID, false)
return err
containersWithImage, err := r.getContainersWithImage(image.ID)
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
@ -300,7 +344,7 @@ func (r *Runtime) GetImage(image string) (*storage.Image, error) {
defer r.lock.Unlock()
if !r.valid {
return nil, fmt.Errorf("runtime is not valid")
return nil, ErrRuntimeStopped
}
return r.getImage(image)
}
@ -330,9 +374,13 @@ func (r *Runtime) GetImageRef(image string) (types.Image, error) {
defer r.lock.Unlock()
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)
if err != nil {
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
// output. Multiple filters are handled by ANDing their output, so only images
// matching all filters are included
func (r *Runtime) GetImages(filter ...ImageFilter) ([]*storage.Image, error) {
return nil, ErrNotImplemented
func (r *Runtime) GetImages(params *ImageFilterParams, filters ...ImageFilter) ([]*storage.Image, error) {
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
func (r *Runtime) ImportImage(path string) (*storage.Image, error) {
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"
[ "$status" -eq 0 ]
inspectOutput="$output"
run ${KPOD_BINARY} $KPOD_OPTIONS images --quiet ${IMAGE}
run ${KPOD_BINARY} $KPOD_OPTIONS images --no-trunc --quiet ${IMAGE}
echo "$output"
[ "$status" -eq 0 ]
[ "$output" = "$inspectOutput" ]

View file

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