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(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 |
|
||||||
|
|
|
@ -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 {
|
||||||
if err == nil {
|
return "", err
|
||||||
img, err := is.Transport.GetStoreImage(store, ref)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return img, nil
|
|
||||||
}
|
}
|
||||||
img2, err2 := store.Image(image)
|
return ctr.LayerID, nil
|
||||||
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
|
||||||
|
}
|
||||||
|
|
|
@ -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
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 (
|
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
|
|
||||||
}
|
|
|
@ -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
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)
|
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
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,
|
historyCommand,
|
||||||
imagesCommand,
|
imagesCommand,
|
||||||
infoCommand,
|
infoCommand,
|
||||||
|
inspectCommand,
|
||||||
pullCommand,
|
pullCommand,
|
||||||
pushCommand,
|
pushCommand,
|
||||||
rmiCommand,
|
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)
|
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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
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
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 {
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue