dockerfile: add run mount parsing

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
docker-18.09
Tonis Tiigi 2018-06-06 16:30:51 -07:00
parent af94740e92
commit aeea615e85
4 changed files with 183 additions and 9 deletions

View File

@ -11,6 +11,7 @@ type FlagType int
const (
boolType FlagType = iota
stringType
stringsType
)
// BFlags contains all flags information for the builder
@ -27,6 +28,7 @@ type Flag struct {
name string
flagType FlagType
Value string
StringValues []string
}
// NewBFlags returns the new BFlags struct
@ -70,6 +72,15 @@ func (bf *BFlags) AddString(name string, def string) *Flag {
return flag
}
// AddString adds a string flag to BFlags that can match multiple values
func (bf *BFlags) AddStrings(name string) *Flag {
flag := bf.addFlag(name, stringsType)
if flag == nil {
return nil
}
return flag
}
// addFlag is a generic func used by the other AddXXX() func
// to add a new flag to the BFlags struct.
// Note, any error will be generated when Parse() is called (see Parse).
@ -145,7 +156,7 @@ func (bf *BFlags) Parse() error {
return fmt.Errorf("Unknown flag: %s", arg)
}
if _, ok = bf.used[arg]; ok {
if _, ok = bf.used[arg]; ok || flag.flagType != stringsType {
return fmt.Errorf("Duplicate flag specified: %s", arg)
}
@ -173,6 +184,12 @@ func (bf *BFlags) Parse() error {
}
flag.Value = value
case stringsType:
if index < 0 {
return fmt.Errorf("Missing a value on flag: %s", arg)
}
flag.StringValues = append(flag.StringValues, value)
default:
panic("No idea what kind of flag we have! Should never get here!")
}

View File

@ -233,6 +233,7 @@ type ShellDependantCmdLine struct {
//
type RunCommand struct {
withNameAndCode
withExternalData
ShellDependantCmdLine
}
@ -416,3 +417,23 @@ func HasStage(s []Stage, name string) (int, bool) {
}
return -1, false
}
type cmdWithExternalData interface {
getExternalValue(k interface{}) interface{}
setExternalValue(k, v interface{})
}
type withExternalData struct {
m map[interface{}]interface{}
}
func (c *withExternalData) getExternalValue(k interface{}) interface{} {
return c.m[k]
}
func (c *withExternalData) setExternalValue(k, v interface{}) {
if c.m == nil {
c.m = map[interface{}]interface{}{}
}
c.m[k] = v
}

View File

@ -0,0 +1,120 @@
// +build dfrunmount dfall
package instructions
import (
"encoding/csv"
"strconv"
"strings"
"github.com/pkg/errors"
)
type mountsKeyT string
var mountsKey = mountsKeyT("dockerfile/run/mounts")
func init() {
parseRunPreHooks = append(parseRunPreHooks, runMountPreHook)
parseRunPostHooks = append(parseRunPostHooks, runMountPostHook)
}
func runMountPreHook(cmd *RunCommand, req parseRequest) error {
st := &mountState{}
st.flag = req.flags.AddString("mount", "")
cmd.setExternalValue(mountsKey, st)
return nil
}
func runMountPostHook(cmd *RunCommand, req parseRequest) error {
v := cmd.getExternalValue(mountsKey)
if v != nil {
return errors.Errorf("no mount state")
}
st := v.(*mountState)
var mounts []*Mount
for _, str := range st.flag.StringValues {
m, err := parseMount(str)
if err != nil {
return err
}
mounts = append(mounts, m)
}
return nil
}
type mountState struct {
flag *Flag
mounts []*Mount
}
type Mount struct {
Type string
From string
Source string
Target string
ReadOnly bool
CacheID string
}
func parseMount(value string) (*Mount, error) {
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return nil, errors.Wrap(err, "failed to parse csv mounts")
}
m := &Mount{ReadOnly: true}
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)
key := strings.ToLower(parts[0])
if len(parts) == 1 {
switch key {
case "readonly", "ro":
m.ReadOnly = true
continue
case "readwrite", "rw":
m.ReadOnly = false
continue
}
}
if len(parts) != 2 {
return nil, errors.Errorf("invalid field '%s' must be a key=value pair", field)
}
value := parts[1]
switch key {
case "type":
if value != "" && strings.EqualFold(value, "cache") {
return nil, errors.Errorf("invalid mount type %q", value)
}
m.Type = strings.ToLower(value)
case "from":
m.From = value
case "source", "src":
m.Source = value
case "target", "dst", "destination":
m.Target = value
case "readonly", "ro":
m.ReadOnly, err = strconv.ParseBool(value)
if err != nil {
return nil, errors.Errorf("invalid value for %s: %s", key, value)
}
case "readwrite", "rw":
rw, err := strconv.ParseBool(value)
if err != nil {
return nil, errors.Errorf("invalid value for %s: %s", key, value)
}
m.ReadOnly = !rw
case "id":
m.CacheID = value
default:
return nil, errors.Errorf("unexpected key '%s' in '%s'", key, field)
}
}
return nil, errors.Errorf("not-implemented")
}

View File

@ -24,6 +24,9 @@ type parseRequest struct {
original string
}
var parseRunPreHooks []func(*RunCommand, parseRequest) error
var parseRunPostHooks []func(*RunCommand, parseRequest) error
func nodeArgs(node *parser.Node) []string {
result := []string{}
for ; node.Next != nil; node = node.Next {
@ -355,15 +358,28 @@ func parseShellDependentCommand(req parseRequest, emptyAsNil bool) ShellDependan
}
func parseRun(req parseRequest) (*RunCommand, error) {
cmd := &RunCommand{}
for _, fn := range parseRunPreHooks {
if err := fn(cmd, req); err != nil {
return nil, err
}
}
if err := req.flags.Parse(); err != nil {
return nil, err
}
return &RunCommand{
ShellDependantCmdLine: parseShellDependentCommand(req, false),
withNameAndCode: newWithNameAndCode(req),
}, nil
cmd.ShellDependantCmdLine = parseShellDependentCommand(req, false)
cmd.withNameAndCode = newWithNameAndCode(req)
for _, fn := range parseRunPostHooks {
if err := fn(cmd, req); err != nil {
return nil, err
}
}
return cmd, nil
}
func parseCmd(req parseRequest) (*CmdCommand, error) {