github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/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  			case "bind-nonrecursive":
    80  				bindOptions().NonRecursive = true
    81  				continue
    82  			}
    83  		}
    84  
    85  		if len(parts) != 2 {
    86  			return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
    87  		}
    88  
    89  		value := parts[1]
    90  		switch key {
    91  		case "type":
    92  			mount.Type = mounttypes.Type(strings.ToLower(value))
    93  		case "source", "src":
    94  			mount.Source = value
    95  		case "target", "dst", "destination":
    96  			mount.Target = value
    97  		case "readonly", "ro":
    98  			mount.ReadOnly, err = strconv.ParseBool(value)
    99  			if err != nil {
   100  				return fmt.Errorf("invalid value for %s: %s", key, value)
   101  			}
   102  		case "consistency":
   103  			mount.Consistency = mounttypes.Consistency(strings.ToLower(value))
   104  		case "bind-propagation":
   105  			bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(value))
   106  		case "bind-nonrecursive":
   107  			bindOptions().NonRecursive, err = strconv.ParseBool(value)
   108  			if err != nil {
   109  				return fmt.Errorf("invalid value for %s: %s", key, value)
   110  			}
   111  		case "volume-nocopy":
   112  			volumeOptions().NoCopy, err = strconv.ParseBool(value)
   113  			if err != nil {
   114  				return fmt.Errorf("invalid value for volume-nocopy: %s", value)
   115  			}
   116  		case "volume-label":
   117  			setValueOnMap(volumeOptions().Labels, value)
   118  		case "volume-driver":
   119  			volumeOptions().DriverConfig.Name = value
   120  		case "volume-opt":
   121  			if volumeOptions().DriverConfig.Options == nil {
   122  				volumeOptions().DriverConfig.Options = make(map[string]string)
   123  			}
   124  			setValueOnMap(volumeOptions().DriverConfig.Options, value)
   125  		case "tmpfs-size":
   126  			sizeBytes, err := units.RAMInBytes(value)
   127  			if err != nil {
   128  				return fmt.Errorf("invalid value for %s: %s", key, value)
   129  			}
   130  			tmpfsOptions().SizeBytes = sizeBytes
   131  		case "tmpfs-mode":
   132  			ui64, err := strconv.ParseUint(value, 8, 32)
   133  			if err != nil {
   134  				return fmt.Errorf("invalid value for %s: %s", key, value)
   135  			}
   136  			tmpfsOptions().Mode = os.FileMode(ui64)
   137  		default:
   138  			return fmt.Errorf("unexpected key '%s' in '%s'", key, field)
   139  		}
   140  	}
   141  
   142  	if mount.Type == "" {
   143  		return fmt.Errorf("type is required")
   144  	}
   145  
   146  	if mount.Target == "" {
   147  		return fmt.Errorf("target is required")
   148  	}
   149  
   150  	if mount.VolumeOptions != nil && mount.Type != mounttypes.TypeVolume {
   151  		return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mount.Type)
   152  	}
   153  	if mount.BindOptions != nil && mount.Type != mounttypes.TypeBind {
   154  		return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mount.Type)
   155  	}
   156  	if mount.TmpfsOptions != nil && mount.Type != mounttypes.TypeTmpfs {
   157  		return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", mount.Type)
   158  	}
   159  
   160  	m.values = append(m.values, mount)
   161  	return nil
   162  }
   163  
   164  // Type returns the type of this option
   165  func (m *MountOpt) Type() string {
   166  	return "mount"
   167  }
   168  
   169  // String returns a string repr of this option
   170  func (m *MountOpt) String() string {
   171  	mounts := []string{}
   172  	for _, mount := range m.values {
   173  		repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target)
   174  		mounts = append(mounts, repr)
   175  	}
   176  	return strings.Join(mounts, ", ")
   177  }
   178  
   179  // Value returns the mounts
   180  func (m *MountOpt) Value() []mounttypes.Mount {
   181  	return m.values
   182  }