Merge pull request #988 from umohnani8/libpod-part2
Continue switching from libkpod to libpod
This commit is contained in:
commit
772f4b1515
18 changed files with 820 additions and 837 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package images
|
package libpod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
|
@ -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
|
||||||
}
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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 ¶ms, 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 ¶ms, 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")
|
||||||
|
}
|
|
@ -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" ]
|
||||||
|
|
|
@ -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 ]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue