func TestReplaceLastFrom(t *testing.T) { tests := []struct { original string image string want string }{ { original: `# no FROM instruction`, image: "centos", want: ``, }, { original: `FROM scratch # FROM busybox RUN echo "hello world" `, image: "centos", want: `FROM centos RUN echo "hello world" `, }, { original: `FROM scratch FROM busybox RUN echo "hello world" `, image: "centos", want: `FROM scratch FROM centos RUN echo "hello world" `, }, } for i, test := range tests { got, err := parser.Parse(strings.NewReader(test.original)) if err != nil { t.Errorf("test[%d]: %v", i, err) continue } want, err := parser.Parse(strings.NewReader(test.want)) if err != nil { t.Errorf("test[%d]: %v", i, err) continue } replaceLastFrom(got, test.image) if !bytes.Equal(dockerfile.ParseTreeToDockerfile(got), dockerfile.ParseTreeToDockerfile(want)) { t.Errorf("test[%d]: replaceLastFrom(node, %+v) = %+v; want %+v", i, test.image, got, want) t.Logf("resulting Dockerfile:\n%s", dockerfile.ParseTreeToDockerfile(got)) } } }
// FromImage updates the builder to use the provided image (resetting RunConfig // and recording the image environment), and updates the node with any ONBUILD // statements extracted from the parent image. func (b *Builder) FromImage(image *docker.Image, node *parser.Node) error { SplitChildren(node, command.From) b.RunConfig = *image.Config b.Env = b.RunConfig.Env b.RunConfig.Env = nil // Check to see if we have a default PATH, note that windows won't // have one as its set by HCS if runtime.GOOS != "windows" && !hasEnvName(b.Env, "PATH") { b.RunConfig.Env = append(b.RunConfig.Env, "PATH="+defaultPathEnv) } // Join the image onbuild statements into node if image.Config == nil || len(image.Config.OnBuild) == 0 { return nil } extra, err := parser.Parse(bytes.NewBufferString(strings.Join(image.Config.OnBuild, "\n"))) if err != nil { return err } for _, child := range extra.Children { switch strings.ToUpper(child.Value) { case "ONBUILD": return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") case "MAINTAINER", "FROM": return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", child.Value) } } node.Children = append(extra.Children, node.Children...) // Since we've processed the OnBuild statements, clear them from the runconfig state. b.RunConfig.OnBuild = nil return nil }
// NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config. // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName, // will be read from the Context passed to Build(). func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, backend builder.Backend, buildContext builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) { if config == nil { config = new(types.ImageBuildOptions) } if config.BuildArgs == nil { config.BuildArgs = make(map[string]string) } ctx, cancel := context.WithCancel(clientCtx) b = &Builder{ clientCtx: ctx, cancel: cancel, options: config, Stdout: os.Stdout, Stderr: os.Stderr, docker: backend, context: buildContext, runConfig: new(container.Config), tmpContainers: map[string]struct{}{}, id: stringid.GenerateNonCryptoID(), allowedBuildArgs: make(map[string]bool), } if dockerfile != nil { b.dockerfile, err = parser.Parse(dockerfile) if err != nil { return nil, err } } return b, nil }
// BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile // It will: // - Call parse.Parse() to get an AST root for the concatenated Dockerfile entries. // - Do build by calling builder.dispatch() to call all entries' handling routines // // BuildFromConfig is used by the /commit endpoint, with the changes // coming from the query parameter of the same name. // // TODO: Remove? func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) { ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) if err != nil { return nil, err } // ensure that the commands are valid for _, n := range ast.Children { if !validCommitCommands[n.Value] { return nil, fmt.Errorf("%s is not a valid change command", n.Value) } } b, err := NewBuilder(context.Background(), nil, nil, nil, nil) if err != nil { return nil, err } b.runConfig = config b.Stdout = ioutil.Discard b.Stderr = ioutil.Discard b.disableCommit = true for i, n := range ast.Children { if err := b.dispatch(i, n); err != nil { return nil, err } } return b.runConfig, nil }
func main() { var f *os.File var err error if len(os.Args) < 2 { fmt.Println("please supply filename(s)") os.Exit(1) } for _, fn := range os.Args[1:] { f, err = os.Open(fn) if err != nil { panic(err) } d := parser.Directive{LookingForDirectives: true} parser.SetEscapeToken(parser.DefaultEscapeToken, &d) ast, err := parser.Parse(f, &d) if err != nil { panic(err) } else { fmt.Println(ast.Dump()) } } }
// TestNextValuesOnbuild tests calling nextValues with ONBUILD instructions as // input. func TestNextValuesOnbuild(t *testing.T) { testCases := map[string][]string{ `ONBUILD ADD . /app/src`: {".", "/app/src"}, `ONBUILD RUN echo "Hello universe!"`: {`echo "Hello universe!"`}, } for original, want := range testCases { node, err := parser.Parse(strings.NewReader(original)) if err != nil { t.Fatalf("parse error: %s: %v", original, err) } if len(node.Children) != 1 { t.Fatalf("unexpected number of children in test case: %s", original) } // The Docker parser always wrap instructions in a root node. // Look at the node representing the instruction following // ONBUILD, the one and only one in each test case. node = node.Children[0].Next if node == nil || len(node.Children) != 1 { t.Fatalf("unexpected number of children in ONBUILD instruction of test case: %s", original) } node = node.Children[0] if got := nextValues(node); !reflect.DeepEqual(got, want) { t.Errorf("nextValues(%+v) = %#v; want %#v", node, got, want) } } }
// NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config. // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName, // will be read from the Context passed to Build(). func NewBuilder(config *Config, docker builder.Backend, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) { if config == nil { config = new(Config) } if config.BuildArgs == nil { config.BuildArgs = make(map[string]string) } b = &Builder{ Config: config, Stdout: os.Stdout, Stderr: os.Stderr, docker: docker, context: context, runConfig: new(container.Config), tmpContainers: map[string]struct{}{}, cancelled: make(chan struct{}), id: stringid.GenerateNonCryptoID(), allowedBuildArgs: make(map[string]bool), } if dockerfile != nil { b.dockerfile, err = parser.Parse(dockerfile) if err != nil { return nil, err } } return b, nil }
func TestRun(t *testing.T) { f, err := os.Open("dockerclient/testdata/Dockerfile.add") if err != nil { t.Fatal(err) } node, err := parser.Parse(f) if err != nil { t.Fatal(err) } b := NewBuilder() from, err := b.From(node) if err != nil { t.Fatal(err) } if from != "busybox" { t.Fatalf("unexpected from: %s", from) } for _, child := range node.Children { step := b.Step() if err := step.Resolve(child); err != nil { t.Fatal(err) } if err := b.Run(step, LogExecutor); err != nil { t.Fatal(err) } } t.Logf("config: %#v", b.Config()) t.Logf(node.Dump()) }
func get_label_value(dockerfile_name string, label_name string) (label_value string, err error) { f, err := os.Open(dockerfile_name) if err != nil { return } defer f.Close() ast, err := parser.Parse(f) if err != nil { return } // root node only has children and represents the entire file for _, n := range ast.Children { if n.Value == "label" { // found a label statement, see if it has our label for nn := n.Next; nn != nil; nn = nn.Next.Next { if nn.Value == label_name { nn = nn.Next label_value, err = strconv.Unquote(strings.Replace(nn.Value, "$ {", "${", -1)) return } } } } return "", nil }
//GuessFromDockerfile will guess some information from a Dockerfile file //guessing means to get all the LABELs and process them somehow func GuessFromDockerfile(filename string) (map[string]string, string, error) { dockerfileContent, err := ioutil.ReadFile(filename) if err != nil { jww.ERROR.Println("failed to read the Dockerfile") return nil, "", err } // lets parse the Dockerfile ast, err := parser.Parse(bytes.NewReader(dockerfileContent)) if err != nil { jww.FATAL.Println("Dockerfile parse error") return nil, "", err } var result map[string]string result = make(map[string]string) for _, s := range guessFromLabels(ast) { result[strings.Replace(s.Key, "\"", "", -1)] = strings.Replace(s.Value, "\"", "", -1) } for key, value := range result { jww.DEBUG.Printf("result: LABEL: %s;\t VALUE: %s\n", key, value) } // if --experimental is true, we print all snippets to STDOUT resultingNulecule := "" if viper.GetBool("Experimental") { resultingNulecule = snippetsFromLabelsMap(result) } return result, resultingNulecule, err }
// TestNextValues tests calling nextValues with multiple valid combinations of // input. func TestNextValues(t *testing.T) { testCases := map[string][]string{ `FROM busybox:latest`: {"busybox:latest"}, `MAINTAINER [email protected]`: {"*****@*****.**"}, `LABEL version=1.0`: {"version", "1.0"}, `EXPOSE 8080`: {"8080"}, `VOLUME /var/run/www`: {"/var/run/www"}, `ENV PATH=/bin`: {"PATH", "/bin"}, `ADD file /home/`: {"file", "/home/"}, `COPY dir/ /tmp/`: {"dir/", "/tmp/"}, `RUN echo "Hello world!"`: {`echo "Hello world!"`}, `ENTRYPOINT /bin/sh`: {"/bin/sh"}, `CMD ["-c", "env"]`: {"-c", "env"}, `USER 1001`: {"1001"}, `WORKDIR /home`: {"/home"}, } for original, want := range testCases { node, err := parser.Parse(strings.NewReader(original)) if err != nil { t.Fatalf("parse error: %s: %v", original, err) } if len(node.Children) != 1 { t.Fatalf("unexpected number of children in test case: %s", original) } // The Docker parser always wrap instructions in a root node. // Look at the node representing the first instruction, the one // and only one in each test case. node = node.Children[0] if got := nextValues(node); !reflect.DeepEqual(got, want) { t.Errorf("nextValues(%+v) = %#v; want %#v", node, got, want) } } }
func (b *Builder) parseDockerfile() error { f, err := b.context.Open(b.options.Dockerfile) if err != nil { if os.IsNotExist(err) { return fmt.Errorf("Cannot locate specified Dockerfile: %s", b.options.Dockerfile) } return err } defer f.Close() if f, ok := f.(*os.File); ok { // ignoring error because Open already succeeded fi, err := f.Stat() if err != nil { return fmt.Errorf("Unexpected error reading Dockerfile: %v", err) } if fi.Size() == 0 { return fmt.Errorf("The Dockerfile (%s) cannot be empty", b.options.Dockerfile) } } b.dockerfile, err = parser.Parse(f, &b.directive) if err != nil { return err } return nil }
func executeTestCase(t *testing.T, testCase dispatchTestCase) { contextDir, cleanup := createTestTempDir(t, "", "builder-dockerfile-test") defer cleanup() for filename, content := range testCase.files { createTestTempFile(t, contextDir, filename, content, 0777) } tarStream, err := archive.Tar(contextDir, archive.Uncompressed) if err != nil { t.Fatalf("Error when creating tar stream: %s", err) } defer func() { if err = tarStream.Close(); err != nil { t.Fatalf("Error when closing tar stream: %s", err) } }() context, err := builder.MakeTarSumContext(tarStream) if err != nil { t.Fatalf("Error when creating tar context: %s", err) } defer func() { if err = context.Close(); err != nil { t.Fatalf("Error when closing tar context: %s", err) } }() r := strings.NewReader(testCase.dockerfile) d := parser.Directive{} parser.SetEscapeToken(parser.DefaultEscapeToken, &d) n, err := parser.Parse(r, &d) if err != nil { t.Fatalf("Error when parsing Dockerfile: %s", err) } config := &container.Config{} options := &types.ImageBuildOptions{} b := &Builder{runConfig: config, options: options, Stdout: ioutil.Discard, context: context} err = b.dispatch(0, n.Children[0]) if err == nil { t.Fatalf("No error when executing test %s", testCase.name) } if !strings.Contains(err.Error(), testCase.expectedError) { t.Fatalf("Wrong error message. Should be \"%s\". Got \"%s\"", testCase.expectedError, err.Error()) } }
func NewDockerfile(contents string) (Dockerfile, error) { if len(contents) == 0 { return nil, errors.New("Dockerfile is empty") } node, err := parser.Parse(strings.NewReader(contents)) if err != nil { return nil, err } return dockerfileContents{node, contents}, nil }
// DockerfileRead reads a Dockerfile as io.Reader func DockerfileRead(input io.Reader) (*Dockerfile, error) { dockerfile := Dockerfile{} root, err := parser.Parse(input) if err != nil { return nil, err } dockerfile.root = root return &dockerfile, nil }
func parseDockerfile(dockerfilePath string) (*parser.Node, error) { f, err := os.Open(dockerfilePath) defer f.Close() if err != nil { return nil, err } // Parse the Dockerfile. node, err := parser.Parse(f) if err != nil { return nil, err } return node, nil }
// readDockerfile reads a Dockerfile from the current context. func (b *Builder) readDockerfile() error { // If no -f was specified then look for 'Dockerfile'. If we can't find // that then look for 'dockerfile'. If neither are found then default // back to 'Dockerfile' and use that in the error message. if b.DockerfileName == "" { b.DockerfileName = api.DefaultDockerfileName if _, _, err := b.context.Stat(b.DockerfileName); os.IsNotExist(err) { lowercase := strings.ToLower(b.DockerfileName) if _, _, err := b.context.Stat(lowercase); err == nil { b.DockerfileName = lowercase } } } f, err := b.context.Open(b.DockerfileName) if err != nil { if os.IsNotExist(err) { return fmt.Errorf("Cannot locate specified Dockerfile: %s", b.DockerfileName) } return err } if f, ok := f.(*os.File); ok { // ignoring error because Open already succeeded fi, err := f.Stat() if err != nil { return fmt.Errorf("Unexpected error reading Dockerfile: %v", err) } if fi.Size() == 0 { return fmt.Errorf("The Dockerfile (%s) cannot be empty", b.DockerfileName) } } b.dockerfile, err = parser.Parse(f) f.Close() if err != nil { return err } // After the Dockerfile has been parsed, we need to check the .dockerignore // file for either "Dockerfile" or ".dockerignore", and if either are // present then erase them from the build context. These files should never // have been sent from the client but we did send them to make sure that // we had the Dockerfile to actually parse, and then we also need the // .dockerignore file to know whether either file should be removed. // Note that this assumes the Dockerfile has been read into memory and // is now safe to be removed. if dockerIgnore, ok := b.context.(builder.DockerIgnoreContext); ok { dockerIgnore.Process([]string{b.DockerfileName}) } return nil }
// TestExposedPorts tests calling exposedPorts with multiple valid combinations // of input. func TestExposedPorts(t *testing.T) { testCases := map[string]struct { in string want [][]string }{ "empty Dockerfile": { in: ``, want: nil, }, "EXPOSE missing argument": { in: `EXPOSE`, want: nil, }, "EXPOSE no FROM": { in: `EXPOSE 8080`, want: nil, }, "single EXPOSE after FROM": { in: `FROM centos:7 EXPOSE 8080`, want: [][]string{{"8080"}}, }, "multiple EXPOSE and FROM": { in: `# EXPOSE before FROM should be ignore EXPOSE 777 FROM busybox EXPOSE 8080 COPY . /boot FROM rhel # no EXPOSE instruction FROM centos:7 EXPOSE 8000 EXPOSE 9090 9091 `, want: [][]string{{"8080"}, nil, {"8000", "9090", "9091"}}, }, } for name, tc := range testCases { node, err := parser.Parse(strings.NewReader(tc.in)) if err != nil { t.Errorf("%s: parse error: %v", name, err) continue } got := exposedPorts(node) if !reflect.DeepEqual(got, tc.want) { t.Errorf("exposedPorts: %s: got %#v; want %#v", name, got, tc.want) } } }
// FromDockerfile generates an ImageRef from a given name, directory, and context path. // The directory and context path will be joined and the resulting path should be a // Dockerfile from where the image's ports will be extracted. func (g *imageRefGenerator) FromDockerfile(name string, dir string, context string) (*ImageRef, error) { // Look for Dockerfile in repository file, err := os.Open(filepath.Join(dir, context, "Dockerfile")) if err != nil { return nil, err } defer file.Close() node, err := parser.Parse(file) if err != nil { return nil, err } ports := dockerfile.LastExposedPorts(node) return g.FromNameAndPorts(name, ports) }
// TestInsertInstructionsPosOutOfRange tests calling InsertInstructions with // invalid values for the pos argument. func TestInsertInstructionsPosOutOfRange(t *testing.T) { original := `FROM busybox ENV PATH=/bin ` node, err := parser.Parse(strings.NewReader(original)) if err != nil { t.Fatalf("parse error: %v", err) } for _, pos := range []int{-1, 3, 4} { err := InsertInstructions(node, pos, "") if err == nil { t.Errorf("InsertInstructions(node, %d, \"\"): got nil; want error", pos) } } }
// InsertInstructions inserts instructions starting from the pos-th child of // node, moving other children as necessary. The instructions should be valid // Dockerfile instructions. InsertInstructions mutates node in-place, and the // final state of node is equivalent to what parser.Parse would return if the // original Dockerfile represented by node contained the instructions at the // specified position pos. If the returned error is non-nil, node is guaranteed // to be unchanged. func InsertInstructions(node *parser.Node, pos int, instructions string) error { if node == nil { return fmt.Errorf("cannot insert instructions in a nil node") } if pos < 0 || pos > len(node.Children) { return fmt.Errorf("pos %d out of range [0, %d]", pos, len(node.Children)-1) } newChild, err := parser.Parse(strings.NewReader(instructions)) if err != nil { return err } // InsertVector pattern (https://github.com/golang/go/wiki/SliceTricks) node.Children = append(node.Children[:pos], append(newChild.Children, node.Children[pos:]...)...) return nil }
func (b *Builder) processImageFrom(img builder.Image) error { b.image = img.ID() if img.Config != nil { b.runConfig = img.Config() } // The default path will be blank on Windows (set by HCS) if len(b.runConfig.Env) == 0 && system.DefaultPathEnv != "" { b.runConfig.Env = append(b.runConfig.Env, "PATH="+system.DefaultPathEnv) } // Process ONBUILD triggers if they exist if nTriggers := len(b.runConfig.OnBuild); nTriggers != 0 { word := "trigger" if nTriggers > 1 { word = "triggers" } fmt.Fprintf(b.Stderr, "# Executing %d build %s...\n", nTriggers, word) } // Copy the ONBUILD triggers, and remove them from the config, since the config will be committed. onBuildTriggers := b.runConfig.OnBuild b.runConfig.OnBuild = []string{} // parse the ONBUILD triggers by invoking the parser for _, step := range onBuildTriggers { ast, err := parser.Parse(strings.NewReader(step)) if err != nil { return err } for i, n := range ast.Children { switch strings.ToUpper(n.Value) { case "ONBUILD": return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") case "MAINTAINER", "FROM": return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", n.Value) } if err := b.dispatch(i, n); err != nil { return err } } } return nil }
// TestInsertInstructionsUnparseable tests calling InsertInstructions with // instructions that the Docker parser cannot handle. func TestInsertInstructionsUnparseable(t *testing.T) { original := `FROM busybox ENV PATH=/bin ` node, err := parser.Parse(strings.NewReader(original)) if err != nil { t.Fatalf("parse error: %v", err) } for name, instructions := range map[string]string{ "env without value": `ENV PATH`, "nested json": `CMD [ "echo", [ "nested json" ] ]`, } { err = InsertInstructions(node, 1, instructions) if err == nil { t.Errorf("InsertInstructions: %s: got nil; want error", name) } } }
// this replaces the FROM field in the Dockerfile to one with the previous step's unique name // it stores the parsed result Dockefile in uniqueSessionName file func (b *Builder) replaceFromField(step *Step) error { b.Conf.Logger.Noticef("Parsing and converting '%s'", step.Dockerfile) rwc, err := os.Open(path.Join(b.Conf.Workdir, step.Dockerfile)) if err != nil { return err } defer rwc.Close() d := parser.Directive{LookingForDirectives: true} parser.SetEscapeToken(parser.DefaultEscapeToken, &d) node, err := parser.Parse(rwc, &d) if err != nil { return err } for _, child := range node.Children { if child.Value == "from" { // found it. is it from anyone we know? if child.Next == nil { return errors.New("invalid Dockerfile. No valid FROM found") } imageName := child.Next.Value found, err := step.Manifest.FindStepByName(imageName) if err != nil { return err } if found != nil { child.Next.Value = b.uniqueStepName(found) } } } // did it have any effect? b.Conf.Logger.Debugf("Writing the new Dockerfile into %s", step.Dockerfile+".generated") err = ioutil.WriteFile(b.uniqueDockerfile(step), []byte(dumpDockerfile(node)), 0644) if err != nil { return err } return nil }
// NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config. // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName, // will be read from the Context passed to Build(). func NewBuilder(d *daemon.Daemon, config *Config, docker builder.Docker, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) { if config == nil { config = new(Config) } if config.BuildArgs == nil { config.BuildArgs = make(map[string]string) } hyper, err := GetDaemon() if err != nil { glog.Error(err.Error()) return nil, err } vmId := "buildervm-" + rand.RandStr(10, "number") defer func() { glog.V(1).Infof("Kill VM(%s)...", vmId) hyper.KillVm(vmId) }() b = &Builder{ Config: config, Daemon: d, Name: vmId, Hyperdaemon: hyper, Stdout: os.Stdout, Stderr: os.Stderr, docker: docker, context: context, runConfig: new(runconfig.Config), tmpContainers: map[string]struct{}{}, tmpPods: map[string]struct{}{}, cancelled: make(chan struct{}), id: stringid.GenerateNonCryptoID(), allowedBuildArgs: make(map[string]bool), } if dockerfile != nil { b.dockerfile, err = parser.Parse(dockerfile) if err != nil { return nil, err } } return b, nil }
func TestBuildProcessLabels(t *testing.T) { dockerfile := "FROM scratch" d := parser.Directive{} parser.SetEscapeToken(parser.DefaultEscapeToken, &d) n, err := parser.Parse(strings.NewReader(dockerfile), &d) if err != nil { t.Fatalf("Error when parsing Dockerfile: %s", err) } options := &types.ImageBuildOptions{ Labels: map[string]string{ "org.e": "cli-e", "org.d": "cli-d", "org.c": "cli-c", "org.b": "cli-b", "org.a": "cli-a", }, } b := &Builder{ runConfig: &container.Config{}, options: options, directive: d, dockerfile: n, } err = b.processLabels() if err != nil { t.Fatalf("Error when processing labels: %s", err) } expected := []string{ "FROM scratch", `LABEL "org.a"='cli-a' "org.b"='cli-b' "org.c"='cli-c' "org.d"='cli-d' "org.e"='cli-e'`, } if len(b.dockerfile.Children) != 2 { t.Fatalf("Expect 2, got %d", len(b.dockerfile.Children)) } for i, v := range b.dockerfile.Children { if v.Original != expected[i] { t.Fatalf("Expect '%s' for %dth children, got, '%s'", expected[i], i, v.Original) } } }
// replaceLastFrom changes the last FROM instruction of node to point to the // base image. func replaceLastFrom(node *parser.Node, image string) error { if node == nil { return nil } for i := len(node.Children) - 1; i >= 0; i-- { child := node.Children[i] if child != nil && child.Value == dockercmd.From { from, err := dockerfile.From(image) if err != nil { return err } fromTree, err := parser.Parse(strings.NewReader(from)) if err != nil { return err } node.Children[i] = fromTree.Children[0] return nil } } return nil }
// NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config. // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName, // will be read from the Context passed to Build(). func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, backend builder.Backend, buildContext builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) { if config == nil { config = new(types.ImageBuildOptions) } if config.BuildArgs == nil { config.BuildArgs = make(map[string]string) } ctx, cancel := context.WithCancel(clientCtx) b = &Builder{ clientCtx: ctx, cancel: cancel, options: config, Stdout: os.Stdout, Stderr: os.Stderr, docker: backend, context: buildContext, runConfig: new(container.Config), tmpContainers: map[string]struct{}{}, id: stringid.GenerateNonCryptoID(), allowedBuildArgs: make(map[string]bool), directive: parser.Directive{ EscapeSeen: false, LookingForDirectives: true, }, } if icb, ok := backend.(builder.ImageCacheBuilder); ok { b.imageCache = icb.MakeImageCache(config.CacheFrom) } parser.SetEscapeToken(parser.DefaultEscapeToken, &b.directive) // Assume the default token for escape if dockerfile != nil { b.dockerfile, err = parser.Parse(dockerfile, &b.directive) if err != nil { return nil, err } } return b, nil }
func main() { var f *os.File var err error if len(os.Args) < 2 { fmt.Println("please supply filename(s)") os.Exit(1) } for _, fn := range os.Args[1:] { f, err = os.Open(fn) if err != nil { panic(err) } ast, err := parser.Parse(f) if err != nil { panic(err) } else { fmt.Println(ast.Dump()) } } }
// TestFindAll tests calling FindAll with multiple values of cmd. func TestFindAll(t *testing.T) { instructions := `FROM scratch LABEL version=1.0 FROM busybox ENV PATH=/bin ` node, err := parser.Parse(strings.NewReader(instructions)) if err != nil { t.Fatalf("parse error: %v", err) } for cmd, want := range map[string][]int{ command.From: {0, 2}, command.Label: {1}, command.Env: {3}, command.Maintainer: nil, "UnknownCommand": nil, } { got := FindAll(node, cmd) if !reflect.DeepEqual(got, want) { t.Errorf("FindAll(node, %q) = %#v; want %#v", cmd, got, want) } } }