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