-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move
Dockerfile
parsing to a dedicated package
Also, add a bunch of test cases / code coverage
- Loading branch information
Showing
4 changed files
with
262 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package dockerfile | ||
|
||
import ( | ||
"bufio" | ||
"io" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
type Metadata struct { | ||
StageFroms []string // every image "FROM" instruction value (or the parent stage's FROM value in the case of a named stage) | ||
StageNames []string // the name of any named stage (in order) | ||
StageNameFroms map[string]string // map of stage names to FROM values (or the parent stage's FROM value in the case of a named stage), useful for resolving stage names to FROM values | ||
|
||
Froms []string // every "FROM" or "COPY --from=xxx" value (minus named and/or numbered stages in the case of "--from=") | ||
} | ||
|
||
func Parse(dockerfile string) (*Metadata, error) { | ||
return ParseReader(strings.NewReader(dockerfile)) | ||
} | ||
|
||
func ParseReader(dockerfile io.Reader) (*Metadata, error) { | ||
meta := &Metadata{ | ||
// panic: assignment to entry in nil map | ||
StageNameFroms: map[string]string{}, | ||
// (nil slices work fine) | ||
} | ||
|
||
scanner := bufio.NewScanner(dockerfile) | ||
for scanner.Scan() { | ||
line := strings.TrimSpace(scanner.Text()) | ||
|
||
if line == "" { | ||
// ignore blank lines | ||
continue | ||
} | ||
|
||
if line[0] == '#' { | ||
// TODO handle "escape" parser directive | ||
// TODO handle "syntax" parser directive -- explode appropriately (since custom syntax invalidates our Dockerfile parsing) | ||
// ignore comments | ||
continue | ||
} | ||
|
||
// handle line continuations | ||
// (TODO see note above regarding "escape" parser directive) | ||
for line[len(line)-1] == '\\' && scanner.Scan() { | ||
nextLine := strings.TrimSpace(scanner.Text()) | ||
if nextLine == "" || nextLine[0] == '#' { | ||
// ignore blank lines and comments | ||
continue | ||
} | ||
line = line[0:len(line)-1] + nextLine | ||
} | ||
|
||
fields := strings.Fields(line) | ||
if len(fields) < 1 { | ||
// must be a much more complex empty line?? | ||
continue | ||
} | ||
instruction := strings.ToUpper(fields[0]) | ||
|
||
// TODO balk at ARG / $ in from values | ||
|
||
switch instruction { | ||
case "FROM": | ||
from := fields[1] | ||
|
||
if stageFrom, ok := meta.StageNameFroms[from]; ok { | ||
// if this is a valid stage name, we should resolve it back to the original FROM value of that previous stage (we don't care about inter-stage dependencies for the purposes of either tag dependency calculation or tag building -- just how many there are and what external things they require) | ||
from = stageFrom | ||
} | ||
|
||
// make sure to add ":latest" if it's implied | ||
from = latestizeRepoTag(from) | ||
|
||
meta.StageFroms = append(meta.StageFroms, from) | ||
meta.Froms = append(meta.Froms, from) | ||
|
||
if len(fields) == 4 && strings.ToUpper(fields[2]) == "AS" { | ||
stageName := fields[3] | ||
meta.StageNames = append(meta.StageNames, stageName) | ||
meta.StageNameFroms[stageName] = from | ||
} | ||
|
||
case "COPY": | ||
for _, arg := range fields[1:] { | ||
if !strings.HasPrefix(arg, "--") { | ||
// doesn't appear to be a "flag"; time to bail! | ||
break | ||
} | ||
if !strings.HasPrefix(arg, "--from=") { | ||
// ignore any flags we're not interested in | ||
continue | ||
} | ||
from := arg[len("--from="):] | ||
|
||
if stageFrom, ok := meta.StageNameFroms[from]; ok { | ||
// see note above regarding stage names in FROM | ||
from = stageFrom | ||
} else if stageNumber, err := strconv.Atoi(from); err == nil && stageNumber < len(meta.StageFroms) { | ||
// must be a stage number, we should resolve it too | ||
from = meta.StageFroms[stageNumber] | ||
} | ||
|
||
// make sure to add ":latest" if it's implied | ||
from = latestizeRepoTag(from) | ||
|
||
meta.Froms = append(meta.Froms, from) | ||
} | ||
} | ||
} | ||
if err := scanner.Err(); err != nil { | ||
return nil, err | ||
} | ||
return meta, nil | ||
} | ||
|
||
func latestizeRepoTag(repoTag string) string { | ||
if repoTag != "scratch" && strings.IndexRune(repoTag, ':') < 0 { | ||
return repoTag + ":latest" | ||
} | ||
return repoTag | ||
} |
Oops, something went wrong.