github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/cli/compose/loader/volume.go (about)

     1  package loader
     2  
     3  import (
     4  	"strings"
     5  	"unicode"
     6  	"unicode/utf8"
     7  
     8  	"github.com/docker/cli/cli/compose/types"
     9  	"github.com/docker/docker/api/types/mount"
    10  	"github.com/pkg/errors"
    11  )
    12  
    13  const endOfSpec = rune(0)
    14  
    15  // ParseVolume parses a volume spec without any knowledge of the target platform
    16  func ParseVolume(spec string) (types.ServiceVolumeConfig, error) {
    17  	volume := types.ServiceVolumeConfig{}
    18  
    19  	switch len(spec) {
    20  	case 0:
    21  		return volume, errors.New("invalid empty volume spec")
    22  	case 1, 2:
    23  		volume.Target = spec
    24  		volume.Type = string(mount.TypeVolume)
    25  		return volume, nil
    26  	}
    27  
    28  	buffer := []rune{}
    29  	for _, char := range spec + string(endOfSpec) {
    30  		switch {
    31  		case isWindowsDrive(buffer, char):
    32  			buffer = append(buffer, char)
    33  		case char == ':' || char == endOfSpec:
    34  			if err := populateFieldFromBuffer(char, buffer, &volume); err != nil {
    35  				populateType(&volume)
    36  				return volume, errors.Wrapf(err, "invalid spec: %s", spec)
    37  			}
    38  			buffer = []rune{}
    39  		default:
    40  			buffer = append(buffer, char)
    41  		}
    42  	}
    43  
    44  	populateType(&volume)
    45  	return volume, nil
    46  }
    47  
    48  func isWindowsDrive(buffer []rune, char rune) bool {
    49  	return char == ':' && len(buffer) == 1 && unicode.IsLetter(buffer[0])
    50  }
    51  
    52  func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolumeConfig) error {
    53  	strBuffer := string(buffer)
    54  	switch {
    55  	case len(buffer) == 0:
    56  		return errors.New("empty section between colons")
    57  	// Anonymous volume
    58  	case volume.Source == "" && char == endOfSpec:
    59  		volume.Target = strBuffer
    60  		return nil
    61  	case volume.Source == "":
    62  		volume.Source = strBuffer
    63  		return nil
    64  	case volume.Target == "":
    65  		volume.Target = strBuffer
    66  		return nil
    67  	case char == ':':
    68  		return errors.New("too many colons")
    69  	}
    70  	for _, option := range strings.Split(strBuffer, ",") {
    71  		switch option {
    72  		case "ro":
    73  			volume.ReadOnly = true
    74  		case "rw":
    75  			volume.ReadOnly = false
    76  		case "nocopy":
    77  			volume.Volume = &types.ServiceVolumeVolume{NoCopy: true}
    78  		default:
    79  			if isBindOption(option) {
    80  				volume.Bind = &types.ServiceVolumeBind{Propagation: option}
    81  			}
    82  			// ignore unknown options
    83  		}
    84  	}
    85  	return nil
    86  }
    87  
    88  func isBindOption(option string) bool {
    89  	for _, propagation := range mount.Propagations {
    90  		if mount.Propagation(option) == propagation {
    91  			return true
    92  		}
    93  	}
    94  	return false
    95  }
    96  
    97  func populateType(volume *types.ServiceVolumeConfig) {
    98  	switch {
    99  	// Anonymous volume
   100  	case volume.Source == "":
   101  		volume.Type = string(mount.TypeVolume)
   102  	case isFilePath(volume.Source):
   103  		volume.Type = string(mount.TypeBind)
   104  	default:
   105  		volume.Type = string(mount.TypeVolume)
   106  	}
   107  }
   108  
   109  func isFilePath(source string) bool {
   110  	switch source[0] {
   111  	case '.', '/', '~':
   112  		return true
   113  	}
   114  
   115  	// windows named pipes
   116  	if strings.HasPrefix(source, `\\`) {
   117  		return true
   118  	}
   119  
   120  	first, nextIndex := utf8.DecodeRuneInString(source)
   121  	return isWindowsDrive([]rune{first}, rune(source[nextIndex]))
   122  }