forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
dockerfile.go
166 lines (157 loc) · 4.78 KB
/
dockerfile.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package dockerfile
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
"github.com/docker/docker/builder/dockerfile/command"
"github.com/docker/docker/builder/dockerfile/parser"
)
// Parse parses the provided stream as a canonical Dockerfile
func Parse(r io.Reader) (*parser.Node, error) {
result, err := parser.Parse(r)
if err != nil {
return nil, err
}
return result.AST, nil
}
// Write takes a Dockerfile AST node, as generated by
// parser.Parse, and returns a new, equivalent, Dockerfile.
func Write(node *parser.Node) []byte {
if node == nil {
return nil
}
buf := &bytes.Buffer{}
if len(node.Value) > 0 {
buf.Write([]byte(strings.ToUpper(node.Value)))
for _, flag := range node.Flags {
buf.Write([]byte(" "))
buf.Write([]byte(flag))
}
switch node.Value {
case command.Onbuild:
if node.Next != nil && len(node.Next.Children) > 0 {
buf.Write([]byte(" "))
buf.Write(Write(node.Next.Children[0]))
}
return buf.Bytes()
case command.Env, command.Label:
for n := node.Next; n != nil; n = n.Next {
if buf.Len() > 0 {
buf.Write([]byte(" "))
}
buf.Write([]byte(n.Value))
buf.Write([]byte("="))
if n.Next != nil {
buf.Write([]byte(n.Next.Value))
}
n = n.Next
}
buf.Write([]byte("\n"))
return buf.Bytes()
default:
if node.Attributes["json"] {
var values []string
for n := node.Next; n != nil; n = n.Next {
values = append(values, n.Value)
}
out, _ := json.Marshal(values)
buf.Write([]byte(" "))
buf.Write(out)
buf.Write([]byte("\n"))
return buf.Bytes()
}
for n := node.Next; n != nil; n = n.Next {
if buf.Len() > 0 {
buf.Write([]byte(" "))
}
buf.Write([]byte(n.Value))
}
buf.Write([]byte("\n"))
return buf.Bytes()
}
}
for _, child := range node.Children {
buf.Write(Write(child))
}
return buf.Bytes()
}
// 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 := 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
}
// 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
}
// 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
}