Merge pull request #648 from 14rcole/kpod-inspect
Implement `kpod inspect`
This commit is contained in:
commit
0e17bf4659
17 changed files with 1180 additions and 318 deletions
|
@ -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-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-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-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 |
|
||||
|
|
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -78,6 +77,35 @@ func getStore(c *cli.Context) (storage.Store, error) {
|
|||
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 {
|
||||
if srcDockerRegistry == nil {
|
||||
srcDockerRegistry = &dockerRegistryOptions{}
|
||||
|
@ -96,33 +124,20 @@ func getCopyOptions(reportWriter io.Writer, signaturePolicyPath string, srcDocke
|
|||
}
|
||||
}
|
||||
|
||||
func getPolicyContext(path string) (*signature.PolicyContext, error) {
|
||||
policy, err := signature.DefaultPolicy(&types.SystemContext{SignaturePolicyPath: path})
|
||||
func findContainer(store storage.Store, container string) (*storage.Container, error) {
|
||||
ctrStore, err := store.ContainerStore()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signature.NewPolicyContext(policy)
|
||||
return ctrStore.Get(container)
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
return img, nil
|
||||
func getContainerTopLayerID(store storage.Store, containerID string) (string, error) {
|
||||
ctr, err := findContainer(store, containerID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
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
|
||||
return ctr.LayerID, nil
|
||||
}
|
||||
|
||||
func getSystemContext(signaturePolicyPath string) *types.SystemContext {
|
||||
|
@ -133,37 +148,6 @@ func getSystemContext(signaturePolicyPath string) *types.SystemContext {
|
|||
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 {
|
||||
n := map[string]string{}
|
||||
for k, v := range m {
|
||||
|
@ -172,16 +156,140 @@ func copyStringStringMap(m map[string]string) map[string]string {
|
|||
return n
|
||||
}
|
||||
|
||||
func (o dockerRegistryOptions) getSystemContext(signaturePolicyPath string) *types.SystemContext {
|
||||
sc := &types.SystemContext{
|
||||
SignaturePolicyPath: signaturePolicyPath,
|
||||
DockerAuthConfig: o.DockerRegistryCreds,
|
||||
DockerCertPath: o.DockerCertPath,
|
||||
DockerInsecureSkipTLSVerify: o.DockerInsecureSkipTLSVerify,
|
||||
// A container FS is split into two parts. The first is the top layer, a
|
||||
// mutable layer, and the rest is the RootFS: the set of immutable layers
|
||||
// that make up the image on which the container is based
|
||||
func getRootFsSize(store storage.Store, containerID string) (int64, error) {
|
||||
ctrStore, err := store.ContainerStore()
|
||||
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) {
|
||||
if creds == "" {
|
||||
return nil, errors.New("no credentials supplied")
|
||||
|
@ -196,3 +304,13 @@ func parseRegistryCreds(creds string) (*types.DockerAuthConfig, error) {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ func TestGetSize(t *testing.T) {
|
|||
t.Fatalf("Error reading images: %v", err)
|
||||
}
|
||||
|
||||
_, err = getSize(images[0], store)
|
||||
_, err = getImageSize(images[0], store)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
|
219
cmd/kpod/containerData.go
Normal file
219
cmd/kpod/containerData.go
Normal 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)
|
||||
}
|
|
@ -3,6 +3,8 @@ package main
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
@ -12,7 +14,8 @@ import (
|
|||
"github.com/containers/image/types"
|
||||
"github.com/containers/storage"
|
||||
"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"
|
||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
@ -20,142 +23,159 @@ import (
|
|||
)
|
||||
|
||||
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,
|
||||
// suitable for specifying as a value of the PreferredManifestType
|
||||
// member of a CommitOptions structure. It is also the default.
|
||||
OCIv1ImageManifest = v1.MediaTypeImageManifest
|
||||
)
|
||||
|
||||
type imagePushData struct {
|
||||
type containerImageData struct {
|
||||
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"`
|
||||
// FromImage is the name of the source image which ws used to create
|
||||
// the container, if one was used
|
||||
// FromImage is the name of the source image which was used to create
|
||||
// the container, if one was used. It should not be modified.
|
||||
FromImage string `json:"image,omitempty"`
|
||||
// FromImageID is the id of the source image
|
||||
FromImageID string `json:"imageid"`
|
||||
// Config is the source image's configuration
|
||||
// FromImageID is the ID of the source image which was used to create
|
||||
// the container, if one was used. It should not be modified.
|
||||
FromImageID string `json:"image-id"`
|
||||
// Config is the source image's configuration. It should not be
|
||||
// modified.
|
||||
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"`
|
||||
|
||||
// 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
|
||||
// image's manifest
|
||||
// image's manifest.
|
||||
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"`
|
||||
|
||||
// Image metadata and runtime settings, in multiple formats
|
||||
OCIv1 ociv1.Image `json:"ociv1,omitempty"`
|
||||
// Image metadata and runtime settings, in multiple formats.
|
||||
OCIv1 v1.Image `json:"ociv1,omitempty"`
|
||||
Docker docker.V2Image `json:"docker,omitempty"`
|
||||
}
|
||||
|
||||
func (i *imagePushData) initConfig() {
|
||||
func (c *containerImageData) initConfig() {
|
||||
image := ociv1.Image{}
|
||||
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
|
||||
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)
|
||||
if err != nil {
|
||||
image = ociv1.Image{}
|
||||
}
|
||||
} 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 {
|
||||
dimage = docker.V2Image{}
|
||||
}
|
||||
}
|
||||
}
|
||||
i.OCIv1 = image
|
||||
i.Docker = dimage
|
||||
c.OCIv1 = image
|
||||
c.Docker = dimage
|
||||
} else {
|
||||
// Try to dig out the image configuration from the manifest
|
||||
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 image, err = makeOCIv1Image(&dimage); err != nil {
|
||||
image = ociv1.Image{}
|
||||
}
|
||||
}
|
||||
}
|
||||
i.OCIv1 = image
|
||||
i.Docker = dimage
|
||||
c.OCIv1 = image
|
||||
c.Docker = dimage
|
||||
}
|
||||
|
||||
if len(i.Manifest) > 0 {
|
||||
if len(c.Manifest) > 0 {
|
||||
// Attempt to recover format-specific data from the manifest
|
||||
v1Manifest := ociv1.Manifest{}
|
||||
if json.Unmarshal(i.Manifest, &v1Manifest) == nil {
|
||||
i.ImageAnnotations = v1Manifest.Annotations
|
||||
if json.Unmarshal(c.Manifest, &v1Manifest) == nil {
|
||||
c.ImageAnnotations = v1Manifest.Annotations
|
||||
}
|
||||
}
|
||||
|
||||
i.fixupConfig()
|
||||
c.fixupConfig()
|
||||
}
|
||||
|
||||
func (i *imagePushData) fixupConfig() {
|
||||
if i.Docker.Config != nil {
|
||||
func (c *containerImageData) fixupConfig() {
|
||||
if c.Docker.Config != nil {
|
||||
// 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
|
||||
i.Docker.DockerVersion = ""
|
||||
c.Docker.Config = &c.Docker.ContainerConfig
|
||||
c.Docker.DockerVersion = ""
|
||||
now := time.Now().UTC()
|
||||
if i.Docker.Created.IsZero() {
|
||||
i.Docker.Created = now
|
||||
if c.Docker.Created.IsZero() {
|
||||
c.Docker.Created = now
|
||||
}
|
||||
if i.OCIv1.Created.IsZero() {
|
||||
i.OCIv1.Created = &now
|
||||
if c.OCIv1.Created.IsZero() {
|
||||
c.OCIv1.Created = &now
|
||||
}
|
||||
if i.OS() == "" {
|
||||
i.SetOS(runtime.GOOS)
|
||||
if c.OS() == "" {
|
||||
c.SetOS(runtime.GOOS)
|
||||
}
|
||||
if i.Architecture() == "" {
|
||||
i.SetArchitecture(runtime.GOARCH)
|
||||
if c.Architecture() == "" {
|
||||
c.SetArchitecture(runtime.GOARCH)
|
||||
}
|
||||
if i.WorkDir() == "" {
|
||||
i.SetWorkDir(string(filepath.Separator))
|
||||
if c.WorkDir() == "" {
|
||||
c.SetWorkDir(string(filepath.Separator))
|
||||
}
|
||||
}
|
||||
|
||||
// OS returns a name of the OS on which a container built using this image
|
||||
//is intended to be run.
|
||||
func (i *imagePushData) OS() string {
|
||||
return i.OCIv1.OS
|
||||
func (c *containerImageData) OS() string {
|
||||
return c.OCIv1.OS
|
||||
}
|
||||
|
||||
// SetOS sets the name of the OS on which a container built using this image
|
||||
// is intended to be run.
|
||||
func (i *imagePushData) SetOS(os string) {
|
||||
i.OCIv1.OS = os
|
||||
i.Docker.OS = os
|
||||
func (c *containerImageData) SetOS(os string) {
|
||||
c.OCIv1.OS = os
|
||||
c.Docker.OS = os
|
||||
}
|
||||
|
||||
// Architecture returns a name of the architecture on which a container built
|
||||
// using this image is intended to be run.
|
||||
func (i *imagePushData) Architecture() string {
|
||||
return i.OCIv1.Architecture
|
||||
func (c *containerImageData) Architecture() string {
|
||||
return c.OCIv1.Architecture
|
||||
}
|
||||
|
||||
// SetArchitecture sets the name of the architecture on which ta container built
|
||||
// using this image is intended to be run.
|
||||
func (i *imagePushData) SetArchitecture(arch string) {
|
||||
i.OCIv1.Architecture = arch
|
||||
i.Docker.Architecture = arch
|
||||
func (c *containerImageData) SetArchitecture(arch string) {
|
||||
c.OCIv1.Architecture = arch
|
||||
c.Docker.Architecture = arch
|
||||
}
|
||||
|
||||
// WorkDir returns the default working directory for running commands in a container
|
||||
// built using this image.
|
||||
func (i *imagePushData) WorkDir() string {
|
||||
return i.OCIv1.Config.WorkingDir
|
||||
func (c *containerImageData) WorkDir() string {
|
||||
return c.OCIv1.Config.WorkingDir
|
||||
}
|
||||
|
||||
// SetWorkDir sets the location of the default working directory for running commands
|
||||
// in a container built using this image.
|
||||
func (i *imagePushData) SetWorkDir(there string) {
|
||||
i.OCIv1.Config.WorkingDir = there
|
||||
i.Docker.Config.WorkingDir = there
|
||||
func (c *containerImageData) SetWorkDir(there string) {
|
||||
c.OCIv1.Config.WorkingDir = there
|
||||
c.Docker.Config.WorkingDir = there
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (i *imagePushData) Annotations() map[string]string {
|
||||
return copyStringStringMap(i.ImageAnnotations)
|
||||
func (c *containerImageData) Annotations() map[string]string {
|
||||
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
|
||||
if len(names) > 0 {
|
||||
if parsed, err := reference.ParseNamed(names[0]); err == nil {
|
||||
|
@ -332,11 +513,11 @@ func (i *imagePushData) makeImageRef(manifestType string, compress archive.Compr
|
|||
if manifestType == "" {
|
||||
manifestType = OCIv1ImageManifest
|
||||
}
|
||||
oconfig, err := json.Marshal(&i.OCIv1)
|
||||
oconfig, err := json.Marshal(&c.OCIv1)
|
||||
if err != nil {
|
||||
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 {
|
||||
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()
|
||||
}
|
||||
ref := &containerImageRef{
|
||||
store: i.store,
|
||||
store: c.store,
|
||||
compression: compress,
|
||||
name: name,
|
||||
names: names,
|
||||
|
@ -354,53 +535,10 @@ func (i *imagePushData) makeImageRef(manifestType string, compress archive.Compr
|
|||
oconfig: oconfig,
|
||||
dconfig: dconfig,
|
||||
created: created,
|
||||
createdBy: i.ImageCreatedBy,
|
||||
annotations: i.ImageAnnotations,
|
||||
createdBy: c.ImageCreatedBy,
|
||||
annotations: c.ImageAnnotations,
|
||||
preferredManifestType: manifestType,
|
||||
exporting: true,
|
||||
}
|
||||
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
|
||||
}
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// TypeLayers github.com/moby/moby/image/rootfs.go
|
||||
// TypeLayers github.com/docker/docker/image/rootfs.go
|
||||
const TypeLayers = "layers"
|
||||
|
||||
// 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
|
||||
// This is currently a placeholder that only supports layers. In the future
|
||||
// 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 string `json:"type"`
|
||||
DiffIDs []digest.Digest `json:"diff_ids,omitempty"`
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Created is the timestamp at which the image was created
|
||||
Created time.Time `json:"created"`
|
||||
|
@ -53,11 +53,11 @@ type V2S2History struct {
|
|||
}
|
||||
|
||||
// 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
|
||||
|
||||
// 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 {
|
||||
// Test is the test to perform to check that the container is healthy.
|
||||
// An empty slice means to inherit the default.
|
||||
|
@ -91,7 +91,7 @@ type Port string
|
|||
// Non-portable information *should* appear in HostConfig.
|
||||
// All fields added to this struct must be marked `omitempty` to keep getting
|
||||
// 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 {
|
||||
Hostname string // Hostname
|
||||
Domainname string // Domainname
|
||||
|
@ -137,7 +137,7 @@ type V1Compatibility struct {
|
|||
}
|
||||
|
||||
// V1Image stores the V1 image configuration.
|
||||
// github.com/moby/moby/image/image.go
|
||||
// github.com/docker/docker/image/image.go
|
||||
type V1Image struct {
|
||||
// ID is a unique 64 character identifier of the image
|
||||
ID string `json:"id,omitempty"`
|
||||
|
@ -166,7 +166,7 @@ type V1Image struct {
|
|||
}
|
||||
|
||||
// V2Image stores the image configuration
|
||||
// github.com/moby/moby/image/image.go
|
||||
// github.com/docker/docker/image/image.go
|
||||
type V2Image struct {
|
||||
V1Image
|
||||
Parent ID `json:"parent,omitempty"`
|
||||
|
|
162
cmd/kpod/imageData.go
Normal file
162
cmd/kpod/imageData.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -133,7 +133,7 @@ func parseFilter(images []storage.Image, filter string) (*filterParams, error) {
|
|||
pair := strings.SplitN(param, "=", 2)
|
||||
switch strings.TrimSpace(pair[0]) {
|
||||
case "dangling":
|
||||
if pair[1] == "true" || pair[1] == "false" {
|
||||
if isValidBool(pair[1]) {
|
||||
params.dangling = pair[1]
|
||||
} else {
|
||||
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 {
|
||||
digest = string(imageMetadata.Blobs[0].Digest)
|
||||
}
|
||||
size, _ := getSize(image, store)
|
||||
size, _ := getImageSize(image, store)
|
||||
|
||||
names := []string{""}
|
||||
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 {
|
||||
if dangling == "false" && name != "<none>" {
|
||||
if isFalse(dangling) && name != "<none>" {
|
||||
return true
|
||||
} else if dangling == "true" && name == "<none>" {
|
||||
} else if isTrue(dangling) && name == "<none>" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
|
123
cmd/kpod/inspect.go
Normal file
123
cmd/kpod/inspect.go
Normal 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
|
||||
}
|
|
@ -25,6 +25,7 @@ func main() {
|
|||
historyCommand,
|
||||
imagesCommand,
|
||||
infoCommand,
|
||||
inspectCommand,
|
||||
pullCommand,
|
||||
pushCommand,
|
||||
rmiCommand,
|
||||
|
|
|
@ -171,16 +171,16 @@ func pushImage(srcName, destName string, options pushOptions) error {
|
|||
return errors.Wrapf(err, "error locating image %q for importing settings", srcName)
|
||||
}
|
||||
systemContext := getSystemContext(options.SignaturePolicyPath)
|
||||
ipd, err := importImagePushDataFromImage(options.Store, img, systemContext)
|
||||
cid, err := importContainerImageDataFromImage(options.Store, systemContext, img.ID, "", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Give the image we're producing the same ancestors as its source image
|
||||
ipd.FromImage = ipd.Docker.ContainerConfig.Image
|
||||
ipd.FromImageID = string(ipd.Docker.Parent)
|
||||
cid.FromImage = cid.Docker.ContainerConfig.Image
|
||||
cid.FromImageID = string(cid.Docker.Parent)
|
||||
|
||||
// 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 {
|
||||
return errors.Wrapf(err, "error copying layers and metadata")
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -6,6 +6,28 @@ __kpod_list_images() {
|
|||
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() {
|
||||
local boolean_options="
|
||||
--help
|
||||
|
@ -125,28 +147,6 @@ _complete_() {
|
|||
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() {
|
||||
local options_with_args="
|
||||
"
|
||||
|
|
169
docs/kpod-inspect.1.md
Normal file
169
docs/kpod-inspect.1.md
Normal 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)
|
|
@ -177,3 +177,10 @@ func (c *Container) NetNsPath() (string, error) {
|
|||
func (c *Container) Metadata() *pb.ContainerMetadata {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -190,3 +190,39 @@ function teardown() {
|
|||
run crioctl image remove "$IMAGE"
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue