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