github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/url/parse_docker.go (about)

     1  package url
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/mutagen-io/mutagen/pkg/url/forwarding"
     9  )
    10  
    11  // dockerURLPrefix is the lowercase version of the Docker URL prefix.
    12  const dockerURLPrefix = "docker://"
    13  
    14  // DockerEnvironmentVariables is a list of Docker environment variables that
    15  // should be locked in to Docker URLs at parse time.
    16  var DockerEnvironmentVariables = []string{
    17  	"DOCKER_HOST",
    18  	"DOCKER_TLS", // This flag is not documented, but is supported by Docker.
    19  	"DOCKER_TLS_VERIFY",
    20  	"DOCKER_CERT_PATH",
    21  	"DOCKER_CONTEXT",
    22  	"DOCKER_CONFIG", // This flag is not documented, but is supported by Docker.
    23  	"DOCKER_API_VERSION",
    24  }
    25  
    26  // dockerParameterNames is a list of supported Docker command line parameters.
    27  var dockerParameterNames = []string{
    28  	"config",
    29  	"context",
    30  	"host",
    31  	"tls",
    32  	"tlscacert",
    33  	"tlscert",
    34  	"tlskey",
    35  	"tlsverify",
    36  }
    37  
    38  // isDockerURL checks whether or not a URL is a Docker URL. It requires the
    39  // presence of a Docker protocol prefix.
    40  func isDockerURL(raw string) bool {
    41  	return strings.HasPrefix(strings.ToLower(raw), dockerURLPrefix)
    42  }
    43  
    44  // parseDocker parses a Docker URL.
    45  func parseDocker(raw string, kind Kind, first bool) (*URL, error) {
    46  	// Strip off the prefix.
    47  	raw = raw[len(dockerURLPrefix):]
    48  
    49  	// Determine the character that splits the container name from the path or
    50  	// forwarding endpoint component.
    51  	var splitCharacter rune
    52  	if kind == Kind_Synchronization {
    53  		splitCharacter = '/'
    54  	} else if kind == Kind_Forwarding {
    55  		splitCharacter = ':'
    56  	} else {
    57  		panic("unhandled URL kind")
    58  	}
    59  
    60  	// Parse off the username. If we hit a '/', then we've reached the end of a
    61  	// container specification and there was no username. Similarly, if we hit
    62  	// the end of the string without seeing an '@', then there's also no
    63  	// username specified. Ideally we'd want also to break on any character that
    64  	// isn't allowed in a username, but that isn't well-defined, even for POSIX
    65  	// (it's effectively determined by a configurable regular expression -
    66  	// NAME_REGEX).
    67  	var username string
    68  	for i, r := range raw {
    69  		if r == splitCharacter {
    70  			break
    71  		} else if r == '@' {
    72  			username = raw[:i]
    73  			raw = raw[i+1:]
    74  			break
    75  		}
    76  	}
    77  
    78  	// Split what remains into the container and the path (or forwarding
    79  	// endpoint, depending on the URL kind). Ideally we'd want to be a bit more
    80  	// stringent here about what characters we accept in container names,
    81  	// potentially breaking early with an error if we see a "disallowed"
    82  	// character, but we're better off just allowing Docker to reject container
    83  	// names that it doesn't like.
    84  	var container, path string
    85  	for i, r := range raw {
    86  		if r == splitCharacter {
    87  			container = raw[:i]
    88  			path = raw[i:]
    89  			break
    90  		}
    91  	}
    92  	if container == "" {
    93  		return nil, errors.New("empty container name")
    94  	} else if path == "" {
    95  		if kind == Kind_Synchronization {
    96  			return nil, errors.New("missing path")
    97  		} else if kind == Kind_Forwarding {
    98  			return nil, errors.New("missing forwarding endpoint")
    99  		} else {
   100  			panic("unhandled URL kind")
   101  		}
   102  	}
   103  
   104  	// Perform path processing based on URL kind.
   105  	if kind == Kind_Synchronization {
   106  		// If the path starts with "/~", then we assume that it's supposed to be
   107  		// a home-directory-relative path and remove the slash. At this point we
   108  		// already know that the path starts with "/" since we retained that as
   109  		// part of the path in the split operation above.
   110  		if len(path) > 1 && path[1] == '~' {
   111  			path = path[1:]
   112  		}
   113  
   114  		// If the path is of the form "/" + Windows path, then assume it's
   115  		// supposed to be a Windows path. This is a heuristic, but a reasonable
   116  		// one. We do this on all systems (not just on Windows as with SSH URLs)
   117  		// because users can connect to Windows containers from non-Windows
   118  		// systems. At this point we already know that the path starts with "/"
   119  		// since we retained that as part of the path in the split operation
   120  		// above.
   121  		if isWindowsPath(path[1:]) {
   122  			path = path[1:]
   123  		}
   124  	} else if kind == Kind_Forwarding {
   125  		// For forwarding paths, we need to trim the split character at the
   126  		// beginning.
   127  		path = path[1:]
   128  
   129  		// Parse the forwarding endpoint URL to ensure that it's valid.
   130  		if _, _, err := forwarding.Parse(path); err != nil {
   131  			return nil, fmt.Errorf("invalid forwarding endpoint URL: %w", err)
   132  		}
   133  	} else {
   134  		panic("unhandled URL kind")
   135  	}
   136  
   137  	// Store any Docker environment variables that we need to preserve. We only
   138  	// store variables that are actually present, because Docker behavior will
   139  	// vary depending on whether a variable is unset vs. set but empty.
   140  	environment := make(map[string]string)
   141  	for _, variable := range DockerEnvironmentVariables {
   142  		if value, present := getEnvironmentVariable(variable, kind, first); present {
   143  			environment[variable] = value
   144  		}
   145  	}
   146  
   147  	// Success.
   148  	return &URL{
   149  		Kind:        kind,
   150  		Protocol:    Protocol_Docker,
   151  		User:        username,
   152  		Host:        container,
   153  		Path:        path,
   154  		Environment: environment,
   155  	}, nil
   156  }