210 lines
4.5 KiB
Go
210 lines
4.5 KiB
Go
// +build dfrunmount dfextall
|
|
|
|
package instructions
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const MountTypeBind = "bind"
|
|
const MountTypeCache = "cache"
|
|
const MountTypeTmpfs = "tmpfs"
|
|
const MountTypeSecret = "secret"
|
|
|
|
var allowedMountTypes = map[string]struct{}{
|
|
MountTypeBind: {},
|
|
MountTypeCache: {},
|
|
MountTypeTmpfs: {},
|
|
MountTypeSecret: {},
|
|
}
|
|
|
|
const MountSharingShared = "shared"
|
|
const MountSharingPrivate = "private"
|
|
const MountSharingLocked = "locked"
|
|
|
|
var allowedSharingTypes = map[string]struct{}{
|
|
MountSharingShared: {},
|
|
MountSharingPrivate: {},
|
|
MountSharingLocked: {},
|
|
}
|
|
|
|
type mountsKeyT string
|
|
|
|
var mountsKey = mountsKeyT("dockerfile/run/mounts")
|
|
|
|
func init() {
|
|
parseRunPreHooks = append(parseRunPreHooks, runMountPreHook)
|
|
parseRunPostHooks = append(parseRunPostHooks, runMountPostHook)
|
|
}
|
|
|
|
func isValidMountType(s string) bool {
|
|
if s == "secret" {
|
|
if !isSecretMountsSupported() {
|
|
return false
|
|
}
|
|
}
|
|
_, ok := allowedMountTypes[s]
|
|
return ok
|
|
}
|
|
|
|
func runMountPreHook(cmd *RunCommand, req parseRequest) error {
|
|
st := &mountState{}
|
|
st.flag = req.flags.AddStrings("mount")
|
|
cmd.setExternalValue(mountsKey, st)
|
|
return nil
|
|
}
|
|
|
|
func runMountPostHook(cmd *RunCommand, req parseRequest) error {
|
|
st := getMountState(cmd)
|
|
if st == nil {
|
|
return errors.Errorf("no mount state")
|
|
}
|
|
var mounts []*Mount
|
|
for _, str := range st.flag.StringValues {
|
|
m, err := parseMount(str)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mounts = append(mounts, m)
|
|
}
|
|
st.mounts = mounts
|
|
return nil
|
|
}
|
|
|
|
func getMountState(cmd *RunCommand) *mountState {
|
|
v := cmd.getExternalValue(mountsKey)
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
return v.(*mountState)
|
|
}
|
|
|
|
func GetMounts(cmd *RunCommand) []*Mount {
|
|
return getMountState(cmd).mounts
|
|
}
|
|
|
|
type mountState struct {
|
|
flag *Flag
|
|
mounts []*Mount
|
|
}
|
|
|
|
type Mount struct {
|
|
Type string
|
|
From string
|
|
Source string
|
|
Target string
|
|
ReadOnly bool
|
|
CacheID string
|
|
CacheSharing string
|
|
Required bool
|
|
}
|
|
|
|
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{Type: MountTypeBind}
|
|
|
|
roAuto := 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
|
|
roAuto = false
|
|
continue
|
|
case "readwrite", "rw":
|
|
m.ReadOnly = false
|
|
roAuto = false
|
|
continue
|
|
case "required":
|
|
if m.Type == "secret" {
|
|
m.Required = true
|
|
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 !isValidMountType(strings.ToLower(value)) {
|
|
return nil, errors.Errorf("unsupported 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)
|
|
}
|
|
roAuto = false
|
|
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
|
|
roAuto = false
|
|
case "id":
|
|
m.CacheID = value
|
|
case "sharing":
|
|
if _, ok := allowedSharingTypes[strings.ToLower(value)]; !ok {
|
|
return nil, errors.Errorf("unsupported sharing value %q", value)
|
|
}
|
|
m.CacheSharing = strings.ToLower(value)
|
|
default:
|
|
return nil, errors.Errorf("unexpected key '%s' in '%s'", key, field)
|
|
}
|
|
}
|
|
|
|
if roAuto {
|
|
if m.Type == MountTypeCache {
|
|
m.ReadOnly = false
|
|
} else {
|
|
m.ReadOnly = true
|
|
}
|
|
}
|
|
|
|
if m.CacheSharing != "" && m.Type != MountTypeCache {
|
|
return nil, errors.Errorf("invalid cache sharing set for %v mount", m.Type)
|
|
}
|
|
|
|
if m.Type == MountTypeSecret {
|
|
if m.From != "" {
|
|
return nil, errors.Errorf("secret mount should not have a from")
|
|
}
|
|
if m.CacheSharing != "" {
|
|
return nil, errors.Errorf("secret mount should not define sharing")
|
|
}
|
|
if m.Source == "" && m.Target == "" && m.CacheID == "" {
|
|
return nil, errors.Errorf("invalid secret mount. one of source, target required")
|
|
}
|
|
if m.Source != "" && m.CacheID != "" {
|
|
return nil, errors.Errorf("both source and id can't be set")
|
|
}
|
|
}
|
|
|
|
return m, nil
|
|
}
|