Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #439 Strip out double quotes in ARG value #834

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions integration/dockerfiles/Dockerfile_test_arg_blank_with_quotes
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
ARG FILE_NAME=""

FROM busybox:latest AS builder
ARG FILE_NAME

RUN echo $FILE_NAME && touch /$FILE_NAME.txt && stat /$FILE_NAME.txt;

FROM busybox:latest
ARG FILE_NAME

RUN echo $FILE_NAME && touch /$FILE_NAME.txt && stat /$FILE_NAME.txt;
COPY --from=builder /$FILE_NAME.txt /
3 changes: 3 additions & 0 deletions integration/dockerfiles/Dockerfile_test_arg_from_quotes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ARG IMAGE_NAME="busybox:latest"

FROM $IMAGE_NAME
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ARG IMAGE_NAME='busybox:latest'

FROM $IMAGE_NAME
12 changes: 12 additions & 0 deletions integration/dockerfiles/Dockerfile_test_arg_multi_with_quotes
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
ARG FILE_NAME="myFile"

FROM busybox:latest AS builder
ARG FILE_NAME

RUN echo $FILE_NAME && touch /$FILE_NAME.txt && stat /$FILE_NAME.txt;

FROM busybox:latest
ARG FILE_NAME

RUN echo $FILE_NAME && touch /$FILE_NAME.txt && stat /$FILE_NAME.txt;
COPY --from=builder /$FILE_NAME.txt /
35 changes: 35 additions & 0 deletions pkg/dockerfile/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,44 @@ func Parse(b []byte) ([]instructions.Stage, []instructions.ArgCommand, error) {
if err != nil {
return nil, nil, err
}

metaArgs, err = stripEnclosingQuotes(metaArgs)
if err != nil {
return nil, nil, err
}

return stages, metaArgs, nil
}

// stripEnclosingQuotes removes quotes enclosing the value of each instructions.ArgCommand in a slice
// if the quotes are escaped it leaves them
func stripEnclosingQuotes(metaArgs []instructions.ArgCommand) ([]instructions.ArgCommand, error) {
dbl := byte('"')
sgl := byte('\'')

for i := range metaArgs {
arg := metaArgs[i]
v := arg.Value
if v != nil {
val := *v
fmt.Printf("val %s\n", val)
if (val[0] == dbl && val[len(val)-1] == dbl) || (val[0] == sgl && val[len(val)-1] == sgl) {
val = val[1 : len(val)-1]
} else if val[:2] == `\"` && val[len(val)-2:] == `\"` {
continue
} else if val[:2] == `\'` && val[len(val)-2:] == `\'` {
continue
} else if val[0] == dbl || val[0] == sgl || val[len(val)-1] == dbl || val[len(val)-1] == sgl {
return nil, errors.New("quotes wrapping arg values must be matched")
}

arg.Value = &val
metaArgs[i] = arg
}
}
return metaArgs, nil
}

// targetStage returns the index of the target stage kaniko is trying to build
func targetStage(stages []instructions.Stage, target string) (int, error) {
if target == "" {
Expand Down
157 changes: 157 additions & 0 deletions pkg/dockerfile/dockerfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,170 @@ limitations under the License.
package dockerfile

import (
"io/ioutil"
"os"
"strconv"
"testing"

"github.com/GoogleContainerTools/kaniko/pkg/config"
"github.com/GoogleContainerTools/kaniko/testutil"
"github.com/moby/buildkit/frontend/dockerfile/instructions"
)

func Test_Stages_ArgValueWithQuotes(t *testing.T) {
dockerfile := `
ARG IMAGE="ubuntu:16.04"
FROM ${IMAGE}
RUN echo hi > /hi

FROM scratch AS second
COPY --from=0 /hi /hi2

FROM scratch
COPY --from=second /hi2 /hi3
`
tmpfile, err := ioutil.TempFile("", "Dockerfile.test")
if err != nil {
t.Fatal(err)
}

defer os.Remove(tmpfile.Name())

if _, err := tmpfile.Write([]byte(dockerfile)); err != nil {
t.Fatal(err)
}
if err := tmpfile.Close(); err != nil {
t.Fatal(err)
}

stages, err := Stages(&config.KanikoOptions{DockerfilePath: tmpfile.Name()})
if err != nil {
t.Fatal(err)
}

if len(stages) == 0 {
t.Fatal("length of stages expected to be greater than zero, but was zero")

}

if len(stages[0].MetaArgs) == 0 {
t.Fatal("length of stage[0] meta args expected to be greater than zero, but was zero")
}

expectedVal := "ubuntu:16.04"

arg := stages[0].MetaArgs[0]
if arg.ValueString() != expectedVal {
t.Fatalf("expected stages[0].MetaArgs[0] val to be %s but was %s", expectedVal, arg.ValueString())
}
}

func Test_stripEnclosingQuotes(t *testing.T) {
type testCase struct {
name string
inArgs []instructions.ArgCommand
expected []string
success bool
}

newArgCommand := func(key, val string) instructions.ArgCommand {
return instructions.ArgCommand{
KeyValuePairOptional: instructions.KeyValuePairOptional{Key: key, Value: &val},
}
}

cases := []testCase{{
name: "value with no enclosing quotes",
inArgs: []instructions.ArgCommand{newArgCommand("MEOW", "Purr")},
expected: []string{"Purr"},
success: true,
}, {
name: "value with unmatched leading double quote",
inArgs: []instructions.ArgCommand{newArgCommand("MEOW", "\"Purr")},
}, {
name: "value with unmatched trailing double quote",
inArgs: []instructions.ArgCommand{newArgCommand("MEOW", "Purr\"")},
}, {
name: "value with enclosing double quotes",
inArgs: []instructions.ArgCommand{newArgCommand("MEOW", "\"mrow\"")},
expected: []string{"mrow"},
success: true,
}, {
name: "value with unmatched leading single quote",
inArgs: []instructions.ArgCommand{newArgCommand("MEOW", "'Purr")},
}, {
name: "value with unmatched trailing single quote",
inArgs: []instructions.ArgCommand{newArgCommand("MEOW", "Purr'")},
}, {
name: "value with enclosing single quotes",
inArgs: []instructions.ArgCommand{newArgCommand("MEOW", "'mrow'")},
expected: []string{"mrow"},
success: true,
}, {
name: "blank value with enclosing double quotes",
inArgs: []instructions.ArgCommand{newArgCommand("MEOW", "\"\"")},
expected: []string{""},
success: true,
}, {
name: "blank value with enclosing single quotes",
inArgs: []instructions.ArgCommand{newArgCommand("MEOW", "''")},
expected: []string{""},
success: true,
}, {
name: "value with escaped, enclosing double quotes",
inArgs: []instructions.ArgCommand{newArgCommand("MEOW", `\"Purr\"`)},
expected: []string{`\"Purr\"`},
success: true,
}, {
name: "value with escaped, enclosing single quotes",
inArgs: []instructions.ArgCommand{newArgCommand("MEOW", `\'Purr\'`)},
expected: []string{`\'Purr\'`},
success: true,
}, {
name: "multiple values enclosed with single quotes",
inArgs: []instructions.ArgCommand{
newArgCommand("MEOW", `'Purr'`),
newArgCommand("MEW", `'Mrow'`),
},
expected: []string{"Purr", "Mrow"},
success: true,
}, {
name: "no values",
success: true,
}}

for _, test := range cases {
t.Run(test.name, func(t *testing.T) {
inArgs := test.inArgs
expected := test.expected
success := test.success

out, err := stripEnclosingQuotes(inArgs)
if success && err != nil {
t.Fatal(err)
}

if !success && err == nil {
t.Fatal("expected an error but none received")
}

if len(expected) != len(out) {
t.Fatalf("Expected %d args but got %d", len(expected), len(out))
}

for i := range out {
if expected[i] != out[i].ValueString() {
t.Errorf(
"Expected arg at index %d to equal %v but instead equaled %v",
i,
expected[i],
out[i].ValueString())
}
}
})
}
}

func Test_resolveStages(t *testing.T) {
dockerfile := `
FROM scratch
Expand Down