func resolveDockerfileDependencies()

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
}