in scan/dependencies.go [157:254]
func resolveDockerfileDependencies(r io.Reader, buildArgs []string, target string) (origin string, buildtimeDependencies []string, err error) {
scanner := bufio.NewScanner(r)
context, err := parseBuildArgs(buildArgs)
if err != nil {
return "", nil, err
}
originLookup := map[string]string{} // given an alias, look up its origin
allOrigins := map[string]bool{} // set of all origins
firstLine := true
SCAN: // label for the scan loop
for scanner.Scan() {
var line string
// Trim UTF-8 BOM if necessary.
if firstLine {
scannedBytes := scanner.Bytes()
scannedBytes = bytes.TrimPrefix(scannedBytes, utf8BOM)
line = string(scannedBytes)
firstLine = false
} else {
line = scanner.Text()
}
line = strings.TrimSpace(line)
// Skip comments.
if line == "" || strings.HasPrefix(line, dockerfileComment) {
continue
}
tokens := strings.Fields(line)
if len(tokens) > 0 {
switch strings.ToUpper(tokens[0]) {
case "FROM":
if len(tokens) < 2 {
return "", nil, fmt.Errorf("unable to understand line %s", line)
}
// trim surrounds single and double quotes from the image reference
var imageToken = os.Expand(util.TrimQuotes(tokens[1]), func(key string) string {
return context[key]
})
var found bool
origin, found = originLookup[imageToken]
if !found {
allOrigins[imageToken] = true
origin = imageToken
}
if len(tokens) > 2 {
if len(tokens) < 4 || !strings.EqualFold(tokens[2], "as") {
return "", nil, fmt.Errorf("unable to understand line %s", line)
}
// alias cannot contain variables it seems. So we don't call context.Expand on it
alias := tokens[3]
originLookup[alias] = origin
// Just ignore the rest of the tokens...
if len(tokens) > 4 {
log.Printf("Ignoring chunks from FROM clause: %v\n", tokens[4:])
}
// reach the target, stop the scanning
if len(target) > 0 && target == alias {
break SCAN
}
}
case "ARG":
if len(tokens) < 2 {
return "", nil, fmt.Errorf("dockerfile syntax requires ARG directive to have exactly 1 argument. LINE: %s", line)
}
if strings.Contains(tokens[1], "=") {
varName, varValue, err := parseAssignment(tokens[1])
if err != nil {
return "", nil, fmt.Errorf("unable to parse assignment %s, error: %s", tokens[1], err)
}
// This line matches docker's behavior here
// 1. If build arg is passed in, the value will not override
// 2. It is actually allowed for same ARG to be specified more than once in a Dockerfile
// However the subsequent value would be ignored instead of overriding the previous
if _, found := context[varName]; !found {
context[varName] = varValue
}
}
}
}
}
if len(allOrigins) == 0 {
return "", nil, errors.New("unexpected dockerfile format")
}
// note that origin variable now points to the runtime origin
for terminal := range allOrigins {
if terminal != origin {
buildtimeDependencies = append(buildtimeDependencies, terminal)
}
}
return origin, buildtimeDependencies, nil
}