commit
2bdcd460bb
6 changed files with 238 additions and 0 deletions
89
cmd/kpod/diff.go
Normal file
89
cmd/kpod/diff.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/kubernetes-incubator/cri-o/libkpod"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type diffJSONOutput struct {
|
||||
Changed []string `json:"changed,omitempty"`
|
||||
Added []string `json:"added,omitempty"`
|
||||
Deleted []string `json:"deleted,omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
diffFlags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "archive",
|
||||
Usage: "Save the diff as a tar archive",
|
||||
Hidden: true,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "json",
|
||||
Usage: "Format output as JSON",
|
||||
},
|
||||
}
|
||||
diffDescription = fmt.Sprint(`Displays changes on a container or image's filesystem. The
|
||||
container or image will be compared to its parent layer`)
|
||||
|
||||
diffCommand = cli.Command{
|
||||
Name: "diff",
|
||||
Usage: "Inspect changes on container's file systems",
|
||||
Description: diffDescription,
|
||||
Flags: diffFlags,
|
||||
Action: diffCmd,
|
||||
ArgsUsage: "ID-NAME",
|
||||
}
|
||||
)
|
||||
|
||||
func diffCmd(c *cli.Context) error {
|
||||
if len(c.Args()) != 1 {
|
||||
return errors.Errorf("container, layer, or image name must be specified: kpod diff [options [...]] ID-NAME")
|
||||
}
|
||||
config, err := getConfig(c)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get config")
|
||||
}
|
||||
|
||||
server, err := libkpod.New(config)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get container server")
|
||||
}
|
||||
|
||||
to := c.Args().Get(0)
|
||||
changes, err := server.GetDiff("", to)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get changes for %q", to)
|
||||
}
|
||||
|
||||
if c.Bool("json") {
|
||||
jsonStruct := diffJSONOutput{}
|
||||
for _, change := range changes {
|
||||
if change.Kind == archive.ChangeModify {
|
||||
jsonStruct.Changed = append(jsonStruct.Changed, change.Path)
|
||||
} else if change.Kind == archive.ChangeAdd {
|
||||
jsonStruct.Added = append(jsonStruct.Added, change.Path)
|
||||
} else if change.Kind == archive.ChangeDelete {
|
||||
jsonStruct.Deleted = append(jsonStruct.Deleted, change.Path)
|
||||
} else {
|
||||
return errors.Errorf("change kind %q not recognized", change.Kind.String())
|
||||
}
|
||||
}
|
||||
b, err := json.MarshalIndent(jsonStruct, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not marshal json for %+v", jsonStruct)
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
} else {
|
||||
for _, change := range changes {
|
||||
fmt.Println(change.String())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -22,6 +22,7 @@ func main() {
|
|||
app.Version = Version
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
diffCommand,
|
||||
exportCommand,
|
||||
historyCommand,
|
||||
imagesCommand,
|
||||
|
|
45
docs/kpod-diff.1.md
Normal file
45
docs/kpod-diff.1.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
% kpod(1) kpod-diff - Inspect changes on a container or image's filesystem
|
||||
% Dan Walsh
|
||||
# kpod-diff "1" "August 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod diff - Inspect changes on a container or image's filesystem
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod** **diff** [*options* [...]] NAME
|
||||
|
||||
## DESCRIPTION
|
||||
Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--json**
|
||||
|
||||
Format output as json
|
||||
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
kpod diff redis:alpine
|
||||
C /usr
|
||||
C /usr/local
|
||||
C /usr/local/bin
|
||||
A /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
kpod diff --json redis:alpine
|
||||
{
|
||||
"changed": [
|
||||
"/usr",
|
||||
"/usr/local",
|
||||
"/usr/local/bin"
|
||||
],
|
||||
"added": [
|
||||
"/usr/local/bin/docker-entrypoint.sh"
|
||||
]
|
||||
}
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1)
|
||||
|
||||
## HISTORY
|
||||
August 2017, Originally compiled by Ryan Cole <rycole@redhat.com>
|
54
libkpod/diff.go
Normal file
54
libkpod/diff.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package libkpod
|
||||
|
||||
import (
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/kubernetes-incubator/cri-o/libkpod/image"
|
||||
"github.com/kubernetes-incubator/cri-o/libkpod/layer"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// GetDiff returns the differences betwen the two images, layers, or containers
|
||||
func (c *ContainerServer) GetDiff(from, to string) ([]archive.Change, error) {
|
||||
toLayer, err := c.getLayerID(to)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fromLayer := ""
|
||||
if from != "" {
|
||||
fromLayer, err = c.getLayerID(from)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c.Store().Changes(fromLayer, toLayer)
|
||||
}
|
||||
|
||||
// GetLayerID gets a full layer id given a full or partial id
|
||||
// If the id matches a container or image, the id of the top layer is returned
|
||||
// If the id matches a layer, the top layer id is returned
|
||||
func (c *ContainerServer) getLayerID(id string) (string, error) {
|
||||
var toLayer string
|
||||
toImage, err := image.FindImage(c.store, id)
|
||||
if err != nil {
|
||||
toCtr, err := c.store.Container(id)
|
||||
if err != nil {
|
||||
toLayer, err = layer.FullID(c.store, id)
|
||||
if err != nil {
|
||||
return "", errors.Errorf("layer, image, or container %s does not exist", id)
|
||||
}
|
||||
} else {
|
||||
toLayer = toCtr.LayerID
|
||||
}
|
||||
} else {
|
||||
toLayer = toImage.TopLayer
|
||||
}
|
||||
return toLayer, nil
|
||||
}
|
||||
|
||||
func (c *ContainerServer) getLayerParent(layerID string) (string, error) {
|
||||
layer, err := c.store.Layer(layerID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return layer.Parent, nil
|
||||
}
|
12
libkpod/layer/layer.go
Normal file
12
libkpod/layer/layer.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package layer
|
||||
|
||||
import cstorage "github.com/containers/storage"
|
||||
|
||||
// FullID gets the full id of a layer given a partial id or name
|
||||
func FullID(store cstorage.Store, id string) (string, error) {
|
||||
layer, err := store.Layer(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return layer.ID, nil
|
||||
}
|
37
test/kpod_diff.bats
Normal file
37
test/kpod_diff.bats
Normal file
|
@ -0,0 +1,37 @@
|
|||
#/usr/bin/env bats
|
||||
|
||||
load helpers
|
||||
|
||||
IMAGE="alpine:latest"
|
||||
ROOT="$TESTDIR/crio"
|
||||
RUNROOT="$TESTDIR/crio-run"
|
||||
KPOD_OPTIONS="--root $ROOT --runroot $RUNROOT --storage-driver vfs"
|
||||
|
||||
function teardown() {
|
||||
cleanup_test
|
||||
}
|
||||
|
||||
@test "test diff of image and parent" {
|
||||
run ${KPOD_BINARY} $KPOD_OPTIONS pull $IMAGE
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} $KPOD_OPTIONS diff $IMAGE
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output"
|
||||
run ${KKPOD_BINARY} $KPOD_OPTIONS rmi $IMAGE
|
||||
}
|
||||
|
||||
@test "test diff on non-existent layer" {
|
||||
run ${KPOD_BINARY} $KPOD_OPTIONS diff "abc123"
|
||||
[ "$status" -ne 0 ]
|
||||
echo "$output"
|
||||
}
|
||||
|
||||
@test "test diff with json output" {
|
||||
run ${KPOD_BINARY} $KPOD_OPTIONS pull $IMAGE
|
||||
[ "$status" -eq 0 ]
|
||||
# run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} diff --json $IMAGE | python -m json.tool"
|
||||
run ${KPOD_BINARY} $KPOD_OPTIONS diff --json $IMAGE
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output"
|
||||
run ${KKPOD_BINARY} $KPOD_OPTIONS rmi $IMAGE
|
||||
}
|
Loading…
Reference in a new issue