github.com/olljanat/moby@v1.13.1/opts/mount.go (about)

     1  package opts
     2  
     3  import (
     4  	"encoding/csv"
     5  	"fmt"
     6  	"os"
     7  	"strconv"
     8  	"strings"
     9  
    10  	mounttypes "github.com/docker/docker/api/types/mount"
    11  	"github.com/docker/go-units"
    12  )
    13  
    14  // MountOpt is a Value type for parsing mounts
    15  type MountOpt struct {
    16  	values []mounttypes.Mount
    17  }
    18  
    19  // Set a new mount value
    20  func (m *MountOpt) Set(value string) error {
    21  	csvReader := csv.NewReader(strings.NewReader(value))
    22  	fields, err := csvReader.Read()
    23  	if err != nil {
    24  		return err
    25  	}
    26  
    27  	mount := mounttypes.Mount{}
    28  
    29  	volumeOptions := func() *mounttypes.VolumeOptions {
    30  		if mount.VolumeOptions == nil {
    31  			mount.VolumeOptions = &mounttypes.VolumeOptions{
    32  				Labels: make(map[string]string),
    33  			}
    34  		}
    35  		if mount.VolumeOptions.DriverConfig == nil {
    36  			mount.VolumeOptions.DriverConfig = &mounttypes.Driver{}
    37  		}
    38  		return mount.VolumeOptions
    39  	}
    40  
    41  	bindOptions := func() *mounttypes.BindOptions {
    42  		if mount.BindOptions == nil {
    43  			mount.BindOptions = new(mounttypes.BindOptions)
    44  		}
    45  		return mount.BindOptions
    46  	}
    47  
    48  	tmpfsOptions := func() *mounttypes.TmpfsOptions {
    49  		if mount.TmpfsOptions == nil {
    50  			mount.TmpfsOptions = new(mounttypes.TmpfsOptions)
    51  		}
    52  		return mount.TmpfsOptions
    53  	}
    54  
    55  	setValueOnMap := func(target map[string]string, value string) {
    56  		parts := strings.SplitN(value, "=", 2)
    57  		if len(parts) == 1 {
    58  			target[value] = ""
    59  		} else {
    60  			target[parts[0]] = parts[1]
    61  		}
    62  	}
    63  
    64  	mount.Type = mounttypes.TypeVolume // default to volume mounts
    65  	// Set writable as the default
    66  	for _, field := range fields {
    67  		parts := strings.SplitN(field, "=", 2)
    68  		key := strings.ToLower(parts[0])
    69  
    70  		if len(parts) == 1 {
    71  			switch key {
    72  			case "readonly", "ro":
    73  				mount.ReadOnly = true
    74  				continue
    75  			case "volume-nocopy":
    76  				volumeOptions().NoCopy = true
    77  				continue
    78  			}
    79  		}
    80  
    81  		if len(parts) != 2 {
    82  			return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
    83  		}
    84  
    85  		value := parts[1]
    86  		switch key {
    87  		case "type":
    88  			mount.Type = mounttypes.Type(strings.ToLower(value))
    89  		case "source", "src":
    90  			mount.Source = value
    91  		case "target", "dst", "destination":
    92  			mount.Target = value
    93  		case "readonly", "ro":
    94  			mount.ReadOnly, err = strconv.ParseBool(value)
    95  			if err != nil {
    96  				return fmt.Errorf("invalid value for %s: %s", key, value)
    97  			}
    98  		case "bind-propagation":
    99  			bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(value))
   100  		case "volume-nocopy":
   101  			volumeOptions().NoCopy, err = strconv.ParseBool(value)
   102  			if err != nil {
   103  				return fmt.Errorf("invalid value for populate: %s", value)
   104  			}
   105  		case "volume-label":
   106  			setValueOnMap(volumeOptions().Labels, value)
   107  		case "volume-driver":
   108  			volumeOptions().DriverConfig.Name = value
   109  		case "volume-opt":
   110  			if volumeOptions().DriverConfig.Options == nil {
   111  				volumeOptions().DriverConfig.Options = make(map[string]string)
   112  			}
   113  			setValueOnMap(volumeOptions().DriverConfig.Options, value)
   114  		case "tmpfs-size":
   115  			sizeBytes, err := units.RAMInBytes(value)
   116  			if err != nil {
   117  				return fmt.Errorf("invalid value for %s: %s", key, value)
   118  			}
   119  			tmpfsOptions().SizeBytes = sizeBytes
   120  		case "tmpfs-mode":
   121  			ui64, err := strconv.ParseUint(value, 8, 32)
   122  			if err != nil {
   123  				return fmt.Errorf("invalid value for %s: %s", key, value)
   124  			}
   125  			tmpfsOptions().Mode = os.FileMode(ui64)
   126  		default:
   127  			return fmt.Errorf("unexpected key '%s' in '%s'", key, field)
   128  		}
   129  	}
   130  
   131  	if mount.Type == "" {
   132  		return fmt.Errorf("type is required")
   133  	}
   134  
   135  	if mount.Target == "" {
   136  		return fmt.Errorf("target is required")
   137  	}
   138  
   139  	if mount.VolumeOptions != nil && mount.Type != mounttypes.TypeVolume {
   140  		return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mount.Type)
   141  	}
   142  	if mount.BindOptions != nil && mount.Type != mounttypes.TypeBind {
   143  		return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mount.Type)
   144  	}
   145  	if mount.TmpfsOptions != nil && mount.Type != mounttypes.TypeTmpfs {
   146  		return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", mount.Type)
   147  	}
   148  
   149  	m.values = append(m.values, mount)
   150  	return nil
   151  }
   152  
   153  // Type returns the type of this option
   154  func (m *MountOpt) Type() string {
   155  	return "mount"
   156  }
   157  
   158  // String returns a string repr of this option
   159  func (m *MountOpt) String() string {
   160  	mounts := []string{}
   161  	for _, mount := range m.values {
   162  		repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target)
   163  		mounts = append(mounts, repr)
   164  	}
   165  	return strings.Join(mounts, ", ")
   166  }
   167  
   168  // Value returns the mounts
   169  func (m *MountOpt) Value() []mounttypes.Mount {
   170  	return m.values
   171  }