mirror of
https://github.com/containers/podman.git
synced 2025-05-25 19:16:59 +08:00
syntax updated for podman import --change
currently, podman import change do not support syntax like - KEY val - KEY ["val"] This adds support for both of these syntax along with KEY=val Signed-off-by: Kunal Kushwaha <kunal.kushwaha@gmail.com>
This commit is contained in:
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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())
|
||||||
|
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user