mirror of https://github.com/docker/cli.git
command: check for wsl mount path on windows
This checks for the equivalent WSL mount path on windows. WSL will mount the windows drives at `/mnt/c` (or whichever drive is being used). This is done by parsing a UNC path with forward slashes from the unix socket URL. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
This commit is contained in:
parent
6372ec9c75
commit
38c3fef1a8
|
@ -5,9 +5,14 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
|
@ -77,14 +82,7 @@ func dockerExporterOTLPEndpoint(cli Cli) (endpoint string, secure bool) {
|
||||||
|
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case "unix":
|
case "unix":
|
||||||
// Unix sockets are a bit weird. OTEL seems to imply they
|
endpoint = unixSocketEndpoint(u)
|
||||||
// can be used as an environment variable and are handled properly,
|
|
||||||
// but they don't seem to be as the behavior of the environment variable
|
|
||||||
// is to strip the scheme from the endpoint, but the underlying implementation
|
|
||||||
// needs the scheme to use the correct resolver.
|
|
||||||
//
|
|
||||||
// We'll just handle this in a special way and add the unix:// back to the endpoint.
|
|
||||||
endpoint = "unix://" + path.Join(u.Host, u.Path)
|
|
||||||
case "https":
|
case "https":
|
||||||
secure = true
|
secure = true
|
||||||
fallthrough
|
fallthrough
|
||||||
|
@ -135,3 +133,109 @@ func dockerMetricExporter(ctx context.Context, cli Cli) []sdkmetric.Option {
|
||||||
}
|
}
|
||||||
return []sdkmetric.Option{sdkmetric.WithReader(newCLIReader(exp))}
|
return []sdkmetric.Option{sdkmetric.WithReader(newCLIReader(exp))}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unixSocketEndpoint converts the unix scheme from URL to
|
||||||
|
// an OTEL endpoint that can be used with the OTLP exporter.
|
||||||
|
//
|
||||||
|
// The OTLP exporter handles unix sockets in a strange way.
|
||||||
|
// It seems to imply they can be used as an environment variable
|
||||||
|
// and are handled properly, but they don't seem to be as the behavior
|
||||||
|
// of the environment variable is to strip the scheme from the endpoint
|
||||||
|
// while the underlying implementation needs the scheme to use the
|
||||||
|
// correct resolver.
|
||||||
|
func unixSocketEndpoint(u *url.URL) string {
|
||||||
|
// GRPC does not allow host to be used.
|
||||||
|
socketPath := u.Path
|
||||||
|
|
||||||
|
// If we are on windows and we have an absolute path
|
||||||
|
// that references a letter drive, check to see if the
|
||||||
|
// WSL equivalent path exists and we should use that instead.
|
||||||
|
if isWsl() {
|
||||||
|
if p := wslSocketPath(socketPath, os.DirFS("/")); p != "" {
|
||||||
|
socketPath = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Enforce that we are using forward slashes.
|
||||||
|
return "unix://" + filepath.ToSlash(socketPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wslSocketPath will convert the referenced URL to a WSL-compatible
|
||||||
|
// path and check if that path exists. If the path exists, it will
|
||||||
|
// be returned.
|
||||||
|
func wslSocketPath(s string, f fs.FS) string {
|
||||||
|
if p := toWslPath(s); p != "" {
|
||||||
|
if _, err := stat(p, f); err == nil {
|
||||||
|
return "/" + p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// toWslPath converts the referenced URL to a WSL-compatible
|
||||||
|
// path if this looks like a Windows absolute path.
|
||||||
|
//
|
||||||
|
// If no drive is in the URL, defaults to the C drive.
|
||||||
|
func toWslPath(s string) string {
|
||||||
|
drive, p, ok := parseUNCPath(s)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("mnt/%s%s", drive, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUNCPath(s string) (drive, p string, ok bool) {
|
||||||
|
// UNC paths use backslashes but we're using forward slashes
|
||||||
|
// so also enforce that here.
|
||||||
|
//
|
||||||
|
// In reality, this should have been enforced much earlier
|
||||||
|
// than here since backslashes aren't allowed in URLs, but
|
||||||
|
// we're going to code defensively here.
|
||||||
|
s = filepath.ToSlash(s)
|
||||||
|
|
||||||
|
const uncPrefix = "//./"
|
||||||
|
if !strings.HasPrefix(s, uncPrefix) {
|
||||||
|
// Not a UNC path.
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
s = s[len(uncPrefix):]
|
||||||
|
|
||||||
|
parts := strings.SplitN(s, "/", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
// Not enough components.
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
drive, ok = splitWindowsDrive(parts[0])
|
||||||
|
if !ok {
|
||||||
|
// Not a windows drive.
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
return drive, "/" + parts[1], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitWindowsDrive checks if the string references a windows
|
||||||
|
// drive (such as c:) and returns the drive letter if it is.
|
||||||
|
func splitWindowsDrive(s string) (string, bool) {
|
||||||
|
if b := []rune(s); len(b) == 2 && unicode.IsLetter(b[0]) && b[1] == ':' {
|
||||||
|
return string(b[0]), true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func stat(p string, f fs.FS) (fs.FileInfo, error) {
|
||||||
|
if f, ok := f.(fs.StatFS); ok {
|
||||||
|
return f.Stat(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := f.Open(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
return file.Stat()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWsl() bool {
|
||||||
|
return os.Getenv("WSL_DISTRO_NAME") != ""
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWslSocketPath(t *testing.T) {
|
||||||
|
u, err := url.Parse("unix:////./c:/my/file/path")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
// Ensure host is empty.
|
||||||
|
assert.Equal(t, u.Host, "")
|
||||||
|
|
||||||
|
// Use a filesystem where the WSL path exists.
|
||||||
|
fs := fstest.MapFS{
|
||||||
|
"mnt/c/my/file/path": {},
|
||||||
|
}
|
||||||
|
assert.Equal(t, wslSocketPath(u.Path, fs), "/mnt/c/my/file/path")
|
||||||
|
|
||||||
|
// Use a filesystem where the WSL path doesn't exist.
|
||||||
|
fs = fstest.MapFS{
|
||||||
|
"my/file/path": {},
|
||||||
|
}
|
||||||
|
assert.Equal(t, wslSocketPath(u.Path, fs), "")
|
||||||
|
}
|
Loading…
Reference in New Issue