Add 'kpod import' command

Imports a tarball to create a filesystem image

Signed-off-by: umohnani8 <umohnani@redhat.com>
This commit is contained in:
Urvashi Mohnani 2017-11-01 10:28:38 -04:00
parent 9e1caabcaa
commit 803fbbfb56
8 changed files with 512 additions and 3 deletions

View file

@ -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)

190
cmd/kpod/import.go Normal file
View file

@ -0,0 +1,190 @@
package main
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
"github.com/kubernetes-incubator/cri-o/libpod"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var (
importFlags = []cli.Flag{
cli.StringFlag{
Name: "change, c",
Usage: "Apply imgspecv1 configurations to the created image",
},
cli.StringFlag{
Name: "message, m",
Usage: "Set commit message for image imported",
},
}
importDescription = "Imports a tarball and saves it as a root filesystem image.\n" +
"The commit message and image config can be modified by the user."
importCommand = cli.Command{
Name: "import",
Usage: "Import a tarball to create a filesystem image",
Description: importDescription,
Flags: importFlags,
Action: importCmd,
ArgsUsage: "TARBALL",
}
)
func importCmd(c *cli.Context) error {
if err := validateFlags(c, historyFlags); 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")
case 1:
source = args[0]
case 2:
source = args[0]
opts.Reference = args[1]
default:
return errors.Errorf("too many arguments, need 2 only")
}
if _, err := url.ParseRequestURI(source); err == nil {
file, err := downloadFromURL(source)
if err != nil {
return nil
}
defer os.Remove(file)
source = file
}
changes := v1.ImageConfig{}
if c.IsSet("change") {
changes, err = getImageConfig(c.String("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
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(change 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)
changes := strings.Split(change, " ")
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
}

View file

@ -36,6 +36,7 @@ func main() {
exportCommand,
historyCommand,
imagesCommand,
importCommand,
infoCommand,
inspectCommand,
killCommand,

View file

@ -711,6 +711,28 @@ _kpod_history() {
esac
}
_kpod_import() {
local options_with_args="
--change
-c
--message
-m
"
local boolean_options="
"
_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 +1424,7 @@ _kpod_kpod() {
export
history
images
import
info
inspect
kill

87
docs/kpod-import.1.md Normal file
View file

@ -0,0 +1,87 @@
% 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 and saves it as a filesystem image.
The image configuration can be modified 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 imgspecv1 configurations to the created image
Possible configurations include:
**USER** | **EXPOSE** | **ENV** | **ENTRYPOINT** | **CMD** | **VOLUME** | **WORKDIR** | **LABEL** | **STOPSIGNAL**
**--message, -m**
Set commit message for image imported
## EXAMPLES
```
# kpod import --change "CMD=/bin/bash ENTRYPOINT=/bin/sh 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://lacrosse.redhat.com/~umohnani/ctr.tar url-image
Downloading from "http://lacrosse.redhat.com/~umohnani/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>

View file

@ -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
@ -877,8 +885,70 @@ 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)
annotations[ociv1.AnnotationDescription] = "test image built"
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 == "" {
newImg, err := src.NewImage(sc)
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")
}
reference = "@" + digest.Hex()
}
policy, err := signature.DefaultPolicy(sc)
if err != nil {
return err
}
policyContext, err := signature.NewPolicyContext(policy)
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

137
test/kpod_import.bats Normal file
View file

@ -0,0 +1,137 @@
#!/usr/bin/env bats
load helpers
IMAGE="redis:alpine"
function teardown() {
cleanup_test
}
@test "kpod import with source and reference" {
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" {
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" {
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" {
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
}

View file

@ -13,6 +13,7 @@ import (
_ "github.com/containers/image/oci/archive"
_ "github.com/containers/image/oci/layout"
_ "github.com/containers/image/openshift"
_ "github.com/containers/image/tarball"
// The ostree transport is registered by ostree*.go
// The storage transport is registered by storage*.go
"github.com/containers/image/transports"