Merge pull request #648 from 14rcole/kpod-inspect

Implement `kpod inspect`
This commit is contained in:
Mrunal Patel 2017-07-21 07:12:08 -07:00 committed by GitHub
commit 0e17bf4659
17 changed files with 1180 additions and 318 deletions

View file

@ -43,6 +43,7 @@ It is currently in active development in the Kubernetes community through the [d
| [kpod(1)](/docs/kpod.1.md) | Simple management tool for pods and images | | [kpod(1)](/docs/kpod.1.md) | Simple management tool for pods and images |
| [kpod-history(1)](/docs/kpod-history.1.md)] | Shows the history of an image | | [kpod-history(1)](/docs/kpod-history.1.md)] | Shows the history of an image |
| [kpod-images(1)](/docs/kpod-images.1.md) | List images in local storage | | [kpod-images(1)](/docs/kpod-images.1.md) | List images in local storage |
| [kpod-inspect(1)](/docs/kpod-inspect.1.md) | Display the configuration of a container or image |
| [kpod-pull(1)](/docs/kpod-pull.1.md) | Pull an image from a registry | | [kpod-pull(1)](/docs/kpod-pull.1.md) | Pull an image from a registry |
| [kpod-push(1)](/docs/kpod-push.1.md) | Push an image to a specified destination | | [kpod-push(1)](/docs/kpod-push.1.md) | Push an image to a specified destination |
| [kpod-rmi(1)](/docs/kpod-rmi.1.md) | Removes one or more images | | [kpod-rmi(1)](/docs/kpod-rmi.1.md) | Removes one or more images |

View file

