package dockerfile import ( "fmt" "strings" "github.com/docker/docker/builder/dockerfile/command" "github.com/docker/docker/builder/dockerfile/parser" ) // ParseTreeToDockerfile takes a Dockerfile AST node, as generated by // parser.Parse, and returns a new, equivalent, Dockerfile. func ParseTreeToDockerfile(node *parser.Node) []byte { if node == nil { return nil } buf := []byte(node.Original) for _, child := range node.Children { buf = append(buf, ParseTreeToDockerfile(child)...) } // Append a line break when needed. if len(buf) > 0 && buf[len(buf)-1] != '\n' { buf = append(buf, '\n') } return buf } // FindAll returns the indices of all children of node such that // node.Children[i].Value == cmd. Valid values for cmd are defined in the // package github.com/docker/docker/builder/dockerfile/command. func FindAll(node *parser.Node, cmd string) []int { if node == nil { return nil } var indices []int for i, child := range node.Children { if child != nil && child.Value == cmd { indices = append(indices, i) } } return indices } // 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 } // LastBaseImage takes a Dockerfile root node and returns the base image // declared in the last FROM instruction. func LastBaseImage(node *parser.Node) string { baseImages := baseImages(node) if len(baseImages) == 0 { return "" } return baseImages[len(baseImages)-1] } // baseImages takes a Dockerfile root node and returns a list of all base images // declared in the Dockerfile. Each base image is the argument of a FROM // instruction. func baseImages(node *parser.Node) []string { var images []string for _, pos := range FindAll(node, command.From) { images = append(images, nextValues(node.Children[pos])...) } return images } // LastExposedPorts takes a Dockerfile root node and returns a list of ports // exposed in the last image built by the Dockerfile, i.e., only the EXPOSE // instructions after the last FROM instruction are considered. func LastExposedPorts(node *parser.Node) []string { exposedPorts := exposedPorts(node) if len(exposedPorts) == 0 { return nil } return exposedPorts[len(exposedPorts)-1] } // exposedPorts takes a Dockerfile root node and returns a list of all ports // exposed in the Dockerfile, grouped by images that this Dockerfile produces. // The number of port lists returned is the number of images produced by this // Dockerfile, which is the same as the number of FROM instructions. func exposedPorts(node *parser.Node) [][]string { var allPorts [][]string var ports []string froms := FindAll(node, command.From) exposes := FindAll(node, command.Expose) for i, j := len(froms)-1, len(exposes)-1; i >= 0; i-- { for ; j >= 0 && exposes[j] > froms[i]; j-- { ports = append(nextValues(node.Children[exposes[j]]), ports...) } allPorts = append([][]string{ports}, allPorts...) ports = nil } return allPorts } // nextValues returns a slice of values from the next nodes following node. This // roughly translates to the arguments to the Docker builder instruction // represented by node. func nextValues(node *parser.Node) []string { if node == nil { return nil } var values []string for next := node.Next; next != nil; next = next.Next { values = append(values, next.Value) } return values }