abstract the string slice struct to stringutils package

Signed-off-by: Shijiang Wei <mountkin@gmail.com>
This commit is contained in:
Shijiang Wei 2015-08-28 23:29:10 +08:00
parent fdc73cc3fc
commit ea4a06740b
11 changed files with 218 additions and 340 deletions

View File

@ -20,6 +20,7 @@ import (
"github.com/Sirupsen/logrus"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/nat"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/runconfig"
)
@ -352,7 +353,7 @@ func run(b *builder, args []string, attributes map[string]bool, original string)
b.Config.Cmd = config.Cmd
runconfig.Merge(b.Config, config)
defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
defer func(cmd *stringutils.StrSlice) { b.Config.Cmd = cmd }(cmd)
logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd)
@ -405,7 +406,7 @@ func cmd(b *builder, args []string, attributes map[string]bool, original string)
}
}
b.Config.Cmd = runconfig.NewCommand(cmdSlice...)
b.Config.Cmd = stringutils.NewStrSlice(cmdSlice...)
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
return err
@ -436,16 +437,16 @@ func entrypoint(b *builder, args []string, attributes map[string]bool, original
switch {
case attributes["json"]:
// ENTRYPOINT ["echo", "hi"]
b.Config.Entrypoint = runconfig.NewEntrypoint(parsed...)
b.Config.Entrypoint = stringutils.NewStrSlice(parsed...)
case len(parsed) == 0:
// ENTRYPOINT []
b.Config.Entrypoint = nil
default:
// ENTRYPOINT echo hi
if runtime.GOOS != "windows" {
b.Config.Entrypoint = runconfig.NewEntrypoint("/bin/sh", "-c", parsed[0])
b.Config.Entrypoint = stringutils.NewStrSlice("/bin/sh", "-c", parsed[0])
} else {
b.Config.Entrypoint = runconfig.NewEntrypoint("cmd", "/S /C", parsed[0])
b.Config.Entrypoint = stringutils.NewStrSlice("cmd", "/S /C", parsed[0])
}
}

View File

@ -33,6 +33,7 @@ import (
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/progressreader"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/tarsum"
"github.com/docker/docker/pkg/urlutil"
@ -73,7 +74,7 @@ func (b *builder) readContext(context io.Reader) (err error) {
return
}
func (b *builder) commit(id string, autoCmd *runconfig.Command, comment string) error {
func (b *builder) commit(id string, autoCmd *stringutils.StrSlice, comment string) error {
if b.disableCommit {
return nil
}
@ -84,11 +85,11 @@ func (b *builder) commit(id string, autoCmd *runconfig.Command, comment string)
if id == "" {
cmd := b.Config.Cmd
if runtime.GOOS != "windows" {
b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", "#(nop) "+comment)
b.Config.Cmd = stringutils.NewStrSlice("/bin/sh", "-c", "#(nop) "+comment)
} else {
b.Config.Cmd = runconfig.NewCommand("cmd", "/S /C", "REM (nop) "+comment)
b.Config.Cmd = stringutils.NewStrSlice("cmd", "/S /C", "REM (nop) "+comment)
}
defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
defer func(cmd *stringutils.StrSlice) { b.Config.Cmd = cmd }(cmd)
hit, err := b.probeCache()
if err != nil {
@ -215,11 +216,11 @@ func (b *builder) runContextCommand(args []string, allowRemote bool, allowDecomp
cmd := b.Config.Cmd
if runtime.GOOS != "windows" {
b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest))
b.Config.Cmd = stringutils.NewStrSlice("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest))
} else {
b.Config.Cmd = runconfig.NewCommand("cmd", "/S /C", fmt.Sprintf("REM (nop) %s %s in %s", cmdName, srcHash, dest))
b.Config.Cmd = stringutils.NewStrSlice("cmd", "/S /C", fmt.Sprintf("REM (nop) %s %s in %s", cmdName, srcHash, dest))
}
defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
defer func(cmd *stringutils.StrSlice) { b.Config.Cmd = cmd }(cmd)
hit, err := b.probeCache()
if err != nil {
@ -630,7 +631,7 @@ func (b *builder) create() (*daemon.Container, error) {
c.Path = s[0]
c.Args = s[1:]
} else {
config.Cmd = runconfig.NewCommand()
config.Cmd = stringutils.NewStrSlice()
}
return c, nil

View File

@ -33,6 +33,7 @@ import (
"github.com/docker/docker/pkg/nat"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/pkg/sysinfo"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/truncindex"
@ -437,7 +438,7 @@ func (daemon *Daemon) generateHostname(id string, config *runconfig.Config) {
}
}
func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint *runconfig.Entrypoint, configCmd *runconfig.Command) (string, []string) {
func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint *stringutils.StrSlice, configCmd *stringutils.StrSlice) (string, []string) {
var (
entrypoint string
args []string

View File

@ -14,6 +14,7 @@ import (
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/runconfig"
)
@ -139,8 +140,8 @@ func (d *Daemon) ContainerExecCreate(config *runconfig.ExecConfig) (string, erro
return "", err
}
cmd := runconfig.NewCommand(config.Cmd...)
entrypoint, args := d.getEntrypointAndArgs(runconfig.NewEntrypoint(), cmd)
cmd := stringutils.NewStrSlice(config.Cmd...)
entrypoint, args := d.getEntrypointAndArgs(stringutils.NewStrSlice(), cmd)
user := config.User
if len(user) == 0 {

View File

@ -0,0 +1,71 @@
package stringutils
import (
"encoding/json"
"strings"
)
// StrSlice representes a string or an array of strings.
// We need to override the json decoder to accept both options.
type StrSlice struct {
parts []string
}
// MarshalJSON Marshals (or serializes) the StrSlice into the json format.
// This method is needed to implement json.Marshaller.
func (e *StrSlice) MarshalJSON() ([]byte, error) {
if e == nil {
return []byte{}, nil
}
return json.Marshal(e.Slice())
}
// UnmarshalJSON decodes the byte slice whether it's a string or an array of strings.
// This method is needed to implement json.Unmarshaler.
func (e *StrSlice) UnmarshalJSON(b []byte) error {
if len(b) == 0 {
return nil
}
p := make([]string, 0, 1)
if err := json.Unmarshal(b, &p); err != nil {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
p = append(p, s)
}
e.parts = p
return nil
}
// Len returns the number of parts of the StrSlice.
func (e *StrSlice) Len() int {
if e == nil {
return 0
}
return len(e.parts)
}
// Slice gets the parts of the StrSlice as a Slice of string.
func (e *StrSlice) Slice() []string {
if e == nil {
return nil
}
return e.parts
}
// ToString gets space separated string of all the parts.
func (e *StrSlice) ToString() string {
s := e.Slice()
if s == nil {
return ""
}
return strings.Join(s, " ")
}
// NewStrSlice creates an StrSlice based on the specified parts (as strings).
func NewStrSlice(parts ...string) *StrSlice {
return &StrSlice{parts}
}

View File

@ -0,0 +1,105 @@
package stringutils
import (
"encoding/json"
"testing"
)
func TestStrSliceMarshalJSON(t *testing.T) {
strss := map[*StrSlice]string{
nil: "",
&StrSlice{}: "null",
&StrSlice{[]string{"/bin/sh", "-c", "echo"}}: `["/bin/sh","-c","echo"]`,
}
for strs, expected := range strss {
data, err := strs.MarshalJSON()
if err != nil {
t.Fatal(err)
}
if string(data) != expected {
t.Fatalf("Expected %v, got %v", expected, string(data))
}
}
}
func TestStrSliceUnmarshalJSON(t *testing.T) {
parts := map[string][]string{
"": {"default", "values"},
"[]": {},
`["/bin/sh","-c","echo"]`: {"/bin/sh", "-c", "echo"},
}
for json, expectedParts := range parts {
strs := &StrSlice{
[]string{"default", "values"},
}
if err := strs.UnmarshalJSON([]byte(json)); err != nil {
t.Fatal(err)
}
actualParts := strs.Slice()
if len(actualParts) != len(expectedParts) {
t.Fatalf("Expected %v parts, got %v (%v)", len(expectedParts), len(actualParts), expectedParts)
}
for index, part := range actualParts {
if part != expectedParts[index] {
t.Fatalf("Expected %v, got %v", expectedParts, actualParts)
break
}
}
}
}
func TestStrSliceUnmarshalString(t *testing.T) {
var e *StrSlice
echo, err := json.Marshal("echo")
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(echo, &e); err != nil {
t.Fatal(err)
}
slice := e.Slice()
if len(slice) != 1 {
t.Fatalf("expected 1 element after unmarshal: %q", slice)
}
if slice[0] != "echo" {
t.Fatalf("expected `echo`, got: %q", slice[0])
}
}
func TestStrSliceUnmarshalSlice(t *testing.T) {
var e *StrSlice
echo, err := json.Marshal([]string{"echo"})
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(echo, &e); err != nil {
t.Fatal(err)
}
slice := e.Slice()
if len(slice) != 1 {
t.Fatalf("expected 1 element after unmarshal: %q", slice)
}
if slice[0] != "echo" {
t.Fatalf("expected `echo`, got: %q", slice[0])
}
}
func TestStrSliceToString(t *testing.T) {
slices := map[*StrSlice]string{
NewStrSlice(""): "",
NewStrSlice("one"): "one",
NewStrSlice("one", "two"): "one two",
}
for s, expected := range slices {
toString := s.ToString()
if toString != expected {
t.Fatalf("Expected %v, got %v", expected, toString)
}
}
}

View File

@ -4,6 +4,7 @@ import (
"testing"
"github.com/docker/docker/pkg/nat"
"github.com/docker/docker/pkg/stringutils"
)
// Just to make life easier
@ -32,12 +33,12 @@ func TestCompare(t *testing.T) {
volumes3["/test3"] = struct{}{}
envs1 := []string{"ENV1=value1", "ENV2=value2"}
envs2 := []string{"ENV1=value1", "ENV3=value3"}
entrypoint1 := &Entrypoint{parts: []string{"/bin/sh", "-c"}}
entrypoint2 := &Entrypoint{parts: []string{"/bin/sh", "-d"}}
entrypoint3 := &Entrypoint{parts: []string{"/bin/sh", "-c", "echo"}}
cmd1 := &Command{parts: []string{"/bin/sh", "-c"}}
cmd2 := &Command{parts: []string{"/bin/sh", "-d"}}
cmd3 := &Command{parts: []string{"/bin/sh", "-c", "echo"}}
entrypoint1 := stringutils.NewStrSlice("/bin/sh", "-c")
entrypoint2 := stringutils.NewStrSlice("/bin/sh", "-d")
entrypoint3 := stringutils.NewStrSlice("/bin/sh", "-c", "echo")
cmd1 := stringutils.NewStrSlice("/bin/sh", "-c")
cmd2 := stringutils.NewStrSlice("/bin/sh", "-d")
cmd3 := stringutils.NewStrSlice("/bin/sh", "-c", "echo")
labels1 := map[string]string{"LABEL1": "value1", "LABEL2": "value2"}
labels2 := map[string]string{"LABEL1": "value1", "LABEL2": "value3"}
labels3 := map[string]string{"LABEL1": "value1", "LABEL2": "value2", "LABEL3": "value3"}

View File

@ -3,132 +3,11 @@ package runconfig
import (
"encoding/json"
"io"
"strings"
"github.com/docker/docker/pkg/nat"
"github.com/docker/docker/pkg/stringutils"
)
// Entrypoint encapsulates the container entrypoint.
// It might be represented as a string or an array of strings.
// We need to override the json decoder to accept both options.
// The JSON decoder will fail if the api sends an string and
// we try to decode it into an array of string.
type Entrypoint struct {
parts []string
}
// MarshalJSON Marshals (or serializes) the Entrypoint into the json format.
// This method is needed to implement json.Marshaller.
func (e *Entrypoint) MarshalJSON() ([]byte, error) {
if e == nil {
return []byte{}, nil
}
return json.Marshal(e.Slice())
}
// UnmarshalJSON decodes the entrypoint whether it's a string or an array of strings.
// This method is needed to implement json.Unmarshaler.
func (e *Entrypoint) UnmarshalJSON(b []byte) error {
if len(b) == 0 {
return nil
}
p := make([]string, 0, 1)
if err := json.Unmarshal(b, &p); err != nil {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
p = append(p, s)
}
e.parts = p
return nil
}
// Len returns the number of parts of the Entrypoint.
func (e *Entrypoint) Len() int {
if e == nil {
return 0
}
return len(e.parts)
}
// Slice gets the parts of the Entrypoint as a Slice of string.
func (e *Entrypoint) Slice() []string {
if e == nil {
return nil
}
return e.parts
}
// NewEntrypoint creates an Entrypoint based on the specified parts (as strings).
func NewEntrypoint(parts ...string) *Entrypoint {
return &Entrypoint{parts}
}
// Command encapsulates the container command.
// It might be represented as a string or an array of strings.
// We need to override the json decoder to accept both options.
// The JSON decoder will fail if the api sends an string and
// we try to decode it into an array of string.
type Command struct {
parts []string
}
// ToString gets a string representing a Command.
func (e *Command) ToString() string {
return strings.Join(e.parts, " ")
}
// MarshalJSON Marshals (or serializes) the Command into the json format.
// This method is needed to implement json.Marshaller.
func (e *Command) MarshalJSON() ([]byte, error) {
if e == nil {
return []byte{}, nil
}
return json.Marshal(e.Slice())
}
// UnmarshalJSON decodes the entrypoint whether it's a string or an array of strings.
// This method is needed to implement json.Unmarshaler.
func (e *Command) UnmarshalJSON(b []byte) error {
if len(b) == 0 {
return nil
}
p := make([]string, 0, 1)
if err := json.Unmarshal(b, &p); err != nil {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
p = append(p, s)
}
e.parts = p
return nil
}
// Len returns the number of parts of the Entrypoint.
func (e *Command) Len() int {
if e == nil {
return 0
}
return len(e.parts)
}
// Slice gets the parts of the Entrypoint as a Slice of string.
func (e *Command) Slice() []string {
if e == nil {
return nil
}
return e.parts
}
// NewCommand creates a Command based on the specified parts (as strings).
func NewCommand(parts ...string) *Command {
return &Command{parts}
}
// Config contains the configuration data about a container.
// It should hold only portable information about the container.
// Here, "portable" means "independent from the host we are running on".
@ -146,12 +25,12 @@ type Config struct {
OpenStdin bool // Open stdin
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
Env []string // List of environment variable to set in the container
Cmd *Command // Command to run when starting the container
Cmd *stringutils.StrSlice // Command to run when starting the container
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
Volumes map[string]struct{} // List of volumes (mounts) used for the container
VolumeDriver string // Name of the volume driver used to mount volumes
WorkingDir string // Current directory (PWD) in the command will be launched
Entrypoint *Entrypoint // Entrypoint to run when starting the container
Entrypoint *stringutils.StrSlice // Entrypoint to run when starting the container
NetworkDisabled bool // Is network disabled
MacAddress string // Mac Address of the container
OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile

View File

@ -2,124 +2,21 @@ package runconfig
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"testing"
"github.com/docker/docker/pkg/stringutils"
)
func TestEntrypointMarshalJSON(t *testing.T) {
entrypoints := map[*Entrypoint]string{
nil: "",
&Entrypoint{}: "null",
&Entrypoint{[]string{"/bin/sh", "-c", "echo"}}: `["/bin/sh","-c","echo"]`,
}
for entrypoint, expected := range entrypoints {
data, err := entrypoint.MarshalJSON()
if err != nil {
t.Fatal(err)
}
if string(data) != expected {
t.Fatalf("Expected %v, got %v", expected, string(data))
}
}
}
func TestEntrypointUnmarshalJSON(t *testing.T) {
parts := map[string][]string{
"": {"default", "values"},
"[]": {},
`["/bin/sh","-c","echo"]`: {"/bin/sh", "-c", "echo"},
}
for json, expectedParts := range parts {
entrypoint := &Entrypoint{
[]string{"default", "values"},
}
if err := entrypoint.UnmarshalJSON([]byte(json)); err != nil {
t.Fatal(err)
}
actualParts := entrypoint.Slice()
if len(actualParts) != len(expectedParts) {
t.Fatalf("Expected %v parts, got %v (%v)", len(expectedParts), len(actualParts), expectedParts)
}
for index, part := range actualParts {
if part != expectedParts[index] {
t.Fatalf("Expected %v, got %v", expectedParts, actualParts)
break
}
}
}
}
func TestCommandToString(t *testing.T) {
commands := map[*Command]string{
&Command{[]string{""}}: "",
&Command{[]string{"one"}}: "one",
&Command{[]string{"one", "two"}}: "one two",
}
for command, expected := range commands {
toString := command.ToString()
if toString != expected {
t.Fatalf("Expected %v, got %v", expected, toString)
}
}
}
func TestCommandMarshalJSON(t *testing.T) {
commands := map[*Command]string{
nil: "",
&Command{}: "null",
&Command{[]string{"/bin/sh", "-c", "echo"}}: `["/bin/sh","-c","echo"]`,
}
for command, expected := range commands {
data, err := command.MarshalJSON()
if err != nil {
t.Fatal(err)
}
if string(data) != expected {
t.Fatalf("Expected %v, got %v", expected, string(data))
}
}
}
func TestCommandUnmarshalJSON(t *testing.T) {
parts := map[string][]string{
"": {"default", "values"},
"[]": {},
`["/bin/sh","-c","echo"]`: {"/bin/sh", "-c", "echo"},
}
for json, expectedParts := range parts {
command := &Command{
[]string{"default", "values"},
}
if err := command.UnmarshalJSON([]byte(json)); err != nil {
t.Fatal(err)
}
actualParts := command.Slice()
if len(actualParts) != len(expectedParts) {
t.Fatalf("Expected %v parts, got %v (%v)", len(expectedParts), len(actualParts), expectedParts)
}
for index, part := range actualParts {
if part != expectedParts[index] {
t.Fatalf("Expected %v, got %v", expectedParts, actualParts)
break
}
}
}
}
func TestDecodeContainerConfig(t *testing.T) {
fixtures := []struct {
file string
entrypoint *Entrypoint
entrypoint *stringutils.StrSlice
}{
{"fixtures/container_config_1_14.json", NewEntrypoint()},
{"fixtures/container_config_1_17.json", NewEntrypoint("bash")},
{"fixtures/container_config_1_19.json", NewEntrypoint("bash")},
{"fixtures/container_config_1_14.json", stringutils.NewStrSlice()},
{"fixtures/container_config_1_17.json", stringutils.NewStrSlice("bash")},
{"fixtures/container_config_1_19.json", stringutils.NewStrSlice("bash")},
}
for _, f := range fixtures {
@ -146,83 +43,3 @@ func TestDecodeContainerConfig(t *testing.T) {
}
}
}
func TestEntrypointUnmarshalString(t *testing.T) {
var e *Entrypoint
echo, err := json.Marshal("echo")
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(echo, &e); err != nil {
t.Fatal(err)
}
slice := e.Slice()
if len(slice) != 1 {
t.Fatalf("expected 1 element after unmarshal: %q", slice)
}
if slice[0] != "echo" {
t.Fatalf("expected `echo`, got: %q", slice[0])
}
}
func TestEntrypointUnmarshalSlice(t *testing.T) {
var e *Entrypoint
echo, err := json.Marshal([]string{"echo"})
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(echo, &e); err != nil {
t.Fatal(err)
}
slice := e.Slice()
if len(slice) != 1 {
t.Fatalf("expected 1 element after unmarshal: %q", slice)
}
if slice[0] != "echo" {
t.Fatalf("expected `echo`, got: %q", slice[0])
}
}
func TestCommandUnmarshalSlice(t *testing.T) {
var e *Command
echo, err := json.Marshal([]string{"echo"})
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(echo, &e); err != nil {
t.Fatal(err)
}
slice := e.Slice()
if len(slice) != 1 {
t.Fatalf("expected 1 element after unmarshal: %q", slice)
}
if slice[0] != "echo" {
t.Fatalf("expected `echo`, got: %q", slice[0])
}
}
func TestCommandUnmarshalString(t *testing.T) {
var e *Command
echo, err := json.Marshal("echo")
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(echo, &e); err != nil {
t.Fatal(err)
}
slice := e.Slice()
if len(slice) != 1 {
t.Fatalf("expected 1 element after unmarshal: %q", slice)
}
if slice[0] != "echo" {
t.Fatalf("expected `echo`, got: %q", slice[0])
}
}

View File

@ -9,6 +9,7 @@ import (
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/nat"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/pkg/units"
)
@ -199,15 +200,15 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
var (
parsedArgs = cmd.Args()
runCmd *Command
entrypoint *Entrypoint
runCmd *stringutils.StrSlice
entrypoint *stringutils.StrSlice
image = cmd.Arg(0)
)
if len(parsedArgs) > 1 {
runCmd = NewCommand(parsedArgs[1:]...)
runCmd = stringutils.NewStrSlice(parsedArgs[1:]...)
}
if *flEntrypoint != "" {
entrypoint = NewEntrypoint(*flEntrypoint)
entrypoint = stringutils.NewStrSlice(*flEntrypoint)
}
lc, err := parseKeyValueOpts(flLxcOpts)

View File

@ -486,7 +486,7 @@ func TestParseEntryPoint(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if config.Entrypoint.Len() != 1 && config.Entrypoint.parts[0] != "anything" {
if config.Entrypoint.Len() != 1 && config.Entrypoint.Slice()[0] != "anything" {
t.Fatalf("Expected entrypoint 'anything', got %v", config.Entrypoint)
}
}