summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUrvashi Mohnani <umohnani@redhat.com>2017-11-02 15:31:49 -0400
committerAtomic Bot <atomic-devel@projectatomic.io>2017-11-07 19:16:31 +0000
commit23979f8e0644cd1e05e05b0fc9bcb4a1fe42bb82 (patch)
treedc018440069dece8df8f57de65cdbb46bb06350d
parentd086beb7abc5ed002fe699ab02792aa47d50d581 (diff)
downloadpodman-23979f8e0644cd1e05e05b0fc9bcb4a1fe42bb82.tar.gz
podman-23979f8e0644cd1e05e05b0fc9bcb4a1fe42bb82.tar.bz2
podman-23979f8e0644cd1e05e05b0fc9bcb4a1fe42bb82.zip
Add 'kpod import' command
Imports a tarball and saves it as a filesystem image Signed-off-by: Urvashi Mohnani <umohnani@redhat.com> Closes: #12 Approved by: rhatdan
-rw-r--r--cmd/kpod/history.go2
-rw-r--r--cmd/kpod/import.go190
-rw-r--r--cmd/kpod/main.go1
-rw-r--r--cmd/kpod/save.go3
-rw-r--r--completions/bash/kpod25
-rw-r--r--docs/kpod-import.1.md88
-rw-r--r--libpod/runtime_img.go125
-rw-r--r--test/kpod_import.bats141
8 files changed, 546 insertions, 29 deletions
diff --git a/cmd/kpod/history.go b/cmd/kpod/history.go
index ab2115aed..c21c1e338 100644
--- a/cmd/kpod/history.go
+++ b/cmd/kpod/history.go
@@ -87,7 +87,7 @@ func historyCmd(c *cli.Context) error {
runtime, err := getRuntime(c)
if err != nil {
- return errors.Wrapf(err, "Could not get config")
+ return errors.Wrapf(err, "could not get runtime")
}
defer runtime.Shutdown(false)
diff --git a/cmd/kpod/import.go b/cmd/kpod/import.go
new file mode 100644
index 000000000..2e8702c3d
--- /dev/null
+++ b/cmd/kpod/import.go
@@ -0,0 +1,190 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "os"
+ "strings"
+
+ "github.com/opencontainers/image-spec/specs-go/v1"
+ "github.com/pkg/errors"
+ "github.com/projectatomic/libpod/libpod"
+ "github.com/urfave/cli"
+)
+
+var (
+ importFlags = []cli.Flag{
+ cli.StringSliceFlag{
+ Name: "change, c",
+ Usage: "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR",
+ },
+ cli.StringFlag{
+ Name: "message, m",
+ Usage: "Set commit message for imported image",
+ },
+ }
+ importDescription = `Create a container image from the contents of the specified tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).
+ Note remote tar balls can be specified, via web address.
+ Optionally tag the image. You can specify the Dockerfile instructions using the --change option.
+ `
+ importCommand = cli.Command{
+ Name: "import",
+ Usage: "Import a tarball to create a filesystem image",
+ Description: importDescription,
+ Flags: importFlags,
+ Action: importCmd,
+ ArgsUsage: "TARBALL [REFERENCE]",
+ }
+)
+
+func importCmd(c *cli.Context) error {
+ if err := validateFlags(c, importFlags); err != nil {
+ return err
+ }
+
+ runtime, err := getRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "could not get runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ var opts libpod.CopyOptions
+ var source string
+ args := c.Args()
+ switch len(args) {
+ case 0:
+ return errors.Errorf("need to give the path to the tarball, or must specify a tarball of '-' for stdin")
+ case 1:
+ source = args[0]
+ case 2:
+ source = args[0]
+ opts.Reference = args[1]
+ default:
+ return errors.Errorf("too many arguments. Usage TARBALL [REFERENCE]")
+ }
+
+ changes := v1.ImageConfig{}
+ if c.IsSet("change") {
+ changes, err = getImageConfig(c.StringSlice("change"))
+ if err != nil {
+ return errors.Wrapf(err, "error adding config changes to image %q", source)
+ }
+ }
+
+ history := []v1.History{
+ {Comment: c.String("message")},
+ }
+
+ config := v1.Image{
+ Config: changes,
+ History: history,
+ }
+
+ opts.ImageConfig = config
+
+ // if source is a url, download it and save to a temp file
+ u, err := url.ParseRequestURI(source)
+ if err == nil && u.Scheme != "" {
+ file, err := downloadFromURL(source)
+ if err != nil {
+ return err
+ }
+ defer os.Remove(file)
+ source = file
+ }
+
+ return runtime.ImportImage(source, opts)
+}
+
+// donwloadFromURL downloads an image in the format "https:/example.com/myimage.tar"
+// and tempoarily saves in it /var/tmp/importxyz, which is deleted after the image is imported
+func downloadFromURL(source string) (string, error) {
+ fmt.Printf("Downloading from %q\n", source)
+
+ outFile, err := ioutil.TempFile("/var/tmp", "import")
+ if err != nil {
+ return "", errors.Wrap(err, "error creating file")
+ }
+ defer outFile.Close()
+
+ response, err := http.Get(source)
+ if err != nil {
+ return "", errors.Wrapf(err, "error downloading %q", source)
+ }
+ defer response.Body.Close()
+
+ _, err = io.Copy(outFile, response.Body)
+ if err != nil {
+ return "", errors.Wrapf(err, "error saving %q to %q", source, outFile)
+ }
+
+ return outFile.Name(), nil
+}
+
+// getImageConfig converts the --change flag values in the format "CMD=/bin/bash USER=example"
+// to a type v1.ImageConfig
+func getImageConfig(changes []string) (v1.ImageConfig, error) {
+ // USER=value | EXPOSE=value | ENV=value | ENTRYPOINT=value |
+ // CMD=value | VOLUME=value | WORKDIR=value | LABEL=key=value | STOPSIGNAL=value
+
+ var (
+ user string
+ env []string
+ entrypoint []string
+ cmd []string
+ workingDir string
+ stopSignal string
+ )
+
+ exposedPorts := make(map[string]struct{})
+ volumes := make(map[string]struct{})
+ labels := make(map[string]string)
+
+ for _, ch := range changes {
+ pair := strings.Split(ch, "=")
+ if len(pair) == 1 {
+ return v1.ImageConfig{}, errors.Errorf("no value given for instruction %q", ch)
+ }
+ switch pair[0] {
+ case "USER":
+ user = pair[1]
+ case "EXPOSE":
+ var st struct{}
+ exposedPorts[pair[1]] = st
+ case "ENV":
+ env = append(env, pair[1])
+ case "ENTRYPOINT":
+ entrypoint = append(entrypoint, pair[1])
+ case "CMD":
+ cmd = append(cmd, pair[1])
+ case "VOLUME":
+ var st struct{}
+ volumes[pair[1]] = st
+ case "WORKDIR":
+ workingDir = pair[1]
+ case "LABEL":
+ if len(pair) == 3 {
+ labels[pair[1]] = pair[2]
+ } else {
+ labels[pair[1]] = ""
+ }
+ case "STOPSIGNAL":
+ stopSignal = pair[1]
+ }
+ }
+
+ return v1.ImageConfig{
+ User: user,
+ ExposedPorts: exposedPorts,
+ Env: env,
+ Entrypoint: entrypoint,
+ Cmd: cmd,
+ Volumes: volumes,
+ WorkingDir: workingDir,
+ Labels: labels,
+ StopSignal: stopSignal,
+ }, nil
+}
diff --git a/cmd/kpod/main.go b/cmd/kpod/main.go
index b8a7b0cb5..ab95995fe 100644
--- a/cmd/kpod/main.go
+++ b/cmd/kpod/main.go
@@ -36,6 +36,7 @@ func main() {
exportCommand,
historyCommand,
imagesCommand,
+ importCommand,
infoCommand,
inspectCommand,
killCommand,
diff --git a/cmd/kpod/save.go b/cmd/kpod/save.go
index 287821f0a..0f5fcfa4d 100644
--- a/cmd/kpod/save.go
+++ b/cmd/kpod/save.go
@@ -91,6 +91,9 @@ func saveCmd(c *cli.Context) error {
for _, image := range args {
dest := dst + ":" + image
if err := runtime.PushImage(image, dest, saveOpts); err != nil {
+ if err2 := os.Remove(output); err2 != nil {
+ logrus.Errorf("error deleting %q: %v", output, err)
+ }
return errors.Wrapf(err, "unable to save %q", image)
}
}
diff --git a/completions/bash/kpod b/completions/bash/kpod
index 88edbee6f..2387228cb 100644
--- a/completions/bash/kpod
+++ b/completions/bash/kpod
@@ -711,6 +711,30 @@ _kpod_history() {
esac
}
+
+_kpod_import() {
+ local options_with_args="
+ --change
+ -c
+ --message
+ -m
+ "
+ local boolean_options="
+ --help
+ -h
+ "
+ _complete_ "$options_with_args" "$boolean_options"
+
+ case "$cur" in
+ -*)
+ COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ ;;
+ *)
+ __kpod_list_images
+ ;;
+ esac
+}
+
_kpod_info() {
local boolean_options="
--help
@@ -1402,6 +1426,7 @@ _kpod_kpod() {
export
history
images
+ import
info
inspect
kill
diff --git a/docs/kpod-import.1.md b/docs/kpod-import.1.md
new file mode 100644
index 000000000..cfcfb6fb2
--- /dev/null
+++ b/docs/kpod-import.1.md
@@ -0,0 +1,88 @@
+% kpod(1) kpod-import - Simple tool to import a tarball as an image
+% Urvashi Mohnani
+# kpod-import "1" "November 2017" "kpod"
+
+## NAME
+kpod-import - import a tarball and save it as a filesystem image
+
+## SYNOPSIS
+**kpod import**
+**TARBALL**
+[**--change**|**-c**]
+[**--message**|**-m**]
+[**--help**|**-h**]
+
+## DESCRIPTION
+**kpod import** imports a tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz)
+and saves it as a filesystem image. Remote tarballs can be specified using a URL.
+Various image instructions can be configured with the **--change** flag and
+a commit message can be set using the **--message** flag.
+
+**kpod [GLOBAL OPTIONS]**
+
+**kpod import [GLOBAL OPTIONS]**
+
+**kpod import [OPTIONS] CONTAINER**
+
+## OPTIONS
+
+**--change, -c**
+Apply the following possible instructions to the created image:
+**CMD** | **ENTRYPOINT** | **ENV** | **EXPOSE** | **LABEL** | **STOPSIGNAL** | **USER** | **VOLUME** | **WORKDIR**
+Can be set multiple times
+
+**--message, -m**
+Set commit message for imported image
+
+## EXAMPLES
+
+```
+# kpod import --change CMD=/bin/bash --change ENTRYPOINT=/bin/sh --change LABEL=blue=image ctr.tar image-imported
+Getting image source signatures
+Copying blob sha256:b41deda5a2feb1f03a5c1bb38c598cbc12c9ccd675f438edc6acd815f7585b86
+ 25.80 MB / 25.80 MB [======================================================] 0s
+Copying config sha256:c16a6d30f3782288ec4e7521c754acc29d37155629cb39149756f486dae2d4cd
+ 448 B / 448 B [============================================================] 0s
+Writing manifest to image destination
+Storing signatures
+```
+
+```
+# cat ctr.tar | kpod import --message "importing the ctr.tar tarball" - image-imported
+Getting image source signatures
+Copying blob sha256:b41deda5a2feb1f03a5c1bb38c598cbc12c9ccd675f438edc6acd815f7585b86
+ 25.80 MB / 25.80 MB [======================================================] 0s
+Copying config sha256:af376cdda5c0ac1d9592bf56567253d203f8de6a8edf356c683a645d75221540
+ 376 B / 376 B [============================================================] 0s
+Writing manifest to image destination
+Storing signatures
+```
+
+```
+# cat ctr.tar | kpod import -
+Getting image source signatures
+Copying blob sha256:b41deda5a2feb1f03a5c1bb38c598cbc12c9ccd675f438edc6acd815f7585b86
+ 25.80 MB / 25.80 MB [======================================================] 0s
+Copying config sha256:d61387b4d5edf65edee5353e2340783703074ffeaaac529cde97a8357eea7645
+ 378 B / 378 B [============================================================] 0s
+Writing manifest to image destination
+Storing signatures
+```
+
+```
+kpod import http://example.com/ctr.tar url-image
+Downloading from "http://example.com/ctr.tar"
+Getting image source signatures
+Copying blob sha256:b41deda5a2feb1f03a5c1bb38c598cbc12c9ccd675f438edc6acd815f7585b86
+ 25.80 MB / 25.80 MB [======================================================] 0s
+Copying config sha256:5813fe8a3b18696089fd09957a12e88bda43dc1745b5240879ffffe93240d29a
+ 419 B / 419 B [============================================================] 0s
+Writing manifest to image destination
+Storing signatures
+```
+
+## SEE ALSO
+kpod(1), kpod-export(1), crio(8), crio.conf(5)
+
+## HISTORY
+November 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go
index a57aec86c..8a5258e75 100644
--- a/libpod/runtime_img.go
+++ b/libpod/runtime_img.go
@@ -18,6 +18,7 @@ import (
"github.com/containers/image/pkg/sysregistries"
"github.com/containers/image/signature"
is "github.com/containers/image/storage"
+ "github.com/containers/image/tarball"
"github.com/containers/image/transports"
"github.com/containers/image/transports/alltransports"
"github.com/containers/image/types"
@@ -49,6 +50,9 @@ var (
DirTransport = "dir"
// TransportNames are the supported transports in string form
TransportNames = [...]string{DefaultRegistry, DockerArchive, OCIArchive, "ostree:", "dir:"}
+ // TarballTransport is the transport for importing a tar archive
+ // and creating a filesystem image
+ TarballTransport = "tarball"
)
// CopyOptions contains the options given when pushing or pulling images
@@ -72,6 +76,10 @@ type CopyOptions struct {
AuthFile string
// Writer is the reportWriter for the output
Writer io.Writer
+ // Reference is the name for the image created when a tar archive is imported
+ Reference string
+ // ImageConfig is the Image spec for the image created when a tar archive is imported
+ ImageConfig ociv1.Image
}
// Image API
@@ -473,23 +481,16 @@ func (r *Runtime) getPullListFromRef(srcRef types.ImageReference, imgName string
}
// to pull the first image stored in the tar file
if len(manifest) == 0 {
- // create an image object and use the hex value of the digest as the image ID
- // for parsing the store reference
- newImg, err := srcRef.NewImage(sc)
+ // use the hex of the digest if no manifest is found
+ reference, err := getImageDigest(srcRef, sc)
if err != nil {
return nil, err
}
- defer newImg.Close()
- digest := newImg.ConfigInfo().Digest
- if err := digest.Validate(); err == nil {
- pullInfo, err := r.getPullStruct(srcRef, "@"+digest.Hex())
- if err != nil {
- return nil, err
- }
- pullStructs = append(pullStructs, pullInfo)
- } else {
- return nil, errors.Wrapf(err, "error getting config info")
+ pullInfo, err := r.getPullStruct(srcRef, reference)
+ if err != nil {
+ return nil, err
}
+ pullStructs = append(pullStructs, pullInfo)
} else {
pullInfo, err := r.getPullStruct(srcRef, manifest[0].RepoTags[0])
if err != nil {
@@ -497,7 +498,6 @@ func (r *Runtime) getPullListFromRef(srcRef types.ImageReference, imgName string
}
pullStructs = append(pullStructs, pullInfo)
}
-
} else if srcRef.Transport().Name() == OCIArchive {
// retrieve the manifest from index.json to access the image name
manifest, err := ociarchive.LoadManifestDescriptor(srcRef)
@@ -572,12 +572,7 @@ func (r *Runtime) PullImage(imgName string, options CopyOptions) error {
}
}
- policy, err := signature.DefaultPolicy(sc)
- if err != nil {
- return err
- }
-
- policyContext, err := signature.NewPolicyContext(policy)
+ policyContext, err := getPolicyContext(sc)
if err != nil {
return err
}
@@ -628,12 +623,7 @@ func (r *Runtime) PushImage(source string, destination string, options CopyOptio
sc := common.GetSystemContext(signaturePolicyPath, options.AuthFile)
- policy, err := signature.DefaultPolicy(sc)
- if err != nil {
- return err
- }
-
- policyContext, err := signature.NewPolicyContext(policy)
+ policyContext, err := getPolicyContext(sc)
if err != nil {
return err
}
@@ -880,8 +870,57 @@ func (r *Runtime) GetHistory(image string) ([]ociv1.History, []types.BlobInfo, s
}
// ImportImage imports an OCI format image archive into storage as an image
-func (r *Runtime) ImportImage(path string) (*storage.Image, error) {
- return nil, ErrNotImplemented
+func (r *Runtime) ImportImage(path string, options CopyOptions) error {
+ r.lock.RLock()
+ defer r.lock.RUnlock()
+
+ if !r.valid {
+ return ErrRuntimeStopped
+ }
+
+ file := TarballTransport + ":" + path
+ src, err := alltransports.ParseImageName(file)
+ if err != nil {
+ return errors.Wrapf(err, "error parsing image name %q", path)
+ }
+
+ updater, ok := src.(tarball.ConfigUpdater)
+ if !ok {
+ return errors.Wrapf(err, "unexpected type, a tarball reference should implement tarball.ConfigUpdater")
+ }
+
+ annotations := make(map[string]string)
+
+ err = updater.ConfigUpdate(options.ImageConfig, annotations)
+ if err != nil {
+ return errors.Wrapf(err, "error updating image config")
+ }
+
+ var reference = options.Reference
+ sc := common.GetSystemContext("", "")
+
+ // if reference not given, get the image digest
+ if reference == "" {
+ reference, err = getImageDigest(src, sc)
+ if err != nil {
+ return err
+ }
+ }
+
+ policyContext, err := getPolicyContext(sc)
+ if err != nil {
+ return err
+ }
+ defer policyContext.Destroy()
+
+ copyOptions := common.GetCopyOptions(os.Stdout, "", nil, nil, common.SigningOptions{}, "")
+
+ dest, err := is.Transport.ParseStoreReference(r.store, reference)
+ if err != nil {
+ errors.Wrapf(err, "error getting image reference for %q", options.Reference)
+ }
+
+ return cp.Image(policyContext, dest, src, copyOptions)
}
// GetImageInspectInfo returns the inspect information of an image
@@ -1083,3 +1122,33 @@ func findImageInSlice(images []storage.Image, ref string) (storage.Image, error)
}
return storage.Image{}, errors.New("could not find image")
}
+
+// getImageDigest creates an image object and uses the hex value of the digest as the image ID
+// for parsing the store reference
+func getImageDigest(src types.ImageReference, ctx *types.SystemContext) (string, error) {
+ newImg, err := src.NewImage(ctx)
+ if err != nil {
+ return "", err
+ }
+ defer newImg.Close()
+
+ digest := newImg.ConfigInfo().Digest
+ if err = digest.Validate(); err != nil {
+ return "", errors.Wrapf(err, "error getting config info")
+ }
+ return "@" + digest.Hex(), nil
+}
+
+// getPolicyContext sets up, intializes and returns a new context for the specified policy
+func getPolicyContext(ctx *types.SystemContext) (*signature.PolicyContext, error) {
+ policy, err := signature.DefaultPolicy(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ policyContext, err := signature.NewPolicyContext(policy)
+ if err != nil {
+ return nil, err
+ }
+ return policyContext, nil
+}
diff --git a/test/kpod_import.bats b/test/kpod_import.bats
new file mode 100644
index 000000000..03a89f2e8
--- /dev/null
+++ b/test/kpod_import.bats
@@ -0,0 +1,141 @@
+#!/usr/bin/env bats
+
+load helpers
+
+IMAGE="redis:alpine"
+
+function teardown() {
+ cleanup_test
+}
+
+@test "kpod import with source and reference" {
+ skip "Test needs to be converted to kpod run"
+ start_crio
+ run crioctl pod run --config "$TESTDATA"/sandbox_config.json
+ echo "$output"
+ [ "$status" -eq 0 ]
+ pod_id="$output"
+ run crioctl image pull "$IMAGE"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ ctr_id="$output"
+ run ${KPOD_BINARY} ${KPOD_OPTIONS} export -o container.tar "$ctr_id"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ run ${KPOD_BINARY} ${KPOD_OPTIONS} import container.tar imported-image
+ echo "$output"
+ [ "$status" -eq 0 ]
+ run ${KPOD_BINARY} ${KPOD_OPTIONS} images
+ echo "$output"
+ [ "$status" -eq 0 ]
+ images="$output"
+ run grep "imported-image" <<< "$images"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ cleanup_ctrs
+ cleanup_pods
+ stop_crio
+ rm -f container.tar
+}
+
+@test "kpod import without reference" {
+ skip "Test needs to be converted to kpod run"
+ start_crio
+ run crioctl pod run --config "$TESTDATA"/sandbox_config.json
+ echo "$output"
+ [ "$status" -eq 0 ]
+ pod_id="$output"
+ run crioctl image pull "$IMAGE"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ ctr_id="$output"
+ run ${KPOD_BINARY} ${KPOD_OPTIONS} export -o container.tar "$ctr_id"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ run ${KPOD_BINARY} ${KPOD_OPTIONS} import container.tar
+ echo "$output"
+ [ "$status" -eq 0 ]
+ run ${KPOD_BINARY} ${KPOD_OPTIONS} images
+ echo "$output"
+ [ "$status" -eq 0 ]
+ images="$output"
+ run grep "<none>" <<< "$images"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ cleanup_ctrs
+ cleanup_pods
+ stop_crio
+ rm -f container.tar
+}
+
+@test "kpod import with message flag" {
+ skip "Test needs to be converted to kpod run"
+ start_crio
+ run crioctl pod run --config "$TESTDATA"/sandbox_config.json
+ echo "$output"
+ [ "$status" -eq 0 ]
+ pod_id="$output"
+ run crioctl image pull "$IMAGE"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ ctr_id="$output"
+ run ${KPOD_BINARY} ${KPOD_OPTIONS} export -o container.tar "$ctr_id"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ run ${KPOD_BINARY} ${KPOD_OPTIONS} import --message "importing container test message" container.tar imported-image
+ echo "$output"
+ [ "$status" -eq 0 ]
+ run ${KPOD_BINARY} ${KPOD_OPTIONS} history imported-image
+ echo "$output"
+ [ "$status" -eq 0 ]
+ history="$output"
+ run grep "importing container test message" <<< "$history"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ cleanup_ctrs
+ cleanup_pods
+ stop_crio
+ rm -f container.tar
+}
+
+@test "kpod import with change flag" {
+ skip "Test needs to be converted to kpod run"
+ start_crio
+ run crioctl pod run --config "$TESTDATA"/sandbox_config.json
+ echo "$output"
+ [ "$status" -eq 0 ]
+ pod_id="$output"
+ run crioctl image pull "$IMAGE"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ ctr_id="$output"
+ run ${KPOD_BINARY} ${KPOD_OPTIONS} export -o container.tar "$ctr_id"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ run ${KPOD_BINARY} ${KPOD_OPTIONS} import --change "CMD=/bin/bash" container.tar imported-image
+ echo "$output"
+ [ "$status" -eq 0 ]
+ run ${KPOD_BINARY} ${KPOD_OPTIONS} inspect imported-image
+ echo "$output"
+ [ "$status" -eq 0 ]
+ inspect="$output"
+ run grep "/bin/bash" <<< "$inspect"
+ echo "$output"
+ [ "$status" -eq 0 ]
+ cleanup_ctrs
+ cleanup_pods
+ stop_crio
+ rm -f container.tar
+}