Ctrl K
ComunidadGuías…

cli/dockercompose/hostuser.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package dockercompose

import (
	"context"
	"errors"
	"fmt"
	"net/url"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strings"
)

// Values accepted by the --user flag besides an explicit "<uid>:<gid>".
const (
	HostUserAuto = "auto"
	HostUserNone = "none"
)

// For rootful docker daemon.
const defaultDockerEndpoint = "unix:///var/run/docker.sock"

type dockerHostResolution struct {
	dockerURL       *url.URL
	known           bool
	fromEnvironment bool
}

func detectDockerHost(ctx context.Context) (dockerHostResolution, error) {
	endpoint := os.Getenv("DOCKER_HOST")
	fromEnvironment := endpoint != ""
	known := fromEnvironment

	if !known {
		out, err := exec.CommandContext(ctx, "docker", "context", "inspect",
			"--format", "{{.Endpoints.docker.Host}}").Output()
		if err != nil {
			if ctxErr := ctx.Err(); ctxErr != nil {
				return dockerHostResolution{}, fmt.Errorf(
					"inspecting current Docker context: %w",
					ctxErr,
				)
			}

			endpoint = defaultDockerEndpoint
		} else {
			endpoint = strings.TrimSpace(string(out))
			known = endpoint != ""
		}
	}

	if endpoint == "" {
		endpoint = defaultDockerEndpoint
	}

	dockerURL, err := url.Parse(endpoint)
	if err != nil {
		return dockerHostResolution{}, fmt.Errorf("parsing Docker endpoint %q: %w", endpoint, err)
	}

	return dockerHostResolution{
		dockerURL:       dockerURL,
		known:           known,
		fromEnvironment: fromEnvironment,
	}, nil
}

func dockerHostForSocketConsumers(
	resolution dockerHostResolution,
	hostOS string,
) (*url.URL, error) {
	if hostOS == osLinux || resolution.fromEnvironment {
		return resolution.dockerURL, nil
	}

	// Docker Desktop context sockets are client-side proxies that containers cannot mount.
	dockerURL, err := url.Parse(defaultDockerEndpoint)
	if err != nil {
		return nil, fmt.Errorf("parsing default Docker endpoint: %w", err)
	}

	return dockerURL, nil
}

func resolveDockerHost(ctx context.Context) (*url.URL, error) {
	resolution, err := detectDockerHost(ctx)
	if err != nil {
		return nil, err
	}

	return dockerHostForSocketConsumers(resolution, runtime.GOOS)
}

// The `auto` heuristic: map containers to the
// caller's uid:gid only on Linux hosts talking to the default rootful
// daemon socket, where container root would otherwise write root-owned
// files into bind mounts. Any other endpoint (rootless daemons, ssh://, tcp://, ...)
// already remaps or isolates ownership, so no user is forced.
func autoUser(hostOS, endpoint string, uid, gid int) string {
	if hostOS != osLinux {
		return ""
	}

	path, ok := strings.CutPrefix(endpoint, "unix://")
	if !ok {
		return "" // ssh://, tcp://, npipe://
	}

	if path != "/var/run/docker.sock" &&
		path != "/run/docker.sock" &&
		path != "/var/run/podman/podman.sock" &&
		path != "/run/podman/podman.sock" {
		return "" // $XDG_RUNTIME_DIR, ~/.docker/desktop, ~/.colima, ...
	}

	return fmt.Sprintf("%d:%d", uid, gid)
}

var (
	uidGidRegex = regexp.MustCompile(`^[0-9]+:[0-9]+$`)

	errInvalidHostUser = errors.New(
		`must be "` + HostUserAuto + `", "` + HostUserNone + `" or "<uid>:<gid>"`,
	)
)

type hostUserEnvironment struct {
	hostOS           string
	uid              int
	gid              int
	detectDockerHost func(context.Context) (dockerHostResolution, error)
}

func resolveHostUser(
	ctx context.Context,
	value string,
	environment hostUserEnvironment,
) (string, error) {
	switch value {
	case HostUserAuto, "":
		resolution, err := environment.detectDockerHost(ctx)
		if err != nil {
			return "", fmt.Errorf("resolving Docker endpoint: %w", err)
		}

		if !resolution.known {
			// The fallback socket does not prove that the daemon is rootful.
			return "", nil
		}

		return autoUser(
			environment.hostOS,
			resolution.dockerURL.String(),
			environment.uid,
			environment.gid,
		), nil
	case HostUserNone:
		return "", nil
	default:
		if !uidGidRegex.MatchString(value) {
			return "", fmt.Errorf("invalid user %q: %w", value, errInvalidHostUser)
		}

		return value, nil
	}
}

func ResolveHostUser(ctx context.Context, value string) (string, error) {
	return resolveHostUser(ctx, value, hostUserEnvironment{
		hostOS:           runtime.GOOS,
		uid:              os.Getuid(),
		gid:              os.Getgid(),
		detectDockerHost: detectDockerHost,
	})
}