diff --git a/fork/golang/expansion/expand.go b/fork/golang/expansion/expand.go index 6bf0ea8ce09..32bf62e9875 100644 --- a/fork/golang/expansion/expand.go +++ b/fork/golang/expansion/expand.go @@ -2,17 +2,20 @@ package expansion import ( "bytes" + "unicode" ) const ( - operator = '$' - referenceOpener = '(' - referenceCloser = ')' + operator = '$' + referenceCurlyOpener = '{' + referenceCurlyCloser = '}' + referenceParenOpener = '(' + referenceParenCloser = ')' ) // syntaxWrap returns the input string wrapped by the expansion syntax. func syntaxWrap(input string) string { - return string(operator) + string(referenceOpener) + input + string(referenceCloser) + return string(operator) + string(referenceParenOpener) + input + string(referenceParenCloser) } // MappingFuncFor returns a mapping function for use with Expand that @@ -83,20 +86,41 @@ func tryReadVariableName(input string) (string, bool, int) { case operator: // Escaped operator; return it. return input[0:1], false, 1 - case referenceOpener: + case referenceParenOpener: // Scan to expression closer for i := 1; i < len(input); i++ { - if input[i] == referenceCloser { + if input[i] == referenceParenCloser { return input[1:i], true, i + 1 } } // Incomplete reference; return it. - return string(operator) + string(referenceOpener), false, 1 + return string(operator) + string(referenceParenOpener), false, 1 + case referenceCurlyOpener: + // Scan to expression closer + for i := 1; i < len(input); i++ { + if input[i] == referenceCurlyCloser { + return input[1:i], true, i + 1 + } + } + + // Incomplete reference; return it. + return string(operator) + string(referenceParenOpener), false, 1 default: // Not the beginning of an expression, ie, an operator // that doesn't begin an expression. Return the operator // and the first rune in the string. - return (string(operator) + string(input[0])), false, 1 + var variableName string + var i int + for i = 0; i < len(input); i++ { + if !unicode.IsLetter(rune(input[i])) && !unicode.IsDigit(rune(input[i])) && input[i] != '_' { + break + } + variableName += string(input[i]) + } + if len(variableName) > 0 { + return variableName, true, i + } + return string(operator) + string(input[0]), false, 1 } } diff --git a/pkg/guest/guest_test.go b/pkg/guest/guest_test.go index d63e9e3fff8..638c187891b 100644 --- a/pkg/guest/guest_test.go +++ b/pkg/guest/guest_test.go @@ -135,3 +135,223 @@ func TestDefault_getGuestCmd(t *testing.T) { }) } } + +func TestDefault_getGuestCmd_dollarBraceVariable(t *testing.T) { + shell := func(cName, containerName, cmd string) string { + return fmt.Sprintf(constants.CdAndExecCmd, constants.GetAppWorkDir(cName, containerName), cmd) + } + type fields struct { + } + type args struct { + envs map[string]string + cluster *v2.Cluster + mounts []v2.MountImage + } + tests := []struct { + name string + fields fields + args args + want []string + }{ + { + name: "default", + fields: fields{}, + args: args{ + envs: map[string]string{}, + cluster: &v2.Cluster{Spec: v2.ClusterSpec{Command: nil}}, + mounts: []v2.MountImage{ + { + Cmd: []string{"IFACE=${IFACE} bash ovn-install.sh"}, + Entrypoint: nil, + Env: map[string]string{"IFACE": "cccc"}, + }, + }, + }, + want: []string{shell("", "", "IFACE=cccc bash ovn-install.sh")}, + }, + + { + name: "default-env", + fields: fields{}, + args: args{ + envs: map[string]string{}, + cluster: &v2.Cluster{Spec: v2.ClusterSpec{Command: nil}}, + mounts: []v2.MountImage{ + { + Cmd: []string{"IFACE=\"${IFACE}\" bash ovn-install.sh"}, + Entrypoint: nil, + Env: map[string]string{"IFACE": "eth.*|en.*"}, + }, + }, + }, + want: []string{shell("", "", "IFACE=\"eth.*|en.*\" bash ovn-install.sh")}, + }, + { + name: "default-env-override", + fields: fields{}, + args: args{ + envs: map[string]string{"IFACE": "default"}, + cluster: &v2.Cluster{Spec: v2.ClusterSpec{Command: nil}}, + mounts: []v2.MountImage{ + { + Cmd: []string{"IFACE=\"${IFACE}\" bash ovn-install.sh"}, + Entrypoint: nil, + Env: map[string]string{"IFACE": "eth.*|en.*"}, + }, + }, + }, + want: []string{shell("", "", "IFACE=\"default\" bash ovn-install.sh")}, + }, + { + name: "default-cmd-override", + fields: fields{}, + args: args{ + envs: map[string]string{"IFACE": "default"}, + cluster: &v2.Cluster{Spec: v2.ClusterSpec{Command: []string{"IFACE=\"${IFACE}\" sh ovn-install.sh"}}}, + mounts: []v2.MountImage{ + { + Cmd: []string{"IFACE=\"${IFACE}\" bash ovn-install.sh"}, + Entrypoint: nil, + Env: map[string]string{"IFACE": "eth.*|en.*"}, + }, + }, + }, + want: []string{shell("", "", "IFACE=\"default\" sh ovn-install.sh")}, + }, + { + name: "default-entrypoint-cmd-override", + fields: fields{}, + args: args{ + envs: map[string]string{"IFACE": "default"}, + cluster: &v2.Cluster{Spec: v2.ClusterSpec{Command: []string{"IFACE=\"${IFACE}\" sh ovn-install.sh"}}}, + mounts: []v2.MountImage{ + { + Cmd: []string{"IFACE=\"${IFACE}\" bash ovn-install.sh"}, + Entrypoint: []string{"AA=${IFACE}"}, + Env: map[string]string{"IFACE": "eth.*|en.*"}, + }, + }, + }, + want: []string{shell("", "", "AA=default"), shell("", "", "IFACE=\"default\" sh ovn-install.sh")}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for i := range tt.args.mounts { + if got := formalizeImageCommands(tt.args.cluster, i, tt.args.mounts[i], tt.args.envs); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getGuestCmd() = %v, want %v", got, tt.want) + } + } + }) + } +} + +func TestDefault_getGuestCmd_dollarVariable(t *testing.T) { + shell := func(cName, containerName, cmd string) string { + return fmt.Sprintf(constants.CdAndExecCmd, constants.GetAppWorkDir(cName, containerName), cmd) + } + type fields struct { + } + type args struct { + envs map[string]string + cluster *v2.Cluster + mounts []v2.MountImage + } + tests := []struct { + name string + fields fields + args args + want []string + }{ + { + name: "default", + fields: fields{}, + args: args{ + envs: map[string]string{}, + cluster: &v2.Cluster{Spec: v2.ClusterSpec{Command: nil}}, + mounts: []v2.MountImage{ + { + Cmd: []string{"IFACE=$IFACE bash ovn-install.sh"}, + Entrypoint: nil, + Env: map[string]string{"IFACE": "cccc"}, + }, + }, + }, + want: []string{shell("", "", "IFACE=cccc bash ovn-install.sh")}, + }, + + { + name: "default-env", + fields: fields{}, + args: args{ + envs: map[string]string{}, + cluster: &v2.Cluster{Spec: v2.ClusterSpec{Command: nil}}, + mounts: []v2.MountImage{ + { + Cmd: []string{"IFACE=\"$IFACE\" bash ovn-install.sh"}, + Entrypoint: nil, + Env: map[string]string{"IFACE": "eth.*|en.*"}, + }, + }, + }, + want: []string{shell("", "", "IFACE=\"eth.*|en.*\" bash ovn-install.sh")}, + }, + { + name: "default-env-override", + fields: fields{}, + args: args{ + envs: map[string]string{"IFACE": "default"}, + cluster: &v2.Cluster{Spec: v2.ClusterSpec{Command: nil}}, + mounts: []v2.MountImage{ + { + Cmd: []string{"IFACE=\"$IFACE\" bash ovn-install.sh"}, + Entrypoint: nil, + Env: map[string]string{"IFACE": "eth.*|en.*"}, + }, + }, + }, + want: []string{shell("", "", "IFACE=\"default\" bash ovn-install.sh")}, + }, + { + name: "default-cmd-override", + fields: fields{}, + args: args{ + envs: map[string]string{"IFACE": "default"}, + cluster: &v2.Cluster{Spec: v2.ClusterSpec{Command: []string{"IFACE=\"$IFACE\" sh ovn-install.sh"}}}, + mounts: []v2.MountImage{ + { + Cmd: []string{"IFACE=\"$IFACE\" bash ovn-install.sh"}, + Entrypoint: nil, + Env: map[string]string{"IFACE": "eth.*|en.*"}, + }, + }, + }, + want: []string{shell("", "", "IFACE=\"default\" sh ovn-install.sh")}, + }, + { + name: "default-entrypoint-cmd-override", + fields: fields{}, + args: args{ + envs: map[string]string{"IFACE": "default"}, + cluster: &v2.Cluster{Spec: v2.ClusterSpec{Command: []string{"IFACE=\"$IFACE\" sh ovn-install.sh"}}}, + mounts: []v2.MountImage{ + { + Cmd: []string{"IFACE=\"$IFACE\" bash ovn-install.sh"}, + Entrypoint: []string{"AA=$IFACE"}, + Env: map[string]string{"IFACE": "eth.*|en.*"}, + }, + }, + }, + want: []string{shell("", "", "AA=default"), shell("", "", "IFACE=\"default\" sh ovn-install.sh")}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for i := range tt.args.mounts { + if got := formalizeImageCommands(tt.args.cluster, i, tt.args.mounts[i], tt.args.envs); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getGuestCmd() = %v, want %v", got, tt.want) + } + } + }) + } +}