Add 'kpod import' command
Imports a tarball to create a filesystem image Signed-off-by: umohnani8 <umohnani@redhat.com>
This commit is contained in:
parent
9e1caabcaa
commit
803fbbfb56
8 changed files with 512 additions and 3 deletions
|
@ -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
190
cmd/kpod/import.go
Normal 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
|
||||
}
|
|
@ -36,6 +36,7 @@ func main() {
|
|||
exportCommand,
|
||||
historyCommand,
|
||||
imagesCommand,
|
||||
importCommand,
|
||||
infoCommand,
|
||||
inspectCommand,
|
||||
killCommand,
|
||||
|
|
|
@ -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
87
docs/kpod-import.1.md
Normal 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>
|
|
@ -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
137
test/kpod_import.bats
Normal 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
|
||||
}
|
1
vendor/github.com/containers/image/transports/alltransports/alltransports.go
generated
vendored
1
vendor/github.com/containers/image/transports/alltransports/alltransports.go
generated
vendored
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue