Merge pull request #4001 from kunalkushwaha/podman-import-fix

podman import syntax fix
This commit is contained in:
OpenShift Merge Robot 2019-09-30 07:20:09 -07:00 committed by GitHub
commit 2c23729c84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 199 additions and 24 deletions

View File

@ -39,7 +39,7 @@ func init() {
importCommand.SetHelpTemplate(HelpTemplate()) importCommand.SetHelpTemplate(HelpTemplate())
importCommand.SetUsageTemplate(UsageTemplate()) importCommand.SetUsageTemplate(UsageTemplate())
flags := importCommand.Flags() flags := importCommand.Flags()
flags.StringSliceVarP(&importCommand.Change, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR") flags.StringArrayVarP(&importCommand.Change, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR")
flags.StringVarP(&importCommand.Message, "message", "m", "", "Set commit message for imported image") flags.StringVarP(&importCommand.Message, "message", "m", "", "Set commit message for imported image")
flags.BoolVarP(&importCommand.Quiet, "quiet", "q", false, "Suppress output") flags.BoolVarP(&importCommand.Quiet, "quiet", "q", false, "Suppress output")
@ -56,7 +56,6 @@ func importCmd(c *cliconfig.ImportValues) error {
source string source string
reference string reference string
) )
args := c.InputArgs args := c.InputArgs
switch len(args) { switch len(args) {
case 0: case 0:
@ -81,7 +80,7 @@ func importCmd(c *cliconfig.ImportValues) error {
if runtime.Remote { if runtime.Remote {
quiet = false quiet = false
} }
iid, err := runtime.Import(getContext(), source, reference, c.StringSlice("change"), c.String("message"), quiet) iid, err := runtime.Import(getContext(), source, reference, importCommand.Change, c.String("message"), quiet)
if err == nil { if err == nil {
fmt.Println(iid) fmt.Println(iid)
} }

View File

@ -54,6 +54,26 @@ Storing signatures
db65d991f3bbf7f31ed1064db9a6ced7652e3f8166c4736aa9133dadd3c7acb3 db65d991f3bbf7f31ed1064db9a6ced7652e3f8166c4736aa9133dadd3c7acb3
``` ```
```
$ podman import --change "ENTRYPOINT ["/bin/sh","-c","test-image"]" --change LABEL=blue=image test-image.tar image-imported
Getting image source signatures
Copying blob e3b0c44298fc skipped: already exists
Copying config 1105523502 done
Writing manifest to image destination
Storing signatures
110552350206337183ceadc0bdd646dc356e06514c548b69a8917b4182414b
```
```
$ podman import --change "CMD /bin/sh" --change LABEL=blue=image test-image.tar image-imported
Getting image source signatures
Copying blob e3b0c44298fc skipped: already exists
Copying config ae9a27e249 done
Writing manifest to image destination
Storing signatures
ae9a27e249f801aff11a4ba54a81751ea9fbc9db45a6df3f1bfd63fc2437bb9c
```
``` ```
$ cat ctr.tar | podman -q import --message "importing the ctr.tar tarball" - image-imported $ cat ctr.tar | podman -q import --message "importing the ctr.tar tarball" - image-imported
db65d991f3bbf7f31ed1064db9a6ced7652e3f8166c4736aa9133dadd3c7acb3 db65d991f3bbf7f31ed1064db9a6ced7652e3f8166c4736aa9133dadd3c7acb3

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -16,7 +17,7 @@ import (
"github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage" "github.com/containers/storage"
"github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/idtools"
"github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@ -69,6 +70,50 @@ func StringInSlice(s string, sl []string) bool {
return false return false
} }
// ParseChanges returns key, value(s) pair for given option.
func ParseChanges(option string) (key string, vals []string, err error) {
// Supported format as below
// 1. key=value
// 2. key value
// 3. key ["value","value1"]
if strings.Contains(option, " ") {
// This handles 2 & 3 conditions.
var val string
tokens := strings.SplitAfterN(option, " ", 2)
if len(tokens) < 2 {
return "", []string{}, fmt.Errorf("invalid key value %s", option)
}
key = strings.Trim(tokens[0], " ") // Need to trim whitespace part of delimeter.
val = tokens[1]
if strings.Contains(tokens[1], "[") && strings.Contains(tokens[1], "]") {
//Trim '[',']' if exist.
val = strings.TrimLeft(strings.TrimRight(tokens[1], "]"), "[")
}
vals = strings.Split(val, ",")
} else if strings.Contains(option, "=") {
// handles condition 1.
tokens := strings.Split(option, "=")
key = tokens[0]
vals = tokens[1:]
} else {
// either ` ` or `=` must be provided after command
return "", []string{}, fmt.Errorf("invalid format %s", option)
}
if len(vals) == 0 {
return "", []string{}, errors.Errorf("no value given for instruction %q", key)
}
for _, v := range vals {
//each option must not have ' '., `[`` or `]` & empty strings
whitespaces := regexp.MustCompile(`[\[\s\]]`)
if whitespaces.MatchString(v) || len(v) == 0 {
return "", []string{}, fmt.Errorf("invalid value %s", v)
}
}
return key, vals, nil
}
// GetImageConfig converts the --change flag values in the format "CMD=/bin/bash USER=example" // GetImageConfig converts the --change flag values in the format "CMD=/bin/bash USER=example"
// to a type v1.ImageConfig // to a type v1.ImageConfig
func GetImageConfig(changes []string) (v1.ImageConfig, error) { func GetImageConfig(changes []string) (v1.ImageConfig, error) {
@ -87,40 +132,42 @@ func GetImageConfig(changes []string) (v1.ImageConfig, error) {
exposedPorts := make(map[string]struct{}) exposedPorts := make(map[string]struct{})
volumes := make(map[string]struct{}) volumes := make(map[string]struct{})
labels := make(map[string]string) labels := make(map[string]string)
for _, ch := range changes { for _, ch := range changes {
pair := strings.Split(ch, "=") key, vals, err := ParseChanges(ch)
if len(pair) == 1 { if err != nil {
return v1.ImageConfig{}, errors.Errorf("no value given for instruction %q", ch) return v1.ImageConfig{}, err
} }
switch pair[0] {
switch key {
case "USER": case "USER":
user = pair[1] user = vals[0]
case "EXPOSE": case "EXPOSE":
var st struct{} var st struct{}
exposedPorts[pair[1]] = st exposedPorts[vals[0]] = st
case "ENV": case "ENV":
if len(pair) < 3 { if len(vals) < 2 {
return v1.ImageConfig{}, errors.Errorf("no value given for environment variable %q", pair[1]) return v1.ImageConfig{}, errors.Errorf("no value given for environment variable %q", vals[0])
} }
env = append(env, strings.Join(pair[1:], "=")) env = append(env, strings.Join(vals[0:], "="))
case "ENTRYPOINT": case "ENTRYPOINT":
entrypoint = append(entrypoint, pair[1]) // ENTRYPOINT and CMD can have array of strings
entrypoint = append(entrypoint, vals...)
case "CMD": case "CMD":
cmd = append(cmd, pair[1]) // ENTRYPOINT and CMD can have array of strings
cmd = append(cmd, vals...)
case "VOLUME": case "VOLUME":
var st struct{} var st struct{}
volumes[pair[1]] = st volumes[vals[0]] = st
case "WORKDIR": case "WORKDIR":
workingDir = pair[1] workingDir = vals[0]
case "LABEL": case "LABEL":
if len(pair) == 3 { if len(vals) == 2 {
labels[pair[1]] = pair[2] labels[vals[0]] = vals[1]
} else { } else {
labels[pair[1]] = "" labels[vals[0]] = ""
} }
case "STOPSIGNAL": case "STOPSIGNAL":
stopSignal = pair[1] stopSignal = vals[0]
} }
} }

View File

@ -1,8 +1,9 @@
package util package util
import ( import (
"github.com/stretchr/testify/assert"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
var ( var (
@ -17,3 +18,71 @@ func TestStringInSlice(t *testing.T) {
// string is not in empty slice // string is not in empty slice
assert.False(t, StringInSlice("one", []string{})) assert.False(t, StringInSlice("one", []string{}))
} }
func TestParseChanges(t *testing.T) {
// CMD=/bin/sh
_, vals, err := ParseChanges("CMD=/bin/sh")
assert.EqualValues(t, []string{"/bin/sh"}, vals)
assert.NoError(t, err)
// CMD [/bin/sh]
_, vals, err = ParseChanges("CMD [/bin/sh]")
assert.EqualValues(t, []string{"/bin/sh"}, vals)
assert.NoError(t, err)
// CMD ["/bin/sh"]
_, vals, err = ParseChanges(`CMD ["/bin/sh"]`)
assert.EqualValues(t, []string{`"/bin/sh"`}, vals)
assert.NoError(t, err)
// CMD ["/bin/sh","-c","ls"]
_, vals, err = ParseChanges(`CMD ["/bin/sh","c","ls"]`)
assert.EqualValues(t, []string{`"/bin/sh"`, `"c"`, `"ls"`}, vals)
assert.NoError(t, err)
// CMD ["/bin/sh","arg-with,comma"]
_, vals, err = ParseChanges(`CMD ["/bin/sh","arg-with,comma"]`)
assert.EqualValues(t, []string{`"/bin/sh"`, `"arg-with`, `comma"`}, vals)
assert.NoError(t, err)
// CMD "/bin/sh"]
_, _, err = ParseChanges(`CMD "/bin/sh"]`)
assert.Error(t, err)
assert.Equal(t, `invalid value "/bin/sh"]`, err.Error())
// CMD [bin/sh
_, _, err = ParseChanges(`CMD "/bin/sh"]`)
assert.Error(t, err)
assert.Equal(t, `invalid value "/bin/sh"]`, err.Error())
// CMD ["/bin /sh"]
_, _, err = ParseChanges(`CMD ["/bin /sh"]`)
assert.Error(t, err)
assert.Equal(t, `invalid value "/bin /sh"`, err.Error())
// CMD ["/bin/sh", "-c","ls"] whitespace between values
_, vals, err = ParseChanges(`CMD ["/bin/sh", "c","ls"]`)
assert.Error(t, err)
assert.Equal(t, `invalid value "c"`, err.Error())
// CMD?
_, _, err = ParseChanges(`CMD?`)
assert.Error(t, err)
assert.Equal(t, `invalid format CMD?`, err.Error())
// empty values for CMD
_, _, err = ParseChanges(`CMD `)
assert.Error(t, err)
assert.Equal(t, `invalid value `, err.Error())
// LABEL=blue=image
_, vals, err = ParseChanges(`LABEL=blue=image`)
assert.EqualValues(t, []string{"blue", "image"}, vals)
assert.NoError(t, err)
// LABEL = blue=image
_, vals, err = ParseChanges(`LABEL = blue=image`)
assert.Error(t, err)
assert.Equal(t, `invalid value = blue=image`, err.Error())
}

View File

@ -88,7 +88,7 @@ var _ = Describe("Podman import", func() {
Expect(results.LineInOuputStartsWith("importing container test message")).To(BeTrue()) Expect(results.LineInOuputStartsWith("importing container test message")).To(BeTrue())
}) })
It("podman import with change flag", func() { It("podman import with change flag CMD=<path>", func() {
outfile := filepath.Join(podmanTest.TempDir, "container.tar") outfile := filepath.Join(podmanTest.TempDir, "container.tar")
_, ec, cid := podmanTest.RunLsContainer("") _, ec, cid := podmanTest.RunLsContainer("")
Expect(ec).To(Equal(0)) Expect(ec).To(Equal(0))
@ -108,4 +108,44 @@ var _ = Describe("Podman import", func() {
Expect(imageData[0].Config.Cmd[0]).To(Equal("/bin/bash")) Expect(imageData[0].Config.Cmd[0]).To(Equal("/bin/bash"))
}) })
It("podman import with change flag CMD <path>", func() {
outfile := filepath.Join(podmanTest.TempDir, "container.tar")
_, ec, cid := podmanTest.RunLsContainer("")
Expect(ec).To(Equal(0))
export := podmanTest.Podman([]string{"export", "-o", outfile, cid})
export.WaitWithDefaultTimeout()
Expect(export.ExitCode()).To(Equal(0))
importImage := podmanTest.Podman([]string{"import", "--change", "CMD /bin/sh", outfile, "imported-image"})
importImage.WaitWithDefaultTimeout()
Expect(importImage.ExitCode()).To(Equal(0))
results := podmanTest.Podman([]string{"inspect", "imported-image"})
results.WaitWithDefaultTimeout()
Expect(results.ExitCode()).To(Equal(0))
imageData := results.InspectImageJSON()
Expect(imageData[0].Config.Cmd[0]).To(Equal("/bin/sh"))
})
It("podman import with change flag CMD [\"path\",\"path'\"", func() {
outfile := filepath.Join(podmanTest.TempDir, "container.tar")
_, ec, cid := podmanTest.RunLsContainer("")
Expect(ec).To(Equal(0))
export := podmanTest.Podman([]string{"export", "-o", outfile, cid})
export.WaitWithDefaultTimeout()
Expect(export.ExitCode()).To(Equal(0))
importImage := podmanTest.Podman([]string{"import", "--change", "CMD [/bin/bash]", outfile, "imported-image"})
importImage.WaitWithDefaultTimeout()
Expect(importImage.ExitCode()).To(Equal(0))
results := podmanTest.Podman([]string{"inspect", "imported-image"})
results.WaitWithDefaultTimeout()
Expect(results.ExitCode()).To(Equal(0))
imageData := results.InspectImageJSON()
Expect(imageData[0].Config.Cmd[0]).To(Equal("/bin/bash"))
})
}) })