@ -2,7 +2,6 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"strings" "strings"
"time" "time"
@ -78,6 +77,35 @@ func getStore(c *cli.Context) (storage.Store, error) {
return store, nil return store, nil
} }
func parseMetadata(image storage.Image) (imageMetadata, error) {
var im imageMetadata
dec := json.NewDecoder(strings.NewReader(image.Metadata))
if err := dec.Decode(&im); err != nil {
return imageMetadata{}, err
}
return im, nil
}
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
}
func getCopyOptions(reportWriter io.Writer, signaturePolicyPath string, srcDockerRegistry, destDockerRegistry *dockerRegistryOptions, signing signingOptions) *cp.Options { func getCopyOptions(reportWriter io.Writer, signaturePolicyPath string, srcDockerRegistry, destDockerRegistry *dockerRegistryOptions, signing signingOptions) *cp.Options {
if srcDockerRegistry == nil { if srcDockerRegistry == nil {
srcDockerRegistry = &dockerRegistryOptions{} srcDockerRegistry = &dockerRegistryOptions{}
@ -96,33 +124,20 @@ func getCopyOptions(reportWriter io.Writer, signaturePolicyPath string, srcDocke
} }
} }
func getPolicyContext(path string) (*signature.PolicyContext, error) { func findContainer(store storage.Store, container string) (*storage.Container, error) {
policy, err := signature.DefaultPolicy(&types.SystemContext{SignaturePolicyPath: path}) ctrStore, err := store.ContainerStore()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return signature.NewPolicyContext(policy) return ctrStore.Get(container)
} }
func findImage(store storage.Store, image string) (*storage.Image, error) { func getContainerTopLayerID(store storage.Store, containerID string) (string, error) {
var img *storage.Image ctr, err := findContainer(store, containerID)
ref, err := is.Transport.ParseStoreReference(store, image)
if err == nil {
img, err := is.Transport.GetStoreImage(store, ref)
if err != nil { if err != nil {
return nil, err return "", err
} }
return img, nil return ctr.LayerID, 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
} }
func getSystemContext(signaturePolicyPath string) *types.SystemContext { func getSystemContext(signaturePolicyPath string) *types.SystemContext {
@ -133,37 +148,6 @@ func getSystemContext(signaturePolicyPath string) *types.SystemContext {
return sc return sc
} }
func parseMetadata(image storage.Image) (imageMetadata, error) {
var im imageMetadata
dec := json.NewDecoder(strings.NewReader(image.Metadata))
if err := dec.Decode(&im); err != nil {
return imageMetadata{}, err
}
return im, nil
}
func getSize(image storage.Image, store storage.Store) (int64, error) {
is.Transport.SetStore(store)
storeRef, err := is.Transport.ParseStoreReference(store, "@"+image.ID)
if err != nil {
fmt.Println(err)
return -1, err
}
img, err := storeRef.NewImage(nil)
if err != nil {
fmt.Println("Error with NewImage")
return -1, err
}
imgSize, err := img.Size()
if err != nil {
fmt.Println("Error getting size")
return -1, err
}
return imgSize, nil
}
func copyStringStringMap(m map[string]string) map[string]string { func copyStringStringMap(m map[string]string) map[string]string {
n := map[string]string{} n := map[string]string{}
for k, v := range m { for k, v := range m {
@ -172,16 +156,140 @@ func copyStringStringMap(m map[string]string) map[string]string {
return n return n
} }
func (o dockerRegistryOptions) getSystemContext(signaturePolicyPath string) *types.SystemContext { // A container FS is split into two parts. The first is the top layer, a
sc := &types.SystemContext{ // mutable layer, and the rest is the RootFS: the set of immutable layers
SignaturePolicyPath: signaturePolicyPath, // that make up the image on which the container is based
DockerAuthConfig: o.DockerRegistryCreds, func getRootFsSize(store storage.Store, containerID string) (int64, error) {
DockerCertPath: o.DockerCertPath, ctrStore, err := store.ContainerStore()
DockerInsecureSkipTLSVerify: o.DockerInsecureSkipTLSVerify, if err != nil {
return 0, err
} }
return sc container, err := ctrStore.Get(containerID)
if err != nil {
return 0, err
}
lstore, err := store.LayerStore()
if err != nil {
return 0, err
} }
// Ignore the size of the top layer. The top layer is a mutable RW layer
// and is not considered a part of the rootfs
rwLayer, err := lstore.Get(container.LayerID)
if err != nil {
return 0, err
}
layer, err := lstore.Get(rwLayer.Parent)
if err != nil {
return 0, err
}
size := int64(0)
for layer.Parent != "" {
layerSize, err := lstore.DiffSize(layer.Parent, layer.ID)
if err != nil {
return size, errors.Wrapf(err, "getting diffsize of layer %q and its parent %q", layer.ID, layer.Parent)
}
size += layerSize
layer, err = lstore.Get(layer.Parent)
if err != nil {
return 0, err
}
}
// Get the size of the last layer. Has to be outside of the loop
// because the parent of the last layer is "", andlstore.Get("")
// will return an error
layerSize, err := lstore.DiffSize(layer.Parent, layer.ID)
return size + layerSize, err
}
func getContainerRwSize(store storage.Store, containerID string) (int64, error) {
ctrStore, err := store.ContainerStore()
if err != nil {
return 0, err
}
container, err := ctrStore.Get(containerID)
if err != nil {
return 0, err
}
lstore, err := store.LayerStore()
if err != nil {
return 0, err
}
// Get the size of the top layer by calculating the size of the diff
// between the layer and its parent. The top layer of a container is
// the only RW layer, all others are immutable
layer, err := lstore.Get(container.LayerID)
if err != nil {
return 0, err
}
return lstore.DiffSize(layer.Parent, layer.ID)
}
func isTrue(str string) bool {
return str == "true"
}
func isFalse(str string) bool {
return str == "false"
}
func isValidBool(str string) bool {
return isTrue(str) || isFalse(str)
}
func getDriverName(store storage.Store) (string, error) {
driver, err := store.GraphDriver()
if err != nil {
return "", err
}
return driver.String(), nil
}
func getDriverMetadata(store storage.Store, layerID string) (map[string]string, error) {
driver, err := store.GraphDriver()
if err != nil {
return nil, err
}
return driver.Metadata(layerID)
}
func getImageSize(image storage.Image, store storage.Store) (int64, error) {
is.Transport.SetStore(store)
storeRef, err := is.Transport.ParseStoreReference(store, "@"+image.ID)
if err != nil {
return -1, err
}
img, err := storeRef.NewImage(nil)
if err != nil {
return -1, err
}
imgSize, err := img.Size()
if err != nil {
return -1, err
}
return imgSize, nil
}
func getImageTopLayer(image storage.Image) (string, error) {
metadata, err := parseMetadata(image)
if err != nil {
return "", err
}
// Get the digest of the first blob
digest := string(metadata.Blobs[0].Digest)
// Return the first layer associated with the given digest
return metadata.Layers[digest][0], nil
}
func getPolicyContext(path string) (*signature.PolicyContext, error) {
policy, err := signature.DefaultPolicy(&types.SystemContext{SignaturePolicyPath: path})
if err != nil {
return nil, err
}
return signature.NewPolicyContext(policy)
}
func parseRegistryCreds(creds string) (*types.DockerAuthConfig, error) { func parseRegistryCreds(creds string) (*types.DockerAuthConfig, error) {
if creds == "" { if creds == "" {
return nil, errors.New("no credentials supplied") return nil, errors.New("no credentials supplied")
@ -196,3 +304,13 @@ func parseRegistryCreds(creds string) (*types.DockerAuthConfig, error) {
} }
return cfg, nil return cfg, nil
} }
func (o dockerRegistryOptions) getSystemContext(signaturePolicyPath string) *types.SystemContext {
sc := &types.SystemContext{
SignaturePolicyPath: signaturePolicyPath,
DockerAuthConfig: o.DockerRegistryCreds,
DockerCertPath: o.DockerCertPath,
DockerInsecureSkipTLSVerify: o.DockerInsecureSkipTLSVerify,
}
return sc
}

View file

@ -70,7 +70,7 @@ func TestGetSize(t *testing.T) {
t.Fatalf("Error reading images: %v", err) t.Fatalf("Error reading images: %v", err)
} }
_, err = getSize(images[0], store) _, err = getImageSize(images[0], store)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

219
cmd/kpod/containerData.go Normal file
View file

@ -0,0 +1,219 @@
package main
import (
"encoding/json"
"time"
"k8s.io/apimachinery/pkg/fields"
pb "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"github.com/containers/storage"
"github.com/kubernetes-incubator/cri-o/cmd/kpod/docker"
"github.com/kubernetes-incubator/cri-o/oci"
"github.com/kubernetes-incubator/cri-o/pkg/annotations"
"github.com/kubernetes-incubator/cri-o/server"
"github.com/opencontainers/image-spec/specs-go/v1"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
type containerData struct {
ID string
Name string
LogPath string
Labels fields.Set
Annotations fields.Set
State *oci.ContainerState
Metadata *pb.ContainerMetadata
BundlePath string
StopSignal string
Type string `json:"type"`
FromImage string `json:"image,omitempty"`
FromImageID string `json:"image-id"`
MountPoint string `json:"mountpoint,omitempty"`
MountLabel string
Mounts []specs.Mount
AppArmorProfile string
ImageAnnotations map[string]string `json:"annotations,omitempty"`
ImageCreatedBy string `json:"created-by,omitempty"`
OCIv1 v1.Image `json:"ociv1,omitempty"`
Docker docker.V2Image `json:"docker,omitempty"`
SizeRw uint `json:"SizeRw,omitempty"`
SizeRootFs uint `json:"SizeRootFs,omitempty"`
Args []string
ResolvConfPath string
HostnamePath string
HostsPath string
GraphDriver driverData
}
type driverData struct {
Name string
Data map[string]string
}
func getContainerData(store storage.Store, name string, size bool) (*containerData, error) {
ctr, err := inspectContainer(store, name)
if err != nil {
return nil, errors.Wrapf(err, "error reading build container %q", name)
}
cid, err := openContainer(store, name)
if err != nil {
return nil, errors.Wrapf(err, "error reading container image data")
}
config, err := store.FromContainerDirectory(ctr.ID(), "config.json")
if err != nil {
return nil, err
}
var m specs.Spec
if err = json.Unmarshal(config, &m); err != nil {
return nil, err
}
driverName, err := getDriverName(store)
if err != nil {
return nil, err
}
topLayer, err := getContainerTopLayerID(store, ctr.ID())
if err != nil {
return nil, err
}
driverMetadata, err := getDriverMetadata(store, topLayer)
if err != nil {
return nil, err
}
data := &containerData{
ID: ctr.ID(),
Name: ctr.Name(),
LogPath: ctr.LogPath(),
Labels: ctr.Labels(),
Annotations: ctr.Annotations(),
State: ctr.State(),
Metadata: ctr.Metadata(),
BundlePath: ctr.BundlePath(),
StopSignal: ctr.GetStopSignal(),
Args: m.Process.Args,
Type: cid.Type,
FromImage: cid.FromImage,
FromImageID: cid.FromImageID,
MountPoint: cid.MountPoint,
ImageAnnotations: cid.ImageAnnotations,
ImageCreatedBy: cid.ImageCreatedBy,
OCIv1: cid.OCIv1,
Docker: cid.Docker,
GraphDriver: driverData{
Name: driverName,
Data: driverMetadata,
},
MountLabel: m.Linux.MountLabel,
Mounts: m.Mounts,
AppArmorProfile: m.Process.ApparmorProfile,
ResolvConfPath: "",
HostnamePath: "",
HostsPath: "",
}
if size {
sizeRootFs, err := getRootFsSize(store, data.ID)
if err != nil {
return nil, errors.Wrapf(err, "error reading size for container %q", name)
}
data.SizeRootFs = uint(sizeRootFs)
sizeRw, err := getContainerRwSize(store, data.ID)
if err != nil {
return nil, errors.Wrapf(err, "error reading RWSize for container %q", name)
}
data.SizeRw = uint(sizeRw)
}
return data, nil
}
// Get an oci.Container and update its status
func inspectContainer(store storage.Store, container string) (*oci.Container, error) {
ociCtr, err := getOCIContainer(store, container)
if err != nil {
return nil, err
}
// call oci.New() to get the runtime
runtime, err := getOCIRuntime(store, container)
if err != nil {
return nil, err
}
// call runtime.UpdateStatus()
err = runtime.UpdateStatus(ociCtr)
if err != nil {
return nil, err
}
return ociCtr, nil
}
// get an oci.Container instance for a given container ID
func getOCIContainer(store storage.Store, container string) (*oci.Container, error) {
ctr, err := findContainer(store, container)
if err != nil {
return nil, err
}
config, err := store.FromContainerDirectory(ctr.ID, "config.json")
if err != nil {
return nil, err
}
var m specs.Spec
if err = json.Unmarshal(config, &m); err != nil {
return nil, err
}
labels := make(map[string]string)
err = json.Unmarshal([]byte(m.Annotations[annotations.Labels]), &labels)
if len(m.Annotations[annotations.Labels]) > 0 && err != nil {
return nil, err
}
name := ctr.Names[0]
var metadata pb.ContainerMetadata
err = json.Unmarshal([]byte(m.Annotations[annotations.Metadata]), &metadata)
if len(m.Annotations[annotations.Metadata]) > 0 && err != nil {
return nil, err
}
tty := isTrue(m.Annotations[annotations.TTY])
stdin := isTrue(m.Annotations[annotations.Stdin])
stdinOnce := isTrue(m.Annotations[annotations.StdinOnce])
containerPath, err := store.ContainerRunDirectory(ctr.ID)
if err != nil {
return nil, err
}
containerDir, err := store.ContainerDirectory(ctr.ID)
if err != nil {
return nil, err
}
img, _ := m.Annotations[annotations.Image]
kubeAnnotations := make(map[string]string)
err = json.Unmarshal([]byte(m.Annotations[annotations.Annotations]), &kubeAnnotations)
if len(m.Annotations[annotations.Annotations]) > 0 && err != nil {
return nil, err
}
created := time.Time{}
if len(m.Annotations[annotations.Created]) > 0 {
created, err = time.Parse(time.RFC3339Nano, m.Annotations[annotations.Created])
if err != nil {
return nil, err
}
}
// create a new OCI Container. kpod currently doesn't deal with pod sandboxes, so the fields for netns, privileged, and trusted are left empty
return oci.NewContainer(ctr.ID, name, containerPath, m.Annotations[annotations.LogPath], nil, labels, kubeAnnotations, img, &metadata, ctr.ImageID, tty, stdin, stdinOnce, false, false, containerDir, created, m.Annotations["org.opencontainers.image.stopSignal"])
}
func getOCIRuntime(store storage.Store, container string) (*oci.Runtime, error) {
config := server.DefaultConfig()
return oci.New(config.Runtime, config.RuntimeUntrustedWorkload, config.DefaultWorkloadTrust, config.Conmon, config.ConmonEnv, config.CgroupManager)
}

View file

@ -3,6 +3,8 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"time" "time"
@ -12,7 +14,8 @@ import (
"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/cmd/kpod/docker" // Get rid of this eventually "github.com/docker/docker/pkg/ioutils"
"github.com/kubernetes-incubator/cri-o/cmd/kpod/docker"
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"
@ -20,142 +23,159 @@ import (
) )
const ( const (
// Package is used to identify working containers
Package = "kpod"
containerType = Package + " 0.0.1"
stateFile = Package + ".json"
// OCIv1ImageManifest is the MIME type of an OCIv1 image manifest, // OCIv1ImageManifest is the MIME type of an OCIv1 image manifest,
// suitable for specifying as a value of the PreferredManifestType // suitable for specifying as a value of the PreferredManifestType
// member of a CommitOptions structure. It is also the default. // member of a CommitOptions structure. It is also the default.
OCIv1ImageManifest = v1.MediaTypeImageManifest OCIv1ImageManifest = v1.MediaTypeImageManifest
) )
type imagePushData struct { type containerImageData struct {
store storage.Store store storage.Store
// Type is used to help a build container's metadata
// Type is used to help identify a build container's metadata. It
// should not be modified.
Type string `json:"type"` Type string `json:"type"`
// FromImage is the name of the source image which ws used to create // FromImage is the name of the source image which was used to create
// the container, if one was used // the container, if one was used. It should not be modified.
FromImage string `json:"image,omitempty"` FromImage string `json:"image,omitempty"`
// FromImageID is the id of the source image // FromImageID is the ID of the source image which was used to create
FromImageID string `json:"imageid"` // the container, if one was used. It should not be modified.
// Config is the source image's configuration FromImageID string `json:"image-id"`
// Config is the source image's configuration. It should not be
// modified.
Config []byte `json:"config,omitempty"` Config []byte `json:"config,omitempty"`
// Manifest is the source image's manifest // Manifest is the source image's manifest. It should not be modified.
Manifest []byte `json:"manifest,omitempty"` Manifest []byte `json:"manifest,omitempty"`
// Container is the name of the build container. It should not be modified.
Container string `json:"container-name,omitempty"`
// ContainerID is the ID of the build container. It should not be modified.
ContainerID string `json:"container-id,omitempty"`
// MountPoint is the last location where the container's root
// filesystem was mounted. It should not be modified.
MountPoint string `json:"mountpoint,omitempty"`
// ImageAnnotations is a set of key-value pairs which is stored in the // ImageAnnotations is a set of key-value pairs which is stored in the
// image's manifest // image's manifest.
ImageAnnotations map[string]string `json:"annotations,omitempty"` ImageAnnotations map[string]string `json:"annotations,omitempty"`
// ImageCreatedBy is a description of how this container was built // ImageCreatedBy is a description of how this container was built.
ImageCreatedBy string `json:"created-by,omitempty"` ImageCreatedBy string `json:"created-by,omitempty"`
// Image metadata and runtime settings, in multiple formats // Image metadata and runtime settings, in multiple formats.
OCIv1 ociv1.Image `json:"ociv1,omitempty"` OCIv1 v1.Image `json:"ociv1,omitempty"`
Docker docker.V2Image `json:"docker,omitempty"` Docker docker.V2Image `json:"docker,omitempty"`
} }
func (i *imagePushData) initConfig() { func (c *containerImageData) initConfig() {
image := ociv1.Image{} image := ociv1.Image{}
dimage := docker.V2Image{} dimage := docker.V2Image{}
if len(i.Config) > 0 { if len(c.Config) > 0 {
// Try to parse the image config. If we fail, try to start over from scratch // Try to parse the image config. If we fail, try to start over from scratch
if err := json.Unmarshal(i.Config, &dimage); err == nil && dimage.DockerVersion != "" { if err := json.Unmarshal(c.Config, &dimage); err == nil && dimage.DockerVersion != "" {
image, err = makeOCIv1Image(&dimage) image, err = makeOCIv1Image(&dimage)
if err != nil { if err != nil {
image = ociv1.Image{} image = ociv1.Image{}
} }
} else { } else {
if err := json.Unmarshal(i.Config, &image); err != nil { if err := json.Unmarshal(c.Config, &image); err != nil {
if dimage, err = makeDockerV2S2Image(&image); err != nil { if dimage, err = makeDockerV2S2Image(&image); err != nil {
dimage = docker.V2Image{} dimage = docker.V2Image{}
} }
} }
} }
i.OCIv1 = image c.OCIv1 = image
i.Docker = dimage c.Docker = dimage
} else { } else {
// Try to dig out the image configuration from the manifest // Try to dig out the image configuration from the manifest
manifest := docker.V2S1Manifest{} manifest := docker.V2S1Manifest{}
if err := json.Unmarshal(i.Manifest, &manifest); err == nil && manifest.SchemaVersion == 1 { if err := json.Unmarshal(c.Manifest, &manifest); err == nil && manifest.SchemaVersion == 1 {
if dimage, err = makeDockerV2S1Image(manifest); err == nil { if dimage, err = makeDockerV2S1Image(manifest); err == nil {
if image, err = makeOCIv1Image(&dimage); err != nil { if image, err = makeOCIv1Image(&dimage); err != nil {
image = ociv1.Image{} image = ociv1.Image{}
} }
} }
} }
i.OCIv1 = image c.OCIv1 = image
i.Docker = dimage c.Docker = dimage
} }
if len(i.Manifest) > 0 { if len(c.Manifest) > 0 {
// Attempt to recover format-specific data from the manifest // Attempt to recover format-specific data from the manifest
v1Manifest := ociv1.Manifest{} v1Manifest := ociv1.Manifest{}
if json.Unmarshal(i.Manifest, &v1Manifest) == nil { if json.Unmarshal(c.Manifest, &v1Manifest) == nil {
i.ImageAnnotations = v1Manifest.Annotations c.ImageAnnotations = v1Manifest.Annotations
} }
} }
i.fixupConfig() c.fixupConfig()
} }
func (i *imagePushData) fixupConfig() { func (c *containerImageData) fixupConfig() {
if i.Docker.Config != nil { if c.Docker.Config != nil {
// Prefer image-level settings over those from the container it was built from // Prefer image-level settings over those from the container it was built from
i.Docker.ContainerConfig = *i.Docker.Config c.Docker.ContainerConfig = *c.Docker.Config
} }
i.Docker.Config = &i.Docker.ContainerConfig c.Docker.Config = &c.Docker.ContainerConfig
i.Docker.DockerVersion = "" c.Docker.DockerVersion = ""
now := time.Now().UTC() now := time.Now().UTC()
if i.Docker.Created.IsZero() { if c.Docker.Created.IsZero() {
i.Docker.Created = now c.Docker.Created = now
} }
if i.OCIv1.Created.IsZero() { if c.OCIv1.Created.IsZero() {
i.OCIv1.Created = &now c.OCIv1.Created = &now
} }
if i.OS() == "" { if c.OS() == "" {
i.SetOS(runtime.GOOS) c.SetOS(runtime.GOOS)
} }
if i.Architecture() == "" { if c.Architecture() == "" {
i.SetArchitecture(runtime.GOARCH) c.SetArchitecture(runtime.GOARCH)
} }
if i.WorkDir() == "" { if c.WorkDir() == "" {
i.SetWorkDir(string(filepath.Separator)) c.SetWorkDir(string(filepath.Separator))
} }
} }
// OS returns a name of the OS on which a container built using this image // OS returns a name of the OS on which a container built using this image
//is intended to be run. //is intended to be run.
func (i *imagePushData) OS() string { func (c *containerImageData) OS() string {
return i.OCIv1.OS return c.OCIv1.OS
} }
// SetOS sets the name of the OS on which a container built using this image // SetOS sets the name of the OS on which a container built using this image
// is intended to be run. // is intended to be run.
func (i *imagePushData) SetOS(os string) { func (c *containerImageData) SetOS(os string) {
i.OCIv1.OS = os c.OCIv1.OS = os
i.Docker.OS = os c.Docker.OS = os
} }
// Architecture returns a name of the architecture on which a container built // Architecture returns a name of the architecture on which a container built
// using this image is intended to be run. // using this image is intended to be run.
func (i *imagePushData) Architecture() string { func (c *containerImageData) Architecture() string {
return i.OCIv1.Architecture return c.OCIv1.Architecture
} }
// SetArchitecture sets the name of the architecture on which ta container built // SetArchitecture sets the name of the architecture on which ta container built
// using this image is intended to be run. // using this image is intended to be run.
func (i *imagePushData) SetArchitecture(arch string) { func (c *containerImageData) SetArchitecture(arch string) {
i.OCIv1.Architecture = arch c.OCIv1.Architecture = arch
i.Docker.Architecture = arch c.Docker.Architecture = arch
} }
// WorkDir returns the default working directory for running commands in a container // WorkDir returns the default working directory for running commands in a container
// built using this image. // built using this image.
func (i *imagePushData) WorkDir() string { func (c *containerImageData) WorkDir() string {
return i.OCIv1.Config.WorkingDir return c.OCIv1.Config.WorkingDir
} }
// SetWorkDir sets the location of the default working directory for running commands // SetWorkDir sets the location of the default working directory for running commands
// in a container built using this image. // in a container built using this image.
func (i *imagePushData) SetWorkDir(there string) { func (c *containerImageData) SetWorkDir(there string) {
i.OCIv1.Config.WorkingDir = there c.OCIv1.Config.WorkingDir = there
i.Docker.Config.WorkingDir = there c.Docker.Config.WorkingDir = there
} }
// makeOCIv1Image builds the best OCIv1 image structure we can from the // makeOCIv1Image builds the best OCIv1 image structure we can from the
@ -318,11 +338,172 @@ func makeDockerV2S1Image(manifest docker.V2S1Manifest) (docker.V2Image, error) {
return dimage, nil return dimage, nil
} }
func (i *imagePushData) Annotations() map[string]string { func (c *containerImageData) Annotations() map[string]string {
return copyStringStringMap(i.ImageAnnotations) return copyStringStringMap(c.ImageAnnotations)
} }
func (i *imagePushData) makeImageRef(manifestType string, compress archive.Compression, names []string, layerID string, historyTimestamp *time.Time) (types.ImageReference, error) { func (c *containerImageData) Save() error {
buildstate, err := json.Marshal(c)
if err != nil {
return err
}
cdir, err := c.store.ContainerDirectory(c.ContainerID)
if err != nil {
return err
}
return ioutils.AtomicWriteFile(filepath.Join(cdir, stateFile), buildstate, 0600)
}
func openContainer(store storage.Store, name string) (*containerImageData, error) {
var data *containerImageData
var err error
if name != "" {
data, err = openContainerImageData(store, name)
if os.IsNotExist(err) {
data, err = importContainerImageData(store, name, "")
}
}
if err != nil {
return nil, errors.Wrapf(err, "error reading build container")
}
if data == nil {
return nil, errors.Errorf("error finding build container")
}
return data, nil
}
func openImage(store storage.Store, image string) (*containerImageData, error) {
if image == "" {
return nil, errors.Errorf("image name must be specified")
}
img, err := findImage(store, image)
if err != nil {
return nil, errors.Wrapf(err, "error locating image %q for importing settings", image)
}
systemContext := getSystemContext("")
data, err := importContainerImageDataFromImage(store, systemContext, img.ID, "", "")
if err != nil {
return nil, errors.Wrapf(err, "error reading image")
}
if data == nil {
return nil, errors.Errorf("error mocking up build configuration")
}
return data, nil
}
func importContainerImageData(store storage.Store, container, signaturePolicyPath string) (*containerImageData, error) {
if container == "" {
return nil, errors.Errorf("container name must be specified")
}
c, err := store.Container(container)
if err != nil {
return nil, err
}
systemContext := getSystemContext(signaturePolicyPath)
data, err := importContainerImageDataFromImage(store, systemContext, c.ImageID, container, c.ID)
if err != nil {
return nil, err
}
if data.FromImageID != "" {
if d, err2 := digest.Parse(data.FromImageID); err2 == nil {
data.Docker.Parent = docker.ID(d)
} else {
data.Docker.Parent = docker.ID(digest.NewDigestFromHex(digest.Canonical.String(), data.FromImageID))
}
}
if data.FromImage != "" {
data.Docker.ContainerConfig.Image = data.FromImage
}
err = data.Save()
if err != nil {
return nil, errors.Wrapf(err, "error saving containerImageData state")
}
return data, nil
}
func openContainerImageData(store storage.Store, container string) (*containerImageData, error) {
cdir, err := store.ContainerDirectory(container)
if err != nil {
return nil, err
}
buildstate, err := ioutil.ReadFile(filepath.Join(cdir, stateFile))
if err != nil {
return nil, err
}
c := &containerImageData{}
err = json.Unmarshal(buildstate, &c)
if err != nil {
return nil, err
}
if c.Type != containerType {
return nil, errors.Errorf("container is not a %s container", Package)
}
c.store = store
c.fixupConfig()
return c, nil
}
func importContainerImageDataFromImage(store storage.Store, systemContext *types.SystemContext, imageID, containerName, containerID string) (*containerImageData, error) {
manifest := []byte{}
config := []byte{}
imageName := ""
if imageID != "" {
ref, err := is.Transport.ParseStoreReference(store, "@"+imageID)
if err != nil {
return nil, errors.Wrapf(err, "no such image %q", "@"+imageID)
}
src, err2 := ref.NewImage(systemContext)
if err2 != nil {
return nil, errors.Wrapf(err2, "error instantiating image")
}
defer src.Close()
config, err = src.ConfigBlob()
if err != nil {
return nil, errors.Wrapf(err, "error reading image configuration")
}
manifest, _, err = src.Manifest()
if err != nil {
return nil, errors.Wrapf(err, "error reading image manifest")
}
if img, err3 := store.Image(imageID); err3 == nil {
if len(img.Names) > 0 {
imageName = img.Names[0]
}
}
}
data := &containerImageData{
store: store,
Type: containerType,
FromImage: imageName,
FromImageID: imageID,
Config: config,
Manifest: manifest,
Container: containerName,
ContainerID: containerID,
ImageAnnotations: map[string]string{},
ImageCreatedBy: "",
}
data.initConfig()
return data, nil
}
func (c *containerImageData) makeImageRef(manifestType string, compress archive.Compression, names []string, layerID string, historyTimestamp *time.Time) (types.ImageReference, error) {
var name reference.Named var name reference.Named
if len(names) > 0 { if len(names) > 0 {
if parsed, err := reference.ParseNamed(names[0]); err == nil { if parsed, err := reference.ParseNamed(names[0]); err == nil {
@ -332,11 +513,11 @@ func (i *imagePushData) makeImageRef(manifestType string, compress archive.Compr
if manifestType == "" { if manifestType == "" {
manifestType = OCIv1ImageManifest manifestType = OCIv1ImageManifest
} }
oconfig, err := json.Marshal(&i.OCIv1) oconfig, err := json.Marshal(&c.OCIv1)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "error encoding OCI-format image configuration") return nil, errors.Wrapf(err, "error encoding OCI-format image configuration")
} }
dconfig, err := json.Marshal(&i.Docker) dconfig, err := json.Marshal(&c.Docker)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "error encoding docker-format image configuration") return nil, errors.Wrapf(err, "error encoding docker-format image configuration")
} }
@ -345,7 +526,7 @@ func (i *imagePushData) makeImageRef(manifestType string, compress archive.Compr
created = historyTimestamp.UTC() created = historyTimestamp.UTC()
} }
ref := &containerImageRef{ ref := &containerImageRef{
store: i.store, store: c.store,
compression: compress, compression: compress,
name: name, name: name,
names: names, names: names,
@ -354,53 +535,10 @@ func (i *imagePushData) makeImageRef(manifestType string, compress archive.Compr
oconfig: oconfig, oconfig: oconfig,
dconfig: dconfig, dconfig: dconfig,
created: created, created: created,
createdBy: i.ImageCreatedBy, createdBy: c.ImageCreatedBy,
annotations: i.ImageAnnotations, annotations: c.ImageAnnotations,
preferredManifestType: manifestType, preferredManifestType: manifestType,
exporting: true, exporting: true,
} }
return ref, nil return ref, nil
} }
func importImagePushDataFromImage(store storage.Store, img *storage.Image, systemContext *types.SystemContext) (*imagePushData, error) {
manifest := []byte{}
config := []byte{}
imageName := ""
if img.ID != "" {
ref, err := is.Transport.ParseStoreReference(store, "@"+img.ID)
if err != nil {
return nil, errors.Wrapf(err, "no such image %q", "@"+img.ID)
}
src, err2 := ref.NewImage(systemContext)
if err2 != nil {
return nil, errors.Wrapf(err2, "error reading image configuration")
}
defer src.Close()
config, err = src.ConfigBlob()
if err != nil {
return nil, errors.Wrapf(err, "error reading image manfest")
}
manifest, _, err = src.Manifest()
if err != nil {
return nil, errors.Wrapf(err, "error reading image manifest")
}
if len(img.Names) > 0 {
imageName = img.Names[0]
}
}
ipd := &imagePushData{
store: store,
FromImage: imageName,
FromImageID: img.ID,
Config: config,
Manifest: manifest,
ImageAnnotations: map[string]string{},
ImageCreatedBy: "",
}
ipd.initConfig()
return ipd, nil
}

View file

@ -11,7 +11,7 @@ import (
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
) )
// TypeLayers github.com/moby/moby/image/rootfs.go // TypeLayers github.com/docker/docker/image/rootfs.go
const TypeLayers = "layers" const TypeLayers = "layers"
// V2S2MediaTypeManifest github.com/docker/distribution/manifest/schema2/manifest.go // V2S2MediaTypeManifest github.com/docker/distribution/manifest/schema2/manifest.go
@ -29,14 +29,14 @@ const V2S2MediaTypeUncompressedLayer = "application/vnd.docker.image.rootfs.diff
// V2S2RootFS describes images root filesystem // V2S2RootFS describes images root filesystem
// This is currently a placeholder that only supports layers. In the future // This is currently a placeholder that only supports layers. In the future
// this can be made into an interface that supports different implementations. // this can be made into an interface that supports different implementations.
// github.com/moby/moby/image/rootfs.go // github.com/docker/docker/image/rootfs.go
type V2S2RootFS struct { type V2S2RootFS struct {
Type string `json:"type"` Type string `json:"type"`
DiffIDs []digest.Digest `json:"diff_ids,omitempty"` DiffIDs []digest.Digest `json:"diff_ids,omitempty"`
} }
// V2S2History stores build commands that were used to create an image // V2S2History stores build commands that were used to create an image
// github.com/moby/moby/image/image.go // github.com/docker/docker/image/image.go
type V2S2History struct { type V2S2History struct {
// Created is the timestamp at which the image was created // Created is the timestamp at which the image was created
Created time.Time `json:"created"` Created time.Time `json:"created"`
@ -53,11 +53,11 @@ type V2S2History struct {
} }
// ID is the content-addressable ID of an image. // ID is the content-addressable ID of an image.
// github.com/moby/moby/image/image.go // github.com/docker/docker/image/image.go
type ID digest.Digest type ID digest.Digest
// HealthConfig holds configuration settings for the HEALTHCHECK feature. // HealthConfig holds configuration settings for the HEALTHCHECK feature.
// github.com/moby/moby/api/types/container/config.go // github.com/docker/docker/api/types/container/config.go
type HealthConfig struct { type HealthConfig struct {
// Test is the test to perform to check that the container is healthy. // Test is the test to perform to check that the container is healthy.
// An empty slice means to inherit the default. // An empty slice means to inherit the default.
@ -91,7 +91,7 @@ type Port string
// Non-portable information *should* appear in HostConfig. // Non-portable information *should* appear in HostConfig.
// All fields added to this struct must be marked `omitempty` to keep getting // All fields added to this struct must be marked `omitempty` to keep getting
// predictable hashes from the old `v1Compatibility` configuration. // predictable hashes from the old `v1Compatibility` configuration.
// github.com/moby/moby/api/types/container/config.go // github.com/docker/docker/api/types/container/config.go
type Config struct { type Config struct {
Hostname string // Hostname Hostname string // Hostname
Domainname string // Domainname Domainname string // Domainname
@ -137,7 +137,7 @@ type V1Compatibility struct {
} }
// V1Image stores the V1 image configuration. // V1Image stores the V1 image configuration.
// github.com/moby/moby/image/image.go // github.com/docker/docker/image/image.go
type V1Image struct { type V1Image struct {
// ID is a unique 64 character identifier of the image // ID is a unique 64 character identifier of the image
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
@ -166,7 +166,7 @@ type V1Image struct {
} }
// V2Image stores the image configuration // V2Image stores the image configuration
// github.com/moby/moby/image/image.go // github.com/docker/docker/image/image.go
type V2Image struct { type V2Image struct {
V1Image V1Image
Parent ID `json:"parent,omitempty"` Parent ID `json:"parent,omitempty"`

162
cmd/kpod/imageData.go Normal file
View file

@ -0,0 +1,162 @@
package main
import (
"encoding/json"
"time"
"github.com/containers/storage"
digest "github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
type imageData struct {
ID string
Names []string
Digests []digest.Digest
Parent string
Comment string
Created *time.Time
Container string
ContainerConfig containerConfig
Author string
Config ociv1.ImageConfig
Architecture string
OS string
Size uint
VirtualSize uint
GraphDriver driverData
RootFS ociv1.RootFS
}
type containerConfig struct {
Hostname string
Domainname string
User string
AttachStdin bool
AttachStdout bool
AttachStderr bool
Tty bool
OpenStdin bool
StdinOnce bool
Env []string
Cmd []string
ArgsEscaped bool
Image digest.Digest
Volumes map[string]interface{}
WorkingDir string
Entrypoint []string
Labels interface{}
OnBuild []string
}
type rootFS struct {
Type string
Layers []string
}
func getImageData(store storage.Store, name string) (*imageData, error) {
img, err := findImage(store, name)
if err != nil {
return nil, errors.Wrapf(err, "error reading image %q", name)
}
cid, err := openImage(store, name)
if err != nil {
return nil, errors.Wrapf(err, "error reading image %q", name)
}
digests, err := getDigests(*img)
if err != nil {
return nil, err
}
var bigData interface{}
ctrConfig := containerConfig{}
container := ""
if len(digests) > 0 {
bd, err := store.ImageBigData(img.ID, string(digests[len(digests)-1]))
if err != nil {
return nil, err
}
err = json.Unmarshal(bd, &bigData)
if err != nil {
return nil, err
}
container = (bigData.(map[string]interface{})["container"]).(string)
cc, err := json.MarshalIndent((bigData.(map[string]interface{})["container_config"]).(map[string]interface{}), "", " ")
if err != nil {
return nil, err
}
err = json.Unmarshal(cc, &ctrConfig)
if err != nil {
return nil, err
}
}
driverName, err := getDriverName(store)
if err != nil {
return nil, err
}
topLayerID, err := getImageTopLayer(*img)
if err != nil {
return nil, err
}
driverMetadata, err := getDriverMetadata(store, topLayerID)
if err != nil {
return nil, err
}
lstore, err := store.LayerStore()
if err != nil {
return nil, err
}
layer, err := lstore.Get(topLayerID)
if err != nil {
return nil, err
}
size, err := lstore.DiffSize(layer.Parent, layer.ID)
if err != nil {
return nil, err
}
virtualSize, err := getImageSize(*img, store)
if err != nil {
return nil, err
}
return &imageData{
ID: img.ID,
Names: img.Names,
Digests: digests,
Parent: string(cid.Docker.Parent),
Comment: cid.OCIv1.History[0].Comment,
Created: cid.OCIv1.Created,
Container: container,
ContainerConfig: ctrConfig,
Author: cid.OCIv1.Author,
Config: cid.OCIv1.Config,
Architecture: cid.OCIv1.Architecture,
OS: cid.OCIv1.OS,
Size: uint(size),
VirtualSize: uint(virtualSize),
GraphDriver: driverData{
Name: driverName,
Data: driverMetadata,
},
RootFS: cid.OCIv1.RootFS,
}, nil
}
func getDigests(img storage.Image) ([]digest.Digest, error) {
metadata, err := parseMetadata(img)
if err != nil {
return nil, err
}
digests := []digest.Digest{}
for _, blob := range metadata.Blobs {
digests = append(digests, blob.Digest)
}
return digests, nil
}

View file

@ -1,40 +0,0 @@
package main
import (
"bytes"
"fmt"
)
// We have to compare the structs manually because they contain
// []byte variables, which cannot be compared with "=="
func compareImagePushData(a, b *imagePushData) bool {
if a.store != b.store {
fmt.Println("store")
return false
} else if a.Type != b.Type {
fmt.Println("type")
return false
} else if a.FromImage != b.FromImage {
fmt.Println("FromImage")
return false
} else if a.FromImageID != b.FromImageID {
fmt.Println("FromImageID")
return false
} else if !bytes.Equal(a.Config, b.Config) {
fmt.Println("Config")
return false
} else if !bytes.Equal(a.Manifest, b.Manifest) {
fmt.Println("Manifest")
return false
} else if fmt.Sprint(a.ImageAnnotations) != fmt.Sprint(b.ImageAnnotations) {
fmt.Println("Annotations")
return false
} else if a.ImageCreatedBy != b.ImageCreatedBy {
fmt.Println("ImageCreatedBy")
return false
} else if fmt.Sprintf("%+v", a.OCIv1) != fmt.Sprintf("%+v", b.OCIv1) {
fmt.Println("OCIv1")
return false
}
return true
}

View file

@ -133,7 +133,7 @@ func parseFilter(images []storage.Image, filter string) (*filterParams, error) {
pair := strings.SplitN(param, "=", 2) pair := strings.SplitN(param, "=", 2)
switch strings.TrimSpace(pair[0]) { switch strings.TrimSpace(pair[0]) {
case "dangling": case "dangling":
if pair[1] == "true" || pair[1] == "false" { if isValidBool(pair[1]) {
params.dangling = pair[1] params.dangling = pair[1]
} else { } else {
return nil, fmt.Errorf("invalid filter: '%s=[%s]'", pair[0], pair[1]) return nil, fmt.Errorf("invalid filter: '%s=[%s]'", pair[0], pair[1])
@ -200,7 +200,7 @@ func outputImages(images []storage.Image, format string, store storage.Store, fi
if len(imageMetadata.Blobs) > 0 { if len(imageMetadata.Blobs) > 0 {
digest = string(imageMetadata.Blobs[0].Digest) digest = string(imageMetadata.Blobs[0].Digest)
} }
size, _ := getSize(image, store) size, _ := getImageSize(image, store)
names := []string{""} names := []string{""}
if len(image.Names) > 0 { if len(image.Names) > 0 {
@ -259,9 +259,9 @@ func matchesFilter(image storage.Image, store storage.Store, name string, params
} }
func matchesDangling(name string, dangling string) bool { func matchesDangling(name string, dangling string) bool {
if dangling == "false" && name != "<none>" { if isFalse(dangling) && name != "<none>" {
return true return true
} else if dangling == "true" && name == "<none>" { } else if isTrue(dangling) && name == "<none>" {
return true return true
} }
return false return false

123
cmd/kpod/inspect.go Normal file
View file

@ -0,0 +1,123 @@
package main
import (
"encoding/json"
"fmt"
"os"
"text/template"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
const (
defaultFormat = `Container: {{.Container}}
ID: {{.ContainerID}}
`
inspectTypeContainer = "container"
inspectTypeImage = "image"
inspectAll = "all"
)
var (
inspectFlags = []cli.Flag{
cli.StringFlag{
Name: "type, t",
Value: inspectAll,
Usage: "Return JSON for specified type, (e.g image, container or task)",
},
cli.StringFlag{
Name: "format, f",
Value: defaultFormat,
Usage: "Format the output using the given go template",
},
cli.BoolFlag{
Name: "size",
Usage: "Display total file size if the type is container",
},
}
inspectDescription = "This displays the low-level information on containers and images identified by name or ID. By default, this will render all results in a JSON array. If the container and image have the same name, this will return container JSON for unspecified type."
inspectCommand = cli.Command{
Name: "inspect",
Usage: "Displays the configuration of a container or image",
Description: inspectDescription,
Flags: inspectFlags,
Action: inspectCmd,
ArgsUsage: "CONTAINER-OR-IMAGE",
}
)
func inspectCmd(c *cli.Context) error {
args := c.Args()
if len(args) == 0 {
return errors.Errorf("container or image name must be specified: kpod inspect [options [...]] name")
}
if len(args) > 1 {
return errors.Errorf("too many arguments specified")
}
itemType := c.String("type")
size := c.Bool("size")
format := defaultFormat
if c.String("format") != "" {
format = c.String("format")
}
switch itemType {
case inspectTypeContainer:
case inspectTypeImage:
case inspectAll:
default:
return errors.Errorf("the only recognized types are %q, %q, and %q", inspectTypeContainer, inspectTypeImage, inspectAll)
}
t := template.Must(template.New("format").Parse(format))
name := args[0]
store, err := getStore(c)
if err != nil {
return err
}
var data interface{}
switch itemType {
case inspectTypeContainer:
data, err = getContainerData(store, name, size)
if err != nil {
return errors.Wrapf(err, "error parsing container data")
}
case inspectTypeImage:
data, err = getImageData(store, name)
if err != nil {
return errors.Wrapf(err, "error parsing image data")
}
case inspectAll:
ctrData, err := getContainerData(store, name, size)
if err != nil {
imgData, err := getImageData(store, name)
if err != nil {
return errors.Wrapf(err, "error parsing image data")
}
data = imgData
} else {
data = ctrData
}
}
if c.IsSet("format") {
if err = t.Execute(os.Stdout, data); err != nil {
return err
}
fmt.Println()
return nil
}
d, err := json.MarshalIndent(data, "", " ")
if err != nil {
return errors.Wrapf(err, "error encoding build container as json")
}
_, err = fmt.Println(string(d))
return err
}

View file

@ -25,6 +25,7 @@ func main() {
historyCommand, historyCommand,
imagesCommand, imagesCommand,
infoCommand, infoCommand,
inspectCommand,
pullCommand, pullCommand,
pushCommand, pushCommand,
rmiCommand, rmiCommand,

View file

@ -171,16 +171,16 @@ func pushImage(srcName, destName string, options pushOptions) error {
return errors.Wrapf(err, "error locating image %q for importing settings", srcName) return errors.Wrapf(err, "error locating image %q for importing settings", srcName)
} }
systemContext := getSystemContext(options.SignaturePolicyPath) systemContext := getSystemContext(options.SignaturePolicyPath)
ipd, err := importImagePushDataFromImage(options.Store, img, systemContext) cid, err := importContainerImageDataFromImage(options.Store, systemContext, img.ID, "", "")
if err != nil { if err != nil {
return err return err
} }
// Give the image we're producing the same ancestors as its source image // Give the image we're producing the same ancestors as its source image
ipd.FromImage = ipd.Docker.ContainerConfig.Image cid.FromImage = cid.Docker.ContainerConfig.Image
ipd.FromImageID = string(ipd.Docker.Parent) cid.FromImageID = string(cid.Docker.Parent)
// Prep the layers and manifest for export // Prep the layers and manifest for export
src, err := ipd.makeImageRef(manifest.GuessMIMEType(ipd.Manifest), options.Compression, img.Names, img.TopLayer, nil) src, err := cid.makeImageRef(manifest.GuessMIMEType(cid.Manifest), options.Compression, img.Names, img.TopLayer, nil)
if err != nil { if err != nil {
return errors.Wrapf(err, "error copying layers and metadata") return errors.Wrapf(err, "error copying layers and metadata")
} }

View file

@ -1,72 +0,0 @@
package main
import (
"os/user"
"testing"
is "github.com/containers/image/storage"
)
func TestImportImagePushDataFromImage(t *testing.T) {
u, err := user.Current()
if err != nil {
t.Log("Could not determine user. Running as root may cause tests to fail")
} else if u.Uid != "0" {
t.Fatal("tests will fail unless run as root")
}
// Get Store
store, err := getStoreForTests()
if err != nil {
t.Fatalf("could not get store: %q", err)
}
// Pull an image and save it to the store
testImageName := "docker.io/library/busybox:1.26"
err = pullTestImage(testImageName)
if err != nil {
t.Fatalf("could not pull test image: %q", err)
}
img, err := findImage(store, testImageName)
if err != nil {
t.Fatalf("could not find image in store: %q", err)
}
// Get System Context
systemContext := getSystemContext("")
// Call importImagePushDataFromImage
ipd, err := importImagePushDataFromImage(store, img, systemContext)
if err != nil {
t.Fatalf("could not get ImagePushData: %q", err)
}
// Get ref and from it, get the config and the manifest
ref, err := is.Transport.ParseStoreReference(store, "@"+img.ID)
if err != nil {
t.Fatalf("no such image %q", "@"+img.ID)
}
src, err := ref.NewImage(systemContext)
if err != nil {
t.Fatalf("error creating new image from system context: %q", err)
}
defer src.Close()
config, err := src.ConfigBlob()
if err != nil {
t.Fatalf("error reading image config: %q", err)
}
manifest, _, err := src.Manifest()
if err != nil {
t.Fatalf("error reading image manifest: %q", err)
}
//Create "expected" ipd struct
expectedIpd := &imagePushData{
store: store,
FromImage: testImageName,
FromImageID: img.ID,
Config: config,
Manifest: manifest,
ImageAnnotations: map[string]string{},
ImageCreatedBy: "",
}
expectedIpd.initConfig()
//Compare structs, error if they are not the same
if !compareImagePushData(ipd, expectedIpd) {
t.Errorf("imagePushData did not match expected imagePushData")
}
}

View file

@ -6,6 +6,28 @@ __kpod_list_images() {
COMPREPLY=($(compgen -W "$(kpod images -q)" -- $cur)) COMPREPLY=($(compgen -W "$(kpod images -q)" -- $cur))
} }
_kpod_history() {
local options_with_args="
--format
"
local boolean_options="
--human -H
--no-trunc
--quiet -q
--json
"
_complete_ "$options_with_args" "$boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__kpod_list_images
;;
esac
}
_kpod_images() { _kpod_images() {
local boolean_options=" local boolean_options="
--help --help
@ -125,28 +147,6 @@ _complete_() {
esac esac
} }
_kpod_history() {
local options_with_args="
--format
"
local boolean_options="
--human -H
--no-trunc
--quiet -q
--json
"
_complete_ "$options_with_args" "$boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__kpod_list_images
;;
esac
}
_kpod_kpod() { _kpod_kpod() {
local options_with_args=" local options_with_args="
" "

169
docs/kpod-inspect.1.md Normal file
View file

@ -0,0 +1,169 @@
## kpod-inspect "1" "July 2017" "kpod"
## NAME
kpod inspect - display a container or image's configuration
## SYNOPSIS
**kpod** **inspect** [*options* [...]] name
## DESCRIPTION
This displays the low-level information on containers and images identified by name or ID. By default, this will render all results in a JSON array. If the container and image have the same name, this will return container JSON for unspecified type. If a format is specified, the given template will be executed for each result.
## OPTIONS
**--type, t="TYPE"**
Return data on items of the specified type. Type can be 'container', 'image' or 'all' (default: all)
**--format, -f="FORMAT"**
Format the output using the given Go template
**--size**
Display the total file size if the type is a container
## EXAMPLE
kpod inspect redis:alpine
{
"ArgsEscaped": true,
"AttachStderr": false,
"AttachStdin": false,
"AttachStdout": false,
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"redis-server\"]"
],
"Domainname": "",
"Entrypoint": [
"entrypoint.sh"
],
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"REDIS_VERSION=3.2.9",
"REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-3.2.9.tar.gz",
"REDIS_DOWNLOAD_SHA=6eaacfa983b287e440d0839ead20c2231749d5d6b78bbe0e0ffa3a890c59ff26"
],
"ExposedPorts": {
"6379/tcp": {}
},
"Hostname": "e1ede117fb1e",
"Image": "sha256:75e877aa15b534396de82d385386cc4dda7819d5cbb018b9f97b77aeb8f4b55a",
"Labels": {},
"OnBuild": [],
"OpenStdin": false,
"StdinOnce": false,
"Tty": false,
"User": "",
"Volumes": {
"/data": {}
},
"WorkingDir": "/data"
}
{
"ID": "b3f2436bdb978c1d33b1387afb5d7ba7e3243ed2ce908db431ac0069da86cb45",
"Names": [
"docker.io/library/redis:alpine"
],
"Digests": [
"sha256:88286f41530e93dffd4b964e1db22ce4939fffa4a4c665dab8591fbab03d4926",
"sha256:07b1ac6c7a5068201d8b63a09bb15358ec1616b813ef3942eb8cc12ae191227f",
"sha256:91e2e140ea27b3e89f359cd9fab4ec45647dda2a8e5fb0c78633217d9dca87b5",
"sha256:08957ceaa2b3be874cde8d7fa15c274300f47185acd62bca812a2ffb6228482d",
"sha256:acd3d12a6a79f772961a771f678c1a39e1f370e7baeb9e606ad8f1b92572f4ab",
"sha256:4ad88df090801e8faa8cf0be1f403b77613d13e11dad73f561461d482f79256c",
"sha256:159ac12c79e1a8d85dfe61afff8c64b96881719139730012a9697f432d6b739a"
],
"Parent": "",
"Comment": "",
"Created": "2017-06-28T22:14:36.35280993Z",
"Container": "ba8d6c6b0d7fdd201fce404236136b44f3bfdda883466531a3d1a1f87906770b",
"ContainerConfig": {
"Hostname": "e1ede117fb1e",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"REDIS_VERSION=3.2.9",
"REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-3.2.9.tar.gz",
"REDIS_DOWNLOAD_SHA=6eaacfa983b287e440d0839ead20c2231749d5d6b78bbe0e0ffa3a890c59ff26"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"redis-server\"]"
],
"ArgsEscaped": true,
"Image": "sha256:75e877aa15b534396de82d385386cc4dda7819d5cbb018b9f97b77aeb8f4b55a",
"Volumes": {
"/data": {}
},
"WorkingDir": "/data",
"Entrypoint": [
"entrypoint.sh"
],
"Labels": {},
"OnBuild": []
},
"Author": "",
"Config": {
"ExposedPorts": {
"6379/tcp": {}
},
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"REDIS_VERSION=3.2.9",
"REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-3.2.9.tar.gz",
"REDIS_DOWNLOAD_SHA=6eaacfa983b287e440d0839ead20c2231749d5d6b78bbe0e0ffa3a890c59ff26"
],
"Entrypoint": [
"entrypoint.sh"
],
"Cmd": [
"redis-server"
],
"Volumes": {
"/data": {}
},
"WorkingDir": "/data"
},
"Architecture": "amd64",
"OS": "linux",
"Size": 3965955,
"VirtualSize": 19808086,
"GraphDriver": {
"Name": "overlay2",
"Data": {
"MergedDir": "/var/lib/containers/storage/overlay2/2059d805c90e034cb773d9722232ef018a72143dd31113b470fb876baeccd700/merged",
"UpperDir": "/var/lib/containers/storage/overlay2/2059d805c90e034cb773d9722232ef018a72143dd31113b470fb876baeccd700/diff",
"WorkDir": "/var/lib/containers/storage/overlay2/2059d805c90e034cb773d9722232ef018a72143dd31113b470fb876baeccd700/work"
}
},
"RootFS": {
"type": "layers",
"diff_ids": [
"sha256:5bef08742407efd622d243692b79ba0055383bbce12900324f75e56f589aedb0",
"sha256:c92a8fc997217611d0bfc9ff14d7ec00350ca564aef0ecbf726624561d7872d7",
"sha256:d4c406dea37a107b0cccb845611266a146725598be3e82ba31c55c08d1583b5a",
"sha256:8b4fa064e2b6c03a6c37089b0203f167375a8b49259c0ad7cb47c8c1e58b3fa0",
"sha256:c393e3d0b00ddf6b4166f1e2ad68245e08e9e3be0a0567a36d0a43854f03bfd6",
"sha256:38047b4117cb8bb3bba82991daf9a4e14ba01f9f66c1434d4895a7e96f67d8ba"
]
}
}
## SEE ALSO
kpod(1)

View file

@ -177,3 +177,10 @@ func (c *Container) NetNsPath() (string, error) {
func (c *Container) Metadata() *pb.ContainerMetadata { func (c *Container) Metadata() *pb.ContainerMetadata {
return c.metadata return c.metadata
} }
// State returns the state of the running container
func (c *Container) State() *ContainerState {
c.opLock.Lock()
defer c.opLock.Unlock()
return c.state
}

View file

@ -190,3 +190,39 @@ function teardown() {
run crioctl image remove "$IMAGE" run crioctl image remove "$IMAGE"
stop_crio stop_crio
} }
@test "kpod inspect image" {
run ${KPOD_BINARY} $KPOD_OPTIONS pull redis:alpine
[ "$status" -eq 0 ]
run bash -c "${KPOD_BINARY} $KPOD_OPTIONS inspect redis:alpine | python -m json.tool"
echo "$output"
[ "$status" -eq 0 ]
}
run ${KPOD_BINARY} $KPOD_OPTIONS rmi redis:alpine
@test "kpod inspect non-existent container" {
run ${KPOD_BINARY} $KPOD_OPTIONS inspect 14rcole/non-existent
echo "$output"
[ "$status" -ne 0 ]
}
@test "kpod inspect with format" {
run ${KPOD_BINARY} $KPOD_OPTIONS pull redis:alpine
[ "$status" -eq 0 ]
run ${KPOD_BINARY} $KPOD_OPTIONS --format {{.ID}} inspect redis:alpine
[ "$status" -eq 0]
inspectOutput="$output"
run ${KPOD_BINARY} $KPOD_OPTIONS images --quiet redis:alpine
[ "$status" -eq 0]
[ "$output" -eq "$inspectOutput" ]
run ${KPOD_BINARY} $KPOD_OPTIONS rmi redis:alpine
}
@test "kpod inspect specified type" {
run ${KPOD_BINARY} $KPOD_OPTIONS pull redis:alpine
[ "$status" -eq 0 ]
run bash -c "${KPOD_BINARY} $KPOD_OPTIONS inspect --type image redis:alpine | python -m json.tool"
echo "$output"
[ "$status" -eq 0]
run ${KPOD_BINARY} $KPOD_OPTIONS rmi redis:alpine
}