diff --git a/act/container/docker_auth.go b/act/container/docker_auth.go index cf046e71..94c8fe71 100644 --- a/act/container/docker_auth.go +++ b/act/container/docker_auth.go @@ -8,34 +8,38 @@ package container import ( "context" - "strings" "gitea.com/gitea/runner/act/common" + "github.com/distribution/reference" "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/credentials" - "github.com/docker/docker/api/types/registry" + "github.com/moby/moby/api/types/registry" ) func LoadDockerAuthConfig(ctx context.Context, image string) (registry.AuthConfig, error) { logger := common.Logger(ctx) - config, err := config.Load(config.Dir()) + // config.LoadDefaultConfigFile panics on nil io.Writer when the config + // file is malformed; use config.Load to route errors through the logger. + cfg, err := config.Load(config.Dir()) if err != nil { logger.Warnf("Could not load docker config: %v", err) return registry.AuthConfig{}, err } - - if !config.ContainsAuth() { - config.CredentialsStore = credentials.DetectDefaultStore(config.CredentialsStore) + if !cfg.ContainsAuth() { + cfg.CredentialsStore = credentials.DetectDefaultStore(cfg.CredentialsStore) } - hostName := "index.docker.io" - index := strings.IndexRune(image, '/') - if index > -1 && (strings.ContainsAny(image[:index], ".:") || image[:index] == "localhost") { - hostName = image[:index] + registryKey := registryAuthConfigKey("docker.io") + if image != "" { + if registryRef, refErr := reference.ParseNormalizedNamed(image); refErr != nil { + logger.Warnf("Could not normalize image reference: %v", refErr) + } else { + registryKey = registryAuthConfigKey(reference.Domain(registryRef)) + } } - authConfig, err := config.GetAuthConfig(hostName) + authConfig, err := cfg.GetAuthConfig(registryKey) if err != nil { logger.Warnf("Could not get auth config from docker config: %v", err) return registry.AuthConfig{}, err @@ -46,17 +50,20 @@ func LoadDockerAuthConfig(ctx context.Context, image string) (registry.AuthConfi func LoadDockerAuthConfigs(ctx context.Context) map[string]registry.AuthConfig { logger := common.Logger(ctx) - config, err := config.Load(config.Dir()) + cfg, err := config.Load(config.Dir()) if err != nil { logger.Warnf("Could not load docker config: %v", err) return nil } - - if !config.ContainsAuth() { - config.CredentialsStore = credentials.DetectDefaultStore(config.CredentialsStore) + if !cfg.ContainsAuth() { + cfg.CredentialsStore = credentials.DetectDefaultStore(cfg.CredentialsStore) } - creds, _ := config.GetAllCredentials() + creds, err := cfg.GetAllCredentials() + if err != nil { + logger.Warnf("Could not get docker auth configs: %v", err) + return nil + } authConfigs := make(map[string]registry.AuthConfig, len(creds)) for k, v := range creds { authConfigs[k] = registry.AuthConfig(v) @@ -64,3 +71,10 @@ func LoadDockerAuthConfigs(ctx context.Context) map[string]registry.AuthConfig { return authConfigs } + +func registryAuthConfigKey(domainName string) string { + if domainName == "docker.io" || domainName == "index.docker.io" { + return "https://index.docker.io/v1/" + } + return domainName +} diff --git a/act/container/docker_build.go b/act/container/docker_build.go index 44c1692a..6adedfff 100644 --- a/act/container/docker_build.go +++ b/act/container/docker_build.go @@ -14,10 +14,12 @@ import ( "gitea.com/gitea/runner/act/common" - "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/archive" - "github.com/moby/buildkit/frontend/dockerfile/dockerignore" + "github.com/moby/go-archive" + "github.com/moby/go-archive/compression" + "github.com/moby/moby/client" "github.com/moby/patternmatcher" + "github.com/moby/patternmatcher/ignorefile" + specs "github.com/opencontainers/image-spec/specs-go/v1" ) // NewDockerBuildExecutor function to create a run executor for the container @@ -42,13 +44,19 @@ func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor { logger.Debugf("Building image from '%v'", input.ContextDir) tags := []string{input.ImageTag} - options := types.ImageBuildOptions{ + options := client.ImageBuildOptions{ Tags: tags, Remove: true, - Platform: input.Platform, AuthConfigs: LoadDockerAuthConfigs(ctx), Dockerfile: input.Dockerfile, } + platform, err := parsePlatform(input.Platform) + if err != nil { + return err + } + if platform != nil { + options.Platforms = []specs.Platform{*platform} + } var buildContext io.ReadCloser if input.BuildContext != nil { buildContext = io.NopCloser(input.BuildContext) @@ -76,7 +84,7 @@ func createBuildContext(ctx context.Context, contextDir, relDockerfile string) ( common.Logger(ctx).Debugf("Creating archive for build context dir '%s' with relative dockerfile '%s'", contextDir, relDockerfile) // And canonicalize dockerfile name to a platform-independent one - relDockerfile = archive.CanonicalTarNameForPath(relDockerfile) + relDockerfile = filepath.ToSlash(relDockerfile) f, err := os.Open(filepath.Join(contextDir, ".dockerignore")) if err != nil && !os.IsNotExist(err) { @@ -86,7 +94,7 @@ func createBuildContext(ctx context.Context, contextDir, relDockerfile string) ( var excludes []string if err == nil { - excludes, err = dockerignore.ReadAll(f) //nolint:staticcheck // pre-existing issue from nektos/act + excludes, err = ignorefile.ReadAll(f) if err != nil { return nil, err } @@ -106,9 +114,8 @@ func createBuildContext(ctx context.Context, contextDir, relDockerfile string) ( includes = append(includes, ".dockerignore", relDockerfile) } - compression := archive.Uncompressed buildCtx, err := archive.TarWithOptions(contextDir, &archive.TarOptions{ - Compression: compression, + Compression: compression.None, ExcludePatterns: excludes, IncludeFiles: includes, }) diff --git a/act/container/docker_cli.go b/act/container/docker_cli.go index 7beb8e1f..ca8e7b5a 100644 --- a/act/container/docker_cli.go +++ b/act/container/docker_cli.go @@ -4,140 +4,153 @@ //go:build !(WITHOUT_DOCKER || !(linux || darwin || windows || netbsd)) -// This file is exact copy of https://github.com/docker/cli/blob/9ac8584acfd501c3f4da0e845e3a40ed15c85041/cli/command/container/opts.go +// This file is exact copy of https://github.com/docker/cli/blob/9a471180cb7d39c236d090399a9d362c3f5a8ebd/cli/command/container/opts.go // appended with license information. // // docker/cli is licensed under the Apache License, Version 2.0. // See DOCKER_LICENSE for the full license text. // -//nolint:errcheck,depguard,unused // verbatim copy from docker/cli with minimal changes +//nolint:errcheck // verbatim copy from docker/cli with minimal changes package container import ( "bytes" "encoding/json" + "errors" "fmt" + "net" + "net/netip" "os" "path" "path/filepath" "reflect" "regexp" "slices" - "strconv" "strings" "time" "github.com/docker/cli/cli/compose/loader" "github.com/docker/cli/opts" - "github.com/docker/docker/api/types/container" - mounttypes "github.com/docker/docker/api/types/mount" - networktypes "github.com/docker/docker/api/types/network" - "github.com/docker/docker/api/types/strslice" - "github.com/docker/docker/api/types/versions" - "github.com/docker/docker/errdefs" "github.com/docker/go-connections/nat" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" + "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/mount" + "github.com/moby/moby/api/types/network" "github.com/spf13/pflag" + cdi "tags.cncf.io/container-device-interface/pkg/parser" +) + +const ( + // TODO(thaJeztah): define these in the API-types, or query available defaults + // from the daemon, or require "local" profiles to be an absolute path or + // relative paths starting with "./". The daemon-config has consts for this + // but we don't want to import that package: + // https://github.com/moby/moby/blob/v23.0.0/daemon/config/config.go#L63-L67 + + // seccompProfileDefault is the built-in default seccomp profile. + seccompProfileDefault = "builtin" + // seccompProfileUnconfined is a special profile name for seccomp to use an + // "unconfined" seccomp profile. + seccompProfileUnconfined = "unconfined" ) var deviceCgroupRuleRegexp = regexp.MustCompile(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`) // containerOptions is a data object with all the options for creating a container type containerOptions struct { - attach opts.ListOpts - volumes opts.ListOpts - tmpfs opts.ListOpts - mounts opts.MountOpt - blkioWeightDevice opts.WeightdeviceOpt - deviceReadBps opts.ThrottledeviceOpt - deviceWriteBps opts.ThrottledeviceOpt - links opts.ListOpts - aliases opts.ListOpts - linkLocalIPs opts.ListOpts - deviceReadIOps opts.ThrottledeviceOpt - deviceWriteIOps opts.ThrottledeviceOpt - env opts.ListOpts - labels opts.ListOpts - deviceCgroupRules opts.ListOpts - devices opts.ListOpts - gpus opts.GpuOpts - ulimits *opts.UlimitOpt - sysctls *opts.MapOpts - publish opts.ListOpts - expose opts.ListOpts - dns opts.ListOpts - dnsSearch opts.ListOpts - dnsOptions opts.ListOpts - extraHosts opts.ListOpts - volumesFrom opts.ListOpts - envFile opts.ListOpts - capAdd opts.ListOpts - capDrop opts.ListOpts - groupAdd opts.ListOpts - securityOpt opts.ListOpts - storageOpt opts.ListOpts - labelsFile opts.ListOpts - loggingOpts opts.ListOpts - privileged bool - pidMode string - utsMode string - usernsMode string - cgroupnsMode string - publishAll bool - stdin bool - tty bool - oomKillDisable bool - oomScoreAdj int - containerIDFile string - entrypoint string - hostname string - domainname string - memory opts.MemBytes - memoryReservation opts.MemBytes - memorySwap opts.MemSwapBytes - kernelMemory opts.MemBytes - user string - workingDir string - cpuCount int64 - cpuShares int64 - cpuPercent int64 - cpuPeriod int64 - cpuRealtimePeriod int64 - cpuRealtimeRuntime int64 - cpuQuota int64 - cpus opts.NanoCPUs - cpusetCpus string - cpusetMems string - blkioWeight uint16 - ioMaxBandwidth opts.MemBytes - ioMaxIOps uint64 - swappiness int64 - netMode opts.NetworkOpt - macAddress string - ipv4Address string - ipv6Address string - ipcMode string - pidsLimit int64 - restartPolicy string - readonlyRootfs bool - loggingDriver string - cgroupParent string - volumeDriver string - stopSignal string - stopTimeout int - isolation string - shmSize opts.MemBytes - noHealthcheck bool - healthCmd string - healthInterval time.Duration - healthTimeout time.Duration - healthStartPeriod time.Duration - healthRetries int - runtime string - autoRemove bool - init bool + attach opts.ListOpts + volumes opts.ListOpts + tmpfs opts.ListOpts + mounts opts.MountOpt + blkioWeightDevice opts.WeightdeviceOpt + deviceReadBps opts.ThrottledeviceOpt + deviceWriteBps opts.ThrottledeviceOpt + links opts.ListOpts + aliases opts.ListOpts + linkLocalIPs opts.ListOpts // TODO(thaJeztah): we need a flag-type to handle []netip.Addr directly + deviceReadIOps opts.ThrottledeviceOpt + deviceWriteIOps opts.ThrottledeviceOpt + env opts.ListOpts + labels opts.ListOpts + deviceCgroupRules opts.ListOpts + devices opts.ListOpts + gpus opts.GpuOpts + ulimits *opts.UlimitOpt + sysctls *opts.MapOpts + publish opts.ListOpts + expose opts.ListOpts + dns opts.ListOpts // TODO(thaJeztah): we need a flag-type to handle []netip.Addr directly + dnsSearch opts.ListOpts + dnsOptions opts.ListOpts + extraHosts opts.ListOpts + volumesFrom opts.ListOpts + envFile opts.ListOpts + capAdd opts.ListOpts + capDrop opts.ListOpts + groupAdd opts.ListOpts + securityOpt opts.ListOpts + storageOpt opts.ListOpts + labelsFile opts.ListOpts + loggingOpts opts.ListOpts + privileged bool + pidMode string + utsMode string + usernsMode string + cgroupnsMode string + publishAll bool + stdin bool + tty bool + oomKillDisable bool + oomScoreAdj int + containerIDFile string + entrypoint string + hostname string + domainname string + memory opts.MemBytes + memoryReservation opts.MemBytes + memorySwap opts.MemSwapBytes + user string + workingDir string + cpuCount int64 + cpuShares int64 + cpuPercent int64 + cpuPeriod int64 + cpuRealtimePeriod int64 + cpuRealtimeRuntime int64 + cpuQuota int64 + cpus opts.NanoCPUs + cpusetCpus string + cpusetMems string + blkioWeight uint16 + ioMaxBandwidth opts.MemBytes + ioMaxIOps uint64 + swappiness int64 + netMode opts.NetworkOpt + macAddress string + ipv4Address net.IP // TODO(thaJeztah): we need a flag-type to handle netip.Addr directly + ipv6Address net.IP // TODO(thaJeztah): we need a flag-type to handle netip.Addr directly + ipcMode string + pidsLimit int64 + restartPolicy string + readonlyRootfs bool + loggingDriver string + cgroupParent string + volumeDriver string + stopSignal string + stopTimeout int + isolation string + shmSize opts.MemBytes + noHealthcheck bool + healthCmd string + healthInterval time.Duration + healthTimeout time.Duration + healthStartPeriod time.Duration + healthStartInterval time.Duration + healthRetries int + runtime string + autoRemove bool + init bool + annotations *opts.MapOpts Image string Args []string @@ -178,6 +191,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { ulimits: opts.NewUlimitOpt(nil), volumes: opts.NewListOpts(nil), volumesFrom: opts.NewListOpts(nil), + annotations: opts.NewMapOpts(nil, nil), } // General purpose flags @@ -196,7 +210,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { flags.VarP(&copts.labels, "label", "l", "Set meta data on a container") flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels") flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only") - flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits") + flags.StringVar(&copts.restartPolicy, "restart", string(container.RestartPolicyDisabled), "Restart policy to apply when a container exits") flags.StringVar(&copts.stopSignal, "stop-signal", "", "Signal to stop the container") flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container") flags.SetAnnotation("stop-timeout", "version", []string{"1.25"}) @@ -205,7 +219,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { flags.Var(copts.ulimits, "ulimit", "Ulimit options") flags.StringVarP(&copts.user, "user", "u", "", "Username or UID (format: [:])") flags.StringVarP(&copts.workingDir, "workdir", "w", "", "Working directory inside the container") - flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container when it exits") + flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container and its associated anonymous volumes when it exits") // Security flags.Var(&copts.capAdd, "cap-add", "Add Linux capabilities") @@ -230,8 +244,8 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { flags.MarkHidden("dns-opt") flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains") flags.Var(&copts.expose, "expose", "Expose a port or a range of ports") - flags.StringVar(&copts.ipv4Address, "ip", "", "IPv4 address (e.g., 172.30.100.104)") - flags.StringVar(&copts.ipv6Address, "ip6", "", "IPv6 address (e.g., 2001:db8::33)") + flags.IPVar(&copts.ipv4Address, "ip", nil, "IPv4 address (e.g., 172.30.100.104)") + flags.IPVar(&copts.ipv6Address, "ip6", nil, "IPv6 address (e.g., 2001:db8::33)") flags.Var(&copts.links, "link", "Add link to another container") flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses") flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g., 92:d0:c6:0a:29:33)") @@ -263,6 +277,8 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ms|s|m|h) (default 0s)") flags.DurationVar(&copts.healthStartPeriod, "health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) (default 0s)") flags.SetAnnotation("health-start-period", "version", []string{"1.29"}) + flags.DurationVar(&copts.healthStartInterval, "health-start-interval", 0, "Time between running the check during the start period (ms|s|m|h) (default 0s)") + flags.SetAnnotation("health-start-interval", "version", []string{"1.44"}) flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK") // Resource management @@ -292,7 +308,6 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { flags.SetAnnotation("io-maxbandwidth", "ostype", []string{"windows"}) flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)") flags.SetAnnotation("io-maxiops", "ostype", []string{"windows"}) - flags.Var(&copts.kernelMemory, "kernel-memory", "Kernel memory limit") flags.VarP(&copts.memory, "memory", "m", "Memory limit") flags.Var(&copts.memoryReservation, "memory-reservation", "Memory soft limit") flags.Var(&copts.memorySwap, "memory-swap", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap") @@ -312,19 +327,28 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes") flags.SetAnnotation("init", "version", []string{"1.25"}) + + flags.Var(copts.annotations, "annotation", "Add an annotation to the container (passed through to the OCI runtime)") + flags.SetAnnotation("annotation", "version", []string{"1.43"}) + + // TODO(thaJeztah): remove in next release (v30.0, or v29.x) + var stub opts.MemBytes + flags.Var(&stub, "kernel-memory", "Kernel memory limit (deprecated)") + _ = flags.MarkDeprecated("kernel-memory", "and no longer supported by the kernel") + return copts } type containerConfig struct { Config *container.Config HostConfig *container.HostConfig - NetworkingConfig *networktypes.NetworkingConfig + NetworkingConfig *network.NetworkingConfig } // parse parses the args for the specified command and generates a Config, // a HostConfig and returns them with the specified command. // If the specified args are not valid, it will return an error. -func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*containerConfig, error) { +func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*containerConfig, error) { //nolint:gocyclo // verbatim copy from docker/cli var ( attachStdin = copts.attach.Get("stdin") attachStdout = copts.attach.Get("stdout") @@ -333,8 +357,8 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con // Validate the input mac address if copts.macAddress != "" { - if _, err := opts.ValidateMACAddress(copts.macAddress); err != nil { - return nil, errors.Errorf("%s is not a valid mac address", copts.macAddress) + if _, err := net.ParseMAC(strings.TrimSpace(copts.macAddress)); err != nil { + return nil, fmt.Errorf("%s is not a valid mac address", copts.macAddress) } } if copts.stdin { @@ -350,31 +374,29 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con swappiness := copts.swappiness if swappiness != -1 && (swappiness < 0 || swappiness > 100) { - return nil, errors.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness) + return nil, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness) } - mounts := copts.mounts.Value() - if len(mounts) > 0 && copts.volumeDriver != "" { - logrus.Warn("`--volume-driver` is ignored for volumes specified via `--mount`. Use `--mount type=volume,volume-driver=...` instead.") - } var binds []string volumes := copts.volumes.GetMap() // add any bind targets to the list of container volumes for bind := range copts.volumes.GetMap() { - parsed, _ := loader.ParseVolume(bind) + parsed, err := loader.ParseVolume(bind) + if err != nil { + return nil, err + } if parsed.Source != "" { toBind := bind - if parsed.Type == string(mounttypes.TypeBind) { - if arr := strings.SplitN(bind, ":", 2); len(arr) == 2 { - hostPart := arr[0] - if strings.HasPrefix(hostPart, "."+string(filepath.Separator)) || hostPart == "." { + if parsed.Type == string(mount.TypeBind) { + if hostPart, targetPath, ok := strings.Cut(bind, ":"); ok { + if !filepath.IsAbs(hostPart) && strings.HasPrefix(hostPart, ".") { if absHostPart, err := filepath.Abs(hostPart); err == nil { hostPart = absHostPart } } - toBind = hostPart + ":" + arr[1] + toBind = hostPart + ":" + targetPath } } @@ -389,69 +411,81 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con // Can't evaluate options passed into --tmpfs until we actually mount tmpfs := make(map[string]string) - for _, t := range copts.tmpfs.GetAll() { - if arr := strings.SplitN(t, ":", 2); len(arr) > 1 { - tmpfs[arr[0]] = arr[1] - } else { - tmpfs[arr[0]] = "" - } + for _, t := range copts.tmpfs.GetSlice() { + k, v, _ := strings.Cut(t, ":") + tmpfs[k] = v } - var ( - runCmd strslice.StrSlice - entrypoint strslice.StrSlice - ) + var runCmd, entrypoint []string if len(copts.Args) > 0 { - runCmd = strslice.StrSlice(copts.Args) + runCmd = copts.Args } if copts.entrypoint != "" { - entrypoint = strslice.StrSlice{copts.entrypoint} + entrypoint = []string{copts.entrypoint} } else if flags.Changed("entrypoint") { // if `--entrypoint=` is parsed then Entrypoint is reset entrypoint = []string{""} } - publishOpts := copts.publish.GetAll() - var ( - ports map[nat.Port]struct{} - portBindings map[nat.Port][]nat.PortBinding - convertedOpts []string - ) - - convertedOpts, err = convertToStandardNotation(publishOpts) + // TODO(thaJeztah): remove uses of go-connections/nat here. + convertedOpts, err := convertToStandardNotation(copts.publish.GetSlice()) if err != nil { return nil, err } - ports, portBindings, err = nat.ParsePortSpecs(convertedOpts) + // short syntax ([ip:]public:private[/proto]) + // + // TODO(thaJeztah): we need an equivalent that handles the "ip-address" part without depending on the nat package. + ports, natPortBindings, err := nat.ParsePortSpecs(convertedOpts) if err != nil { return nil, err } + portBindings := network.PortMap{} + for port, bindings := range natPortBindings { + p, err := network.ParsePort(string(port)) + if err != nil { + return nil, err + } + portBindings[p] = []network.PortBinding{} + for _, b := range bindings { + var hostIP netip.Addr + if b.HostIP != "" { + hostIP, err = netip.ParseAddr(b.HostIP) + if err != nil { + return nil, err + } + } + portBindings[p] = append(portBindings[p], network.PortBinding{ + HostIP: hostIP, + HostPort: b.HostPort, + }) + } + } + + // Add published ports as exposed ports. + exposedPorts := network.PortSet{} + for port := range ports { + p, err := network.ParsePort(string(port)) + if err != nil { + return nil, err + } + exposedPorts[p] = struct{}{} + } // Merge in exposed ports to the map of published ports - for _, e := range copts.expose.GetAll() { - if strings.Contains(e, ":") { - return nil, errors.Errorf("invalid port format for --expose: %s", e) - } + for _, e := range copts.expose.GetSlice() { // support two formats for expose, original format /[] // or /[] - proto, port := nat.SplitProtoPort(e) + pr, err := network.ParsePortRange(e) + if err != nil { + return nil, fmt.Errorf("invalid range format for --expose: %w", err) + } // parse the start and end port and create a sequence of ports to expose // if expose a port, the start and end port are the same - start, end, err := nat.ParsePortRange(port) - if err != nil { - return nil, errors.Errorf("invalid range format for --expose: %s, error: %s", e, err) - } - for i := start; i <= end; i++ { - p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) - if err != nil { - return nil, err - } - if _, exists := ports[p]; !exists { - ports[p] = struct{}{} - } + for p := range pr.All() { + exposedPorts[p] = struct{}{} } } @@ -459,18 +493,19 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con // device path (as opposed to during flag parsing), as at the time we are // parsing flags, we haven't yet sent a _ping to the daemon to determine // what operating system it is. - deviceMappings := []container.DeviceMapping{} - for _, device := range copts.devices.GetAll() { - var ( - validated string - deviceMapping container.DeviceMapping - err error - ) - validated, err = validateDevice(device, serverOS) + devices := copts.devices.GetSlice() + deviceMappings := make([]container.DeviceMapping, 0, len(devices)) + cdiDeviceNames := make([]string, 0, len(devices)) + for _, device := range devices { + if cdi.IsQualifiedName(device) { + cdiDeviceNames = append(cdiDeviceNames, device) + continue + } + validated, err := validateDevice(device, serverOS) if err != nil { return nil, err } - deviceMapping, err = parseDevice(validated, serverOS) + deviceMapping, err := parseDevice(validated, serverOS) if err != nil { return nil, err } @@ -478,35 +513,35 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con } // collect all the environment variables for the container - envVariables, err := opts.ReadKVEnvStrings(copts.envFile.GetAll(), copts.env.GetAll()) + envVariables, err := opts.ReadKVEnvStrings(copts.envFile.GetSlice(), copts.env.GetSlice()) if err != nil { return nil, err } // collect all the labels for the container - labels, err := opts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll()) + labels, err := opts.ReadKVStrings(copts.labelsFile.GetSlice(), copts.labels.GetSlice()) if err != nil { return nil, err } pidMode := container.PidMode(copts.pidMode) if !pidMode.Valid() { - return nil, errors.Errorf("--pid: invalid PID mode") + return nil, errors.New("--pid: invalid PID mode") } utsMode := container.UTSMode(copts.utsMode) if !utsMode.Valid() { - return nil, errors.Errorf("--uts: invalid UTS mode") + return nil, errors.New("--uts: invalid UTS mode") } usernsMode := container.UsernsMode(copts.usernsMode) if !usernsMode.Valid() { - return nil, errors.Errorf("--userns: invalid USER mode") + return nil, errors.New("--userns: invalid USER mode") } cgroupnsMode := container.CgroupnsMode(copts.cgroupnsMode) if !cgroupnsMode.Valid() { - return nil, errors.Errorf("--cgroupns: invalid CGROUP mode") + return nil, errors.New("--cgroupns: invalid CGROUP mode") } restartPolicy, err := opts.ParseRestartPolicy(copts.restartPolicy) @@ -514,19 +549,19 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con return nil, err } - loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetAll()) + loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetSlice()) if err != nil { return nil, err } - securityOpts, err := parseSecurityOpts(copts.securityOpt.GetAll()) + securityOpts, err := parseSecurityOpts(copts.securityOpt.GetSlice()) if err != nil { return nil, err } securityOpts, maskedPaths, readonlyPaths := parseSystemPaths(securityOpts) - storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll()) + storageOpts, err := parseStorageOpts(copts.storageOpt.GetSlice()) if err != nil { return nil, err } @@ -537,48 +572,59 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con copts.healthInterval != 0 || copts.healthTimeout != 0 || copts.healthStartPeriod != 0 || - copts.healthRetries != 0 + copts.healthRetries != 0 || + copts.healthStartInterval != 0 if copts.noHealthcheck { if haveHealthSettings { - return nil, errors.Errorf("--no-healthcheck conflicts with --health-* options") + return nil, errors.New("--no-healthcheck conflicts with --health-* options") } - test := strslice.StrSlice{"NONE"} - healthConfig = &container.HealthConfig{Test: test} + healthConfig = &container.HealthConfig{Test: []string{"NONE"}} } else if haveHealthSettings { - var probe strslice.StrSlice + var probe []string if copts.healthCmd != "" { - args := []string{"CMD-SHELL", copts.healthCmd} - probe = strslice.StrSlice(args) + probe = []string{"CMD-SHELL", copts.healthCmd} } if copts.healthInterval < 0 { - return nil, errors.Errorf("--health-interval cannot be negative") + return nil, errors.New("--health-interval cannot be negative") } if copts.healthTimeout < 0 { - return nil, errors.Errorf("--health-timeout cannot be negative") + return nil, errors.New("--health-timeout cannot be negative") } if copts.healthRetries < 0 { - return nil, errors.Errorf("--health-retries cannot be negative") + return nil, errors.New("--health-retries cannot be negative") } if copts.healthStartPeriod < 0 { return nil, errors.New("--health-start-period cannot be negative") } + if copts.healthStartInterval < 0 { + return nil, errors.New("--health-start-interval cannot be negative") + } healthConfig = &container.HealthConfig{ - Test: probe, - Interval: copts.healthInterval, - Timeout: copts.healthTimeout, - StartPeriod: copts.healthStartPeriod, - Retries: copts.healthRetries, + Test: probe, + Interval: copts.healthInterval, + Timeout: copts.healthTimeout, + StartPeriod: copts.healthStartPeriod, + StartInterval: copts.healthStartInterval, + Retries: copts.healthRetries, } } + deviceRequests := copts.gpus.Value() + if len(cdiDeviceNames) > 0 { + cdiDeviceRequest := container.DeviceRequest{ + Driver: "cdi", + DeviceIDs: cdiDeviceNames, + } + deviceRequests = append(deviceRequests, cdiDeviceRequest) + } + resources := container.Resources{ CgroupParent: copts.cgroupParent, Memory: copts.memory.Value(), MemoryReservation: copts.memoryReservation.Value(), MemorySwap: copts.memorySwap.Value(), MemorySwappiness: &copts.swappiness, - KernelMemory: copts.kernelMemory.Value(), OomKillDisable: &copts.oomKillDisable, NanoCPUs: copts.cpus.Value(), CPUCount: copts.cpuCount, @@ -600,35 +646,30 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con IOMaximumIOps: copts.ioMaxIOps, IOMaximumBandwidth: uint64(copts.ioMaxBandwidth), Ulimits: copts.ulimits.GetList(), - DeviceCgroupRules: copts.deviceCgroupRules.GetAll(), + DeviceCgroupRules: copts.deviceCgroupRules.GetSlice(), Devices: deviceMappings, - DeviceRequests: copts.gpus.Value(), + DeviceRequests: deviceRequests, } config := &container.Config{ Hostname: copts.hostname, Domainname: copts.domainname, - ExposedPorts: ports, + ExposedPorts: exposedPorts, User: copts.user, Tty: copts.tty, - // TODO: deprecated, it comes from -n, --networking - // it's still needed internally to set the network to disabled - // if e.g. bridge is none in daemon opts, and in inspect - NetworkDisabled: false, - OpenStdin: copts.stdin, - AttachStdin: attachStdin, - AttachStdout: attachStdout, - AttachStderr: attachStderr, - Env: envVariables, - Cmd: runCmd, - Image: copts.Image, - Volumes: volumes, - MacAddress: copts.macAddress, - Entrypoint: entrypoint, - WorkingDir: copts.workingDir, - Labels: opts.ConvertKVStringsToMap(labels), - StopSignal: copts.stopSignal, - Healthcheck: healthConfig, + OpenStdin: copts.stdin, + AttachStdin: attachStdin, + AttachStdout: attachStdout, + AttachStderr: attachStderr, + Env: envVariables, + Cmd: runCmd, + Image: copts.Image, + Volumes: volumes, + Entrypoint: entrypoint, + WorkingDir: copts.workingDir, + Labels: opts.ConvertKVStringsToMap(labels), + StopSignal: copts.stopSignal, + Healthcheck: healthConfig, } if flags.Changed("stop-timeout") { config.StopTimeout = &copts.stopTimeout @@ -641,27 +682,27 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con AutoRemove: copts.autoRemove, Privileged: copts.privileged, PortBindings: portBindings, - Links: copts.links.GetAll(), + Links: copts.links.GetSlice(), PublishAllPorts: copts.publishAll, // Make sure the dns fields are never nil. // New containers don't ever have those fields nil, // but pre created containers can still have those nil values. // See https://github.com/docker/docker/pull/17779 // for a more detailed explanation on why we don't want that. - DNS: copts.dns.GetAllOrEmpty(), + DNS: toNetipAddrSlice(copts.dns.GetAllOrEmpty()), DNSSearch: copts.dnsSearch.GetAllOrEmpty(), DNSOptions: copts.dnsOptions.GetAllOrEmpty(), - ExtraHosts: copts.extraHosts.GetAll(), - VolumesFrom: copts.volumesFrom.GetAll(), + ExtraHosts: copts.extraHosts.GetSlice(), + VolumesFrom: copts.volumesFrom.GetSlice(), IpcMode: container.IpcMode(copts.ipcMode), NetworkMode: container.NetworkMode(copts.netMode.NetworkMode()), PidMode: pidMode, UTSMode: utsMode, UsernsMode: usernsMode, CgroupnsMode: cgroupnsMode, - CapAdd: strslice.StrSlice(copts.capAdd.GetAll()), - CapDrop: strslice.StrSlice(copts.capDrop.GetAll()), - GroupAdd: copts.groupAdd.GetAll(), + CapAdd: copts.capAdd.GetSlice(), + CapDrop: copts.capDrop.GetSlice(), + GroupAdd: copts.groupAdd.GetSlice(), RestartPolicy: restartPolicy, SecurityOpt: securityOpts, StorageOpt: storageOpts, @@ -674,13 +715,14 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con Tmpfs: tmpfs, Sysctls: copts.sysctls.GetAll(), Runtime: copts.runtime, - Mounts: mounts, + Mounts: copts.mounts.Value(), MaskedPaths: maskedPaths, ReadonlyPaths: readonlyPaths, + Annotations: copts.annotations.GetAll(), } if copts.autoRemove && !hostConfig.RestartPolicy.IsNone() { - return nil, errors.Errorf("Conflicting options: --restart and --rm") + return nil, errors.New("conflicting options: cannot specify both --restart and --rm") } // only set this value if the user provided the flag, else it should default to nil @@ -693,19 +735,17 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con config.StdinOnce = true } - networkingConfig := &networktypes.NetworkingConfig{ - EndpointsConfig: make(map[string]*networktypes.EndpointSettings), - } - - networkingConfig.EndpointsConfig, err = parseNetworkOpts(copts) + epCfg, err := parseNetworkOpts(copts) if err != nil { return nil, err } return &containerConfig{ - Config: config, - HostConfig: hostConfig, - NetworkingConfig: networkingConfig, + Config: config, + HostConfig: hostConfig, + NetworkingConfig: &network.NetworkingConfig{ + EndpointsConfig: epCfg, + }, }, nil } @@ -716,12 +756,26 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con // this function may return _multiple_ endpoints, which is not currently supported // by the daemon, but may be in future; it's up to the daemon to produce an error // in case that is not supported. -func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.EndpointSettings, error) { +func parseNetworkOpts(copts *containerOptions) (map[string]*network.EndpointSettings, error) { var ( - endpoints = make(map[string]*networktypes.EndpointSettings, len(copts.netMode.Value())) + endpoints = make(map[string]*network.EndpointSettings, len(copts.netMode.Value())) hasUserDefined, hasNonUserDefined bool ) + if len(copts.netMode.Value()) == 0 { + n := opts.NetworkAttachmentOpts{ + Target: "default", + } + if err := applyContainerOptions(&n, copts); err != nil { + return nil, err + } + ep, err := parseNetworkAttachmentOpt(n) + if err != nil { + return nil, err + } + endpoints["default"] = ep + } + for i, n := range copts.netMode.Value() { if container.NetworkMode(n.Target).IsUserDefined() { hasUserDefined = true @@ -743,64 +797,75 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.Endpoin return nil, err } if _, ok := endpoints[n.Target]; ok { - return nil, errdefs.InvalidParameter(errors.Errorf("network %q is specified multiple times", n.Target)) + return nil, invalidParameter(fmt.Errorf("network %q is specified multiple times", n.Target)) } // For backward compatibility: if no custom options are provided for the network, // and only a single network is specified, omit the endpoint-configuration // on the client (the daemon will still create it when creating the container) if i == 0 && len(copts.netMode.Value()) == 1 { - if ep == nil || reflect.DeepEqual(*ep, networktypes.EndpointSettings{}) { + if ep == nil || reflect.ValueOf(*ep).IsZero() { continue } } endpoints[n.Target] = ep } if hasUserDefined && hasNonUserDefined { - return nil, errdefs.InvalidParameter(errors.New("conflicting options: cannot attach both user-defined and non-user-defined network-modes")) + return nil, invalidParameter(errors.New("conflicting options: cannot attach both user-defined and non-user-defined network-modes")) } return endpoints, nil } -func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOptions) error { - // TODO should copts.MacAddress actually be set on the first network? (currently it's not) +func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOptions) error { //nolint:gocyclo // verbatim copy from docker/cli // TODO should we error if _any_ advanced option is used? (i.e. forbid to combine advanced notation with the "old" flags (`--network-alias`, `--link`, `--ip`, `--ip6`)? if len(n.Aliases) > 0 && copts.aliases.Len() > 0 { - return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --network-alias and per-network alias")) + return invalidParameter(errors.New("conflicting options: cannot specify both --network-alias and per-network alias")) } if len(n.Links) > 0 && copts.links.Len() > 0 { - return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --link and per-network links")) + return invalidParameter(errors.New("conflicting options: cannot specify both --link and per-network links")) } - if n.IPv4Address != "" && copts.ipv4Address != "" { - return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip and per-network IPv4 address")) + if n.IPv4Address.IsValid() && copts.ipv4Address != nil { + return invalidParameter(errors.New("conflicting options: cannot specify both --ip and per-network IPv4 address")) } - if n.IPv6Address != "" && copts.ipv6Address != "" { - return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip6 and per-network IPv6 address")) + if n.IPv6Address.IsValid() && copts.ipv6Address != nil { + return invalidParameter(errors.New("conflicting options: cannot specify both --ip6 and per-network IPv6 address")) + } + if n.MacAddress != "" && copts.macAddress != "" { + return invalidParameter(errors.New("conflicting options: cannot specify both --mac-address and per-network MAC address")) + } + if len(n.LinkLocalIPs) > 0 && copts.linkLocalIPs.Len() > 0 { + return invalidParameter(errors.New("conflicting options: cannot specify both --link-local-ip and per-network link-local IP addresses")) } if copts.aliases.Len() > 0 { n.Aliases = make([]string, copts.aliases.Len()) - copy(n.Aliases, copts.aliases.GetAll()) + copy(n.Aliases, copts.aliases.GetSlice()) } - if copts.links.Len() > 0 { + // For a user-defined network, "--link" is an endpoint option, it creates an alias. But, + // for the default bridge it defines a legacy-link. + if container.NetworkMode(n.Target).IsUserDefined() && copts.links.Len() > 0 { n.Links = make([]string, copts.links.Len()) - copy(n.Links, copts.links.GetAll()) + copy(n.Links, copts.links.GetSlice()) } - if copts.ipv4Address != "" { - n.IPv4Address = copts.ipv4Address + if copts.ipv4Address != nil { + if ipv4, ok := netip.AddrFromSlice(copts.ipv4Address.To4()); ok { + n.IPv4Address = ipv4 + } } - if copts.ipv6Address != "" { - n.IPv6Address = copts.ipv6Address + if copts.ipv6Address != nil { + if ipv6, ok := netip.AddrFromSlice(copts.ipv6Address.To16()); ok { + n.IPv6Address = ipv6 + } + } + if copts.macAddress != "" { + n.MacAddress = copts.macAddress } - - // TODO should linkLocalIPs be added to the _first_ network only, or to _all_ networks? (should this be a per-network option as well?) if copts.linkLocalIPs.Len() > 0 { - n.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len()) - copy(n.LinkLocalIPs, copts.linkLocalIPs.GetAll()) + n.LinkLocalIPs = toNetipAddrSlice(copts.linkLocalIPs.GetSlice()) } return nil } -func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.EndpointSettings, error) { +func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*network.EndpointSettings, error) { if strings.TrimSpace(ep.Target) == "" { return nil, errors.New("no name set for network") } @@ -813,7 +878,9 @@ func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.End } } - epConfig := &networktypes.EndpointSettings{} + epConfig := &network.EndpointSettings{ + GwPriority: ep.GwPriority, + } epConfig.Aliases = append(epConfig.Aliases, ep.Aliases...) if len(ep.DriverOpts) > 0 { epConfig.DriverOpts = make(map[string]string) @@ -822,13 +889,20 @@ func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.End if len(ep.Links) > 0 { epConfig.Links = ep.Links } - if ep.IPv4Address != "" || ep.IPv6Address != "" || len(ep.LinkLocalIPs) > 0 { - epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{ + if ep.IPv4Address.IsValid() || ep.IPv6Address.IsValid() || len(ep.LinkLocalIPs) > 0 { + epConfig.IPAMConfig = &network.EndpointIPAMConfig{ IPv4Address: ep.IPv4Address, IPv6Address: ep.IPv6Address, LinkLocalIPs: ep.LinkLocalIPs, } } + if ep.MacAddress != "" { + ma, err := net.ParseMAC(strings.TrimSpace(ep.MacAddress)) + if err != nil { + return nil, fmt.Errorf("%s is not a valid mac address", ep.MacAddress) + } + epConfig.MacAddress = network.HardwareAddr(ma) + } return epConfig, nil } @@ -838,12 +912,11 @@ func convertToStandardNotation(ports []string) ([]string, error) { if strings.Contains(publish, "=") { params := map[string]string{"protocol": "tcp"} for param := range strings.SplitSeq(publish, ",") { - opt := strings.Split(param, "=") - if len(opt) < 2 { - return optsList, errors.Errorf("invalid publish opts format (should be name=value but got '%s')", param) + k, v, ok := strings.Cut(param, "=") + if !ok || k == "" { + return optsList, fmt.Errorf("invalid publish opts format (should be name=value but got '%s')", param) } - - params[opt[0]] = opt[1] + params[k] = v } optsList = append(optsList, fmt.Sprintf("%s:%s/%s", params["published"], params["target"], params["protocol"])) } else { @@ -856,7 +929,7 @@ func convertToStandardNotation(ports []string) ([]string, error) { func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) { loggingOptsMap := opts.ConvertKVStringsToMap(loggingOpts) if loggingDriver == "none" && len(loggingOpts) > 0 { - return map[string]string{}, errors.Errorf("invalid logging opts for driver %s", loggingDriver) + return map[string]string{}, fmt.Errorf("invalid logging opts for driver %s", loggingDriver) } return loggingOptsMap, nil } @@ -864,24 +937,31 @@ func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]st // takes a local seccomp daemon, reads the file contents for sending to the daemon func parseSecurityOpts(securityOpts []string) ([]string, error) { for key, opt := range securityOpts { - con := strings.SplitN(opt, "=", 2) - if len(con) == 1 && con[0] != "no-new-privileges" { - if strings.Contains(opt, ":") { - con = strings.SplitN(opt, ":", 2) - } else { - return securityOpts, errors.Errorf("Invalid --security-opt: %q", opt) - } + k, v, ok := strings.Cut(opt, "=") + if !ok && k != "no-new-privileges" { + k, v, ok = strings.Cut(opt, ":") } - if con[0] == "seccomp" && con[1] != "unconfined" { - f, err := os.ReadFile(con[1]) - if err != nil { - return securityOpts, errors.Errorf("opening seccomp profile (%s) failed: %v", con[1], err) + if (!ok || v == "") && k != "no-new-privileges" { + // "no-new-privileges" is the only option that does not require a value. + return securityOpts, fmt.Errorf("invalid --security-opt: %q", opt) + } + if k == "seccomp" { + switch v { + case seccompProfileDefault, seccompProfileUnconfined: + // known special names for built-in profiles, nothing to do. + default: + // value may be a filename, in which case we send the profile's + // content if it's valid JSON. + f, err := os.ReadFile(v) + if err != nil { + return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %w", v, err) + } + var b bytes.Buffer + if err := json.Compact(&b, f); err != nil { + return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %w", v, err) + } + securityOpts[key] = "seccomp=" + b.String() } - b := bytes.NewBuffer(nil) - if err := json.Compact(b, f); err != nil { - return securityOpts, errors.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err) - } - securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes()) } } @@ -911,12 +991,11 @@ func parseSystemPaths(securityOpts []string) (filtered, maskedPaths, readonlyPat func parseStorageOpts(storageOpts []string) (map[string]string, error) { m := make(map[string]string) for _, option := range storageOpts { - if strings.Contains(option, "=") { - opt := strings.SplitN(option, "=", 2) - m[opt[0]] = opt[1] - } else { - return nil, errors.Errorf("invalid storage option") + k, v, ok := strings.Cut(option, "=") + if !ok { + return nil, errors.New("invalid storage option") } + m[k] = v } return m, nil } @@ -927,9 +1006,11 @@ func parseDevice(device, serverOS string) (container.DeviceMapping, error) { case "linux": return parseLinuxDevice(device) case "windows": - return parseWindowsDevice(device) + // Windows doesn't support mapping, so passing the given value as-is. + return container.DeviceMapping{PathOnHost: device}, nil + default: + return container.DeviceMapping{}, fmt.Errorf("unknown server OS: %s", serverOS) } - return container.DeviceMapping{}, errors.Errorf("unknown server OS: %s", serverOS) } // parseLinuxDevice parses a device mapping string to a container.DeviceMapping struct @@ -937,7 +1018,8 @@ func parseDevice(device, serverOS string) (container.DeviceMapping, error) { func parseLinuxDevice(device string) (container.DeviceMapping, error) { var src, dst string permissions := "rwm" - arr := strings.Split(device, ":") + // We expect 3 parts at maximum; limit to 4 parts to detect invalid options. + arr := strings.SplitN(device, ":", 4) switch len(arr) { case 3: permissions = arr[2] @@ -952,25 +1034,18 @@ func parseLinuxDevice(device string) (container.DeviceMapping, error) { case 1: src = arr[0] default: - return container.DeviceMapping{}, errors.Errorf("invalid device specification: %s", device) + return container.DeviceMapping{}, fmt.Errorf("invalid device specification: %s", device) } if dst == "" { dst = src } - deviceMapping := container.DeviceMapping{ + return container.DeviceMapping{ PathOnHost: src, PathInContainer: dst, CgroupPermissions: permissions, - } - return deviceMapping, nil -} - -// parseWindowsDevice parses a device mapping string to a container.DeviceMapping struct -// knowing that the target is a Windows daemon -func parseWindowsDevice(device string) (container.DeviceMapping, error) { - return container.DeviceMapping{PathOnHost: device}, nil + }, nil } // validateDeviceCgroupRule validates a device cgroup rule string format @@ -982,7 +1057,7 @@ func validateDeviceCgroupRule(val string) (string, error) { return val, nil } - return val, errors.Errorf("invalid device cgroup format '%s'", val) + return val, fmt.Errorf("invalid device cgroup format '%s'", val) } // validDeviceMode checks if the mode for device is valid or not. @@ -1014,7 +1089,7 @@ func validateDevice(val, serverOS string) (string, error) { // Windows does validation entirely server-side return val, nil } - return "", errors.Errorf("unknown server OS: %s", serverOS) + return "", fmt.Errorf("unknown server OS: %s", serverOS) } // validateLinuxPath is the implementation of validateDevice knowing that the @@ -1029,12 +1104,12 @@ func validateLinuxPath(val string, validator func(string) bool) (string, error) var mode string if strings.Count(val, ":") > 2 { - return val, errors.Errorf("bad format for path: %s", val) + return val, fmt.Errorf("bad format for path: %s", val) } split := strings.SplitN(val, ":", 3) if split[0] == "" { - return val, errors.Errorf("bad format for path: %s", val) + return val, fmt.Errorf("bad format for path: %s", val) } switch len(split) { case 1: @@ -1053,13 +1128,13 @@ func validateLinuxPath(val string, validator func(string) bool) (string, error) containerPath = split[1] mode = split[2] if isValid := validator(split[2]); !isValid { - return val, errors.Errorf("bad mode specified: %s", mode) + return val, fmt.Errorf("bad mode specified: %s", mode) } val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode) } if !path.IsAbs(containerPath) { - return val, errors.Errorf("%s is not an absolute path", containerPath) + return val, fmt.Errorf("%s is not an absolute path", containerPath) } return val, nil } @@ -1070,14 +1145,71 @@ func validateAttach(val string) (string, error) { if slices.Contains([]string{"stdin", "stdout", "stderr"}, s) { return s, nil } - return val, errors.Errorf("valid streams are STDIN, STDOUT and STDERR") + return val, errors.New("valid streams are STDIN, STDOUT and STDERR") } -func validateAPIVersion(c *containerConfig, serverAPIVersion string) error { - for _, m := range c.HostConfig.Mounts { - if m.BindOptions != nil && m.BindOptions.NonRecursive && versions.LessThan(serverAPIVersion, "1.40") { - return errors.Errorf("bind-nonrecursive requires API v1.40 or later") - } +func toNetipAddrSlice(ips []string) []netip.Addr { + if len(ips) == 0 { + return nil } - return nil + netIPs := make([]netip.Addr, 0, len(ips)) + for _, ip := range ips { + addr, err := netip.ParseAddr(ip) + if err != nil { + continue + } + netIPs = append(netIPs, addr) + } + return netIPs +} + +// invalidParameter wraps an error to indicate it was caused by invalid input. +// This is a local replacement for docker/docker/errdefs.InvalidParameter. +type invalidParameterError struct{ error } + +func (e invalidParameterError) InvalidParameter() {} + +func invalidParameter(err error) error { + if err == nil { + return nil + } + return invalidParameterError{err} +} + +func convertPortSet(ports nat.PortSet) (network.PortSet, error) { + converted := make(network.PortSet, len(ports)) + for port := range ports { + p, err := network.ParsePort(port.Port() + "/" + port.Proto()) + if err != nil { + return nil, err + } + converted[p] = struct{}{} + } + return converted, nil +} + +func convertPortMap(bindings nat.PortMap) (network.PortMap, error) { + converted := make(network.PortMap, len(bindings)) + for port, portBindings := range bindings { + p, err := network.ParsePort(port.Port() + "/" + port.Proto()) + if err != nil { + return nil, err + } + convertedBindings := make([]network.PortBinding, 0, len(portBindings)) + for _, binding := range portBindings { + var hostIP netip.Addr + if binding.HostIP != "" { + hostIP, err = netip.ParseAddr(binding.HostIP) + if err != nil { + return nil, err + } + } + convertedBindings = append(convertedBindings, network.PortBinding{ + HostIP: hostIP, + HostPort: binding.HostPort, + }) + } + converted[p] = convertedBindings + } + return converted, nil } diff --git a/act/container/docker_cli_test.go b/act/container/docker_cli_test.go index 30aa4d6b..cf7e33c5 100644 --- a/act/container/docker_cli_test.go +++ b/act/container/docker_cli_test.go @@ -16,15 +16,18 @@ package container import ( "fmt" "io" + "net/netip" "os" "runtime" "strings" "testing" "time" - "github.com/docker/docker/api/types/container" - networktypes "github.com/docker/docker/api/types/network" "github.com/docker/go-connections/nat" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/moby/moby/api/types/container" + networktypes "github.com/moby/moby/api/types/network" "github.com/pkg/errors" "github.com/spf13/pflag" "gotest.tools/v3/assert" @@ -77,21 +80,21 @@ func setupRunFlags() (*pflag.FlagSet, *containerOptions) { return flags, copts } -func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) { +func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig) { t.Helper() - config, hostConfig, _, err := parseRun(append(strings.Split(args, " "), "ubuntu", "bash")) + config, hostConfig, networkingConfig, err := parseRun(append(strings.Split(args, " "), "ubuntu", "bash")) assert.NilError(t, err) - return config, hostConfig + return config, hostConfig, networkingConfig } func TestParseRunLinks(t *testing.T) { - if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" { + if _, hostConfig, _ := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" { t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links) } - if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" { + if _, hostConfig, _ := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" { t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links) } - if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 { + if _, hostConfig, _ := mustParse(t, ""); len(hostConfig.Links) != 0 { t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links) } } @@ -140,7 +143,7 @@ func TestParseRunAttach(t *testing.T) { } for _, tc := range tests { t.Run(tc.input, func(t *testing.T) { - config, _ := mustParse(t, tc.input) + config, _, _ := mustParse(t, tc.input) assert.Equal(t, config.AttachStdin, tc.expected.AttachStdin) assert.Equal(t, config.AttachStdout, tc.expected.AttachStdout) assert.Equal(t, config.AttachStderr, tc.expected.AttachStderr) @@ -194,10 +197,10 @@ func TestParseRunWithInvalidArgs(t *testing.T) { } } -func TestParseWithVolumes(t *testing.T) { +func TestParseWithVolumes(t *testing.T) { //nolint:gocyclo // verbatim copy from docker/cli tests // A single volume arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`}) - if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil { + if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds != nil { t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds) } else if _, exists := config.Volumes[arr[0]]; !exists { t.Fatalf("Error parsing volume flags, %q is missing from volumes. Received %v", tryit, config.Volumes) @@ -205,7 +208,7 @@ func TestParseWithVolumes(t *testing.T) { // Two volumes arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`}) - if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil { + if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds != nil { t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds) } else if _, exists := config.Volumes[arr[0]]; !exists { t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes) @@ -215,13 +218,13 @@ func TestParseWithVolumes(t *testing.T) { // A single bind mount arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`}) - if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] { + if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] { t.Fatalf("Error parsing volume flags, %q should mount-bind the path before the colon into the path after the colon. Received %v %v", arr[0], hostConfig.Binds, config.Volumes) } // Two bind mounts. arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`}) - if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { + if _, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds) } @@ -230,26 +233,26 @@ func TestParseWithVolumes(t *testing.T) { arr, tryit = setupPlatformVolume( []string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`}, []string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`}) - if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { + if _, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds) } // Similar to previous test but with alternate modes which are only supported by Linux if runtime.GOOS != "windows" { arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{}) - if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { + if _, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds) } arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{}) - if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { + if _, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds) } } // One bind mount and one volume arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`}) - if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] { + if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] { t.Fatalf("Error parsing volume flags, %s and %s should only one and only one bind mount %s. Received %s", arr[0], arr[1], arr[0], hostConfig.Binds) } else if _, exists := config.Volumes[arr[1]]; !exists { t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes) @@ -258,7 +261,7 @@ func TestParseWithVolumes(t *testing.T) { // Root to non-c: drive letter (Windows specific) if runtime.GOOS == "windows" { arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`}) - if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 { + if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 { t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0]) } } @@ -294,6 +297,36 @@ func compareRandomizedStrings(a, b, c, d string) error { return errors.Errorf("strings don't match") } +func mustNetworkPort(t *testing.T, value string) networktypes.Port { + t.Helper() + + port, err := networktypes.ParsePort(value) + if err != nil { + t.Fatalf("failed to parse network port %q: %v", value, err) + } + return port +} + +func mustAddr(t *testing.T, value string) netip.Addr { + t.Helper() + + addr, err := netip.ParseAddr(value) + if err != nil { + t.Fatalf("failed to parse address %q: %v", value, err) + } + return addr +} + +func mustAddrs(t *testing.T, values ...string) []netip.Addr { + t.Helper() + + addrs := make([]netip.Addr, 0, len(values)) + for _, value := range values { + addrs = append(addrs, mustAddr(t, value)) + } + return addrs +} + // Simple parse with MacAddress validation func TestParseWithMacAddress(t *testing.T) { invalidMacAddress := "--mac-address=invalidMacAddress" @@ -301,9 +334,10 @@ func TestParseWithMacAddress(t *testing.T) { if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" { t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err) } - if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" { //nolint:staticcheck // pre-existing issue from nektos/act - t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress) //nolint:staticcheck // pre-existing issue from nektos/act - } + _, hostConfig, networkingConfig := mustParse(t, validMacAddress) + endpoint := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] + assert.Check(t, endpoint != nil) + assert.Equal(t, "92:d0:c6:0a:29:33", endpoint.MacAddress.String()) } func TestRunFlagsParseWithMemory(t *testing.T) { @@ -312,7 +346,7 @@ func TestRunFlagsParseWithMemory(t *testing.T) { err := flags.Parse(args) assert.ErrorContains(t, err, `invalid argument "invalid" for "-m, --memory" flag`) - _, hostconfig := mustParse(t, "--memory=1G") + _, hostconfig, _ := mustParse(t, "--memory=1G") assert.Check(t, is.Equal(int64(1073741824), hostconfig.Memory)) } @@ -322,10 +356,10 @@ func TestParseWithMemorySwap(t *testing.T) { err := flags.Parse(args) assert.ErrorContains(t, err, `invalid argument "invalid" for "--memory-swap" flag`) - _, hostconfig := mustParse(t, "--memory-swap=1G") + _, hostconfig, _ := mustParse(t, "--memory-swap=1G") assert.Check(t, is.Equal(int64(1073741824), hostconfig.MemorySwap)) - _, hostconfig = mustParse(t, "--memory-swap=-1") + _, hostconfig, _ = mustParse(t, "--memory-swap=-1") assert.Check(t, is.Equal(int64(-1), hostconfig.MemorySwap)) } @@ -340,14 +374,14 @@ func TestParseHostname(t *testing.T) { hostnameWithDomain := "--hostname=hostname.domainname" hostnameWithDomainTld := "--hostname=hostname.domainname.tld" for hostname, expectedHostname := range validHostnames { - if config, _ := mustParse(t, "--hostname="+hostname); config.Hostname != expectedHostname { + if config, _, _ := mustParse(t, "--hostname="+hostname); config.Hostname != expectedHostname { t.Fatalf("Expected the config to have 'hostname' as %q, got %q", expectedHostname, config.Hostname) } } - if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname.domainname" || config.Domainname != "" { + if config, _, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname.domainname" || config.Domainname != "" { t.Fatalf("Expected the config to have 'hostname' as hostname.domainname, got %q", config.Hostname) } - if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname.domainname.tld" || config.Domainname != "" { + if config, _, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname.domainname.tld" || config.Domainname != "" { t.Fatalf("Expected the config to have 'hostname' as hostname.domainname.tld, got %q", config.Hostname) } } @@ -361,26 +395,28 @@ func TestParseHostnameDomainname(t *testing.T) { "domainname-63-bytes-long-should-be-valid-and-without-any-errors": "domainname-63-bytes-long-should-be-valid-and-without-any-errors", } for domainname, expectedDomainname := range validDomainnames { - if config, _ := mustParse(t, "--domainname="+domainname); config.Domainname != expectedDomainname { + if config, _, _ := mustParse(t, "--domainname="+domainname); config.Domainname != expectedDomainname { t.Fatalf("Expected the config to have 'domainname' as %q, got %q", expectedDomainname, config.Domainname) } } - if config, _ := mustParse(t, "--hostname=some.prefix --domainname=domainname"); config.Hostname != "some.prefix" || config.Domainname != "domainname" { + if config, _, _ := mustParse(t, "--hostname=some.prefix --domainname=domainname"); config.Hostname != "some.prefix" || config.Domainname != "domainname" { t.Fatalf("Expected the config to have 'hostname' as 'some.prefix' and 'domainname' as 'domainname', got %q and %q", config.Hostname, config.Domainname) } - if config, _ := mustParse(t, "--hostname=another-prefix --domainname=domainname.tld"); config.Hostname != "another-prefix" || config.Domainname != "domainname.tld" { + if config, _, _ := mustParse(t, "--hostname=another-prefix --domainname=domainname.tld"); config.Hostname != "another-prefix" || config.Domainname != "domainname.tld" { t.Fatalf("Expected the config to have 'hostname' as 'another-prefix' and 'domainname' as 'domainname.tld', got %q and %q", config.Hostname, config.Domainname) } } func TestParseWithExpose(t *testing.T) { - invalids := map[string]string{ - ":": "invalid port format for --expose: :", - "8080:9090": "invalid port format for --expose: 8080:9090", - "NaN/tcp": `invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, - "NaN-NaN/tcp": `invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, - "8080-NaN/tcp": `invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`, - "1234567890-8080/tcp": `invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`, + invalids := []string{ + ":", + "8080:9090", + "/tcp", + "/udp", + "NaN/tcp", + "NaN-NaN/tcp", + "8080-NaN/tcp", + "1234567890-8080/tcp", } valids := map[string][]nat.Port{ "8080/tcp": {"8080/tcp"}, @@ -389,9 +425,9 @@ func TestParseWithExpose(t *testing.T) { "8080-8080/udp": {"8080/udp"}, "8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"}, } - for expose, expectedError := range invalids { - if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError { - t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err) + for _, expose := range invalids { + if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil { + t.Fatalf("Expected error with '--expose=%v', got none", expose) } } for expose, exposedPorts := range valids { @@ -403,7 +439,7 @@ func TestParseWithExpose(t *testing.T) { t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts)) } for _, port := range exposedPorts { - if _, ok := config.ExposedPorts[port]; !ok { + if _, ok := config.ExposedPorts[mustNetworkPort(t, string(port))]; !ok { t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts) } } @@ -418,7 +454,7 @@ func TestParseWithExpose(t *testing.T) { } ports := []nat.Port{"80/tcp", "81/tcp"} for _, port := range ports { - if _, ok := config.ExposedPorts[port]; !ok { + if _, ok := config.ExposedPorts[mustNetworkPort(t, string(port))]; !ok { t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts) } } @@ -498,9 +534,9 @@ func TestParseNetworkConfig(t *testing.T) { expected: map[string]*networktypes.EndpointSettings{ "net1": { IPAMConfig: &networktypes.EndpointIPAMConfig{ - IPv4Address: "172.20.88.22", - IPv6Address: "2001:db8::8822", - LinkLocalIPs: []string{"169.254.2.2", "fe80::169:254:2:2"}, + IPv4Address: mustAddr(t, "172.20.88.22"), + IPv6Address: mustAddr(t, "2001:db8::8822"), + LinkLocalIPs: mustAddrs(t, "169.254.2.2", "fe80::169:254:2:2"), }, Links: []string{"foo:bar", "bar:baz"}, Aliases: []string{"web1", "web2"}, @@ -527,9 +563,9 @@ func TestParseNetworkConfig(t *testing.T) { "net1": { DriverOpts: map[string]string{"field1": "value1"}, IPAMConfig: &networktypes.EndpointIPAMConfig{ - IPv4Address: "172.20.88.22", - IPv6Address: "2001:db8::8822", - LinkLocalIPs: []string{"169.254.2.2", "fe80::169:254:2:2"}, + IPv4Address: mustAddr(t, "172.20.88.22"), + IPv6Address: mustAddr(t, "2001:db8::8822"), + LinkLocalIPs: mustAddrs(t, "169.254.2.2", "fe80::169:254:2:2"), }, Links: []string{"foo:bar", "bar:baz"}, Aliases: []string{"web1", "web2"}, @@ -538,8 +574,8 @@ func TestParseNetworkConfig(t *testing.T) { "net3": { DriverOpts: map[string]string{"field3": "value3"}, IPAMConfig: &networktypes.EndpointIPAMConfig{ - IPv4Address: "172.20.88.22", - IPv6Address: "2001:db8::8822", + IPv4Address: mustAddr(t, "172.20.88.22"), + IPv6Address: mustAddr(t, "2001:db8::8822"), }, Aliases: []string{"web3"}, }, @@ -556,8 +592,8 @@ func TestParseNetworkConfig(t *testing.T) { "field2": "value2", }, IPAMConfig: &networktypes.EndpointIPAMConfig{ - IPv4Address: "172.20.88.22", - IPv6Address: "2001:db8::8822", + IPv4Address: mustAddr(t, "172.20.88.22"), + IPv6Address: mustAddr(t, "2001:db8::8822"), }, Aliases: []string{"web1", "web2"}, }, @@ -610,7 +646,9 @@ func TestParseNetworkConfig(t *testing.T) { assert.NilError(t, err) assert.DeepEqual(t, hConfig.NetworkMode, tc.expectedCfg.NetworkMode) - assert.DeepEqual(t, nwConfig.EndpointsConfig, tc.expected) + if diff := cmp.Diff(tc.expected, nwConfig.EndpointsConfig, cmpopts.EquateComparable(netip.Addr{})); diff != "" { + t.Fatalf("unexpected endpoints (-want +got):\n%s", diff) + } }) } } @@ -631,7 +669,7 @@ func TestParseModes(t *testing.T) { } // uts ko - _, _, _, err = parseRun([]string{"--uts=container:", "img", "cmd"}) + _, _, _, err = parseRun([]string{"--uts=container:", "img", "cmd"}) //nolint:dogsled // verbatim copy from docker/cli tests assert.ErrorContains(t, err, "--uts: invalid UTS mode") // uts ok @@ -691,10 +729,9 @@ func TestParseRestartPolicy(t *testing.T) { } func TestParseRestartPolicyAutoRemove(t *testing.T) { - expected := "Conflicting options: --restart and --rm" - _, _, _, err := parseRun([]string{"--rm", "--restart=always", "img", "cmd"}) - if err == nil || err.Error() != expected { - t.Fatalf("Expected error %v, but got none", expected) + _, _, _, err := parseRun([]string{"--rm", "--restart=always", "img", "cmd"}) //nolint:dogsled // verbatim copy from docker/cli tests + if err == nil { + t.Fatal("Expected error for conflicting --restart and --rm, but got none") } } @@ -752,7 +789,7 @@ func TestParseLoggingOpts(t *testing.T) { } } -func TestParseEnvfileVariables(t *testing.T) { //nolint:dupl // pre-existing issue from nektos/act +func TestParseEnvfileVariables(t *testing.T) { //nolint:dupl // verbatim copy from docker/cli tests e := "open nonexistent: no such file or directory" if runtime.GOOS == "windows" { e = "open nonexistent: The system cannot find the file specified." @@ -795,7 +832,7 @@ func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) { } // UTF16 with BOM - e := "contains invalid utf8 bytes at line" + e := "invalid env file" if _, _, _, err := parseRun([]string{"--env-file=testdata/utf16.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) { t.Fatalf("Expected an error with message '%s', got %v", e, err) } @@ -805,7 +842,7 @@ func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) { } } -func TestParseLabelfileVariables(t *testing.T) { //nolint:dupl // pre-existing issue from nektos/act +func TestParseLabelfileVariables(t *testing.T) { //nolint:dupl // verbatim copy from docker/cli tests e := "open nonexistent: no such file or directory" if runtime.GOOS == "windows" { e = "open nonexistent: The system cannot find the file specified." diff --git a/act/container/docker_images.go b/act/container/docker_images.go index 636ae311..bcd46e64 100644 --- a/act/container/docker_images.go +++ b/act/container/docker_images.go @@ -10,8 +10,8 @@ import ( "context" "fmt" - "github.com/docker/docker/api/types" - "github.com/docker/docker/client" + cerrdefs "github.com/containerd/errdefs" + "github.com/moby/moby/client" ) // ImageExistsLocally returns a boolean indicating if an image with the @@ -23,8 +23,8 @@ func ImageExistsLocally(ctx context.Context, imageName, platform string) (bool, } defer cli.Close() - inspectImage, _, err := cli.ImageInspectWithRaw(ctx, imageName) - if client.IsErrNotFound(err) { + inspectImage, err := cli.ImageInspect(ctx, imageName) + if cerrdefs.IsNotFound(err) { return false, nil } else if err != nil { return false, err @@ -46,14 +46,14 @@ func RemoveImage(ctx context.Context, imageName string, force, pruneChildren boo } defer cli.Close() - inspectImage, _, err := cli.ImageInspectWithRaw(ctx, imageName) - if client.IsErrNotFound(err) { + inspectImage, err := cli.ImageInspect(ctx, imageName) + if cerrdefs.IsNotFound(err) { return false, nil } else if err != nil { return false, err } - if _, err = cli.ImageRemove(ctx, inspectImage.ID, types.ImageRemoveOptions{ + if _, err = cli.ImageRemove(ctx, inspectImage.ID, client.ImageRemoveOptions{ Force: force, PruneChildren: pruneChildren, }); err != nil { diff --git a/act/container/docker_images_test.go b/act/container/docker_images_test.go index b2d54842..d206bf5e 100644 --- a/act/container/docker_images_test.go +++ b/act/container/docker_images_test.go @@ -9,8 +9,8 @@ import ( "io" "testing" - "github.com/docker/docker/api/types" - "github.com/docker/docker/client" + "github.com/moby/moby/client" + specs "github.com/opencontainers/image-spec/specs-go/v1" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) @@ -38,14 +38,14 @@ func TestImageExistsLocally(t *testing.T) { assert.False(t, invalidImagePlatform) // pull an image - cli, err := client.NewClientWithOpts(client.FromEnv) + cli, err := client.New(client.FromEnv) assert.NoError(t, err) //nolint:testifylint // pre-existing issue from nektos/act - cli.NegotiateAPIVersion(context.Background()) + defer cli.Close() // Chose alpine latest because it's so small // maybe we should build an image instead so that tests aren't reliable on dockerhub - readerDefault, err := cli.ImagePull(ctx, "node:24-bookworm-slim", types.ImagePullOptions{ - Platform: "linux/amd64", + readerDefault, err := cli.ImagePull(ctx, "node:24-bookworm-slim", client.ImagePullOptions{ + Platforms: []specs.Platform{{OS: "linux", Architecture: "amd64"}}, }) assert.NoError(t, err) //nolint:testifylint // pre-existing issue from nektos/act defer readerDefault.Close() @@ -57,8 +57,8 @@ func TestImageExistsLocally(t *testing.T) { assert.True(t, imageDefaultArchExists) // Validate if another architecture platform can be pulled - readerArm64, err := cli.ImagePull(ctx, "node:24-bookworm-slim", types.ImagePullOptions{ - Platform: "linux/arm64", + readerArm64, err := cli.ImagePull(ctx, "node:24-bookworm-slim", client.ImagePullOptions{ + Platforms: []specs.Platform{{OS: "linux", Architecture: "arm64"}}, }) assert.NoError(t, err) //nolint:testifylint // pre-existing issue from nektos/act defer readerArm64.Close() diff --git a/act/container/docker_network.go b/act/container/docker_network.go index accd34db..d0fd4a5a 100644 --- a/act/container/docker_network.go +++ b/act/container/docker_network.go @@ -11,7 +11,7 @@ import ( "gitea.com/gitea/runner/act/common" - "github.com/docker/docker/api/types" + "github.com/moby/moby/client" ) func NewDockerNetworkCreateExecutor(name string) common.Executor { @@ -23,20 +23,20 @@ func NewDockerNetworkCreateExecutor(name string) common.Executor { defer cli.Close() // Only create the network if it doesn't exist - networks, err := cli.NetworkList(ctx, types.NetworkListOptions{}) + networks, err := cli.NetworkList(ctx, client.NetworkListOptions{}) if err != nil { return err } // For Gitea, reduce log noise // common.Logger(ctx).Debugf("%v", networks) - for _, network := range networks { - if network.Name == name { + for _, n := range networks.Items { + if n.Name == name { common.Logger(ctx).Debugf("Network %v exists", name) return nil } } - _, err = cli.NetworkCreate(ctx, name, types.NetworkCreate{ + _, err = cli.NetworkCreate(ctx, name, client.NetworkCreateOptions{ Driver: "bridge", Scope: "local", }) @@ -56,23 +56,23 @@ func NewDockerNetworkRemoveExecutor(name string) common.Executor { } defer cli.Close() - // Make shure that all network of the specified name are removed + // Make sure that all network of the specified name are removed // cli.NetworkRemove refuses to remove a network if there are duplicates - networks, err := cli.NetworkList(ctx, types.NetworkListOptions{}) + networks, err := cli.NetworkList(ctx, client.NetworkListOptions{}) if err != nil { return err } // For Gitea, reduce log noise // common.Logger(ctx).Debugf("%v", networks) - for _, network := range networks { - if network.Name == name { - result, err := cli.NetworkInspect(ctx, network.ID, types.NetworkInspectOptions{}) + for _, n := range networks.Items { + if n.Name == name { + result, err := cli.NetworkInspect(ctx, n.ID, client.NetworkInspectOptions{}) if err != nil { return err } - if len(result.Containers) == 0 { - if err = cli.NetworkRemove(ctx, network.ID); err != nil { + if len(result.Network.Containers) == 0 { + if _, err = cli.NetworkRemove(ctx, n.ID, client.NetworkRemoveOptions{}); err != nil { common.Logger(ctx).Debugf("%v", err) } } else { diff --git a/act/container/docker_platform.go b/act/container/docker_platform.go new file mode 100644 index 00000000..198445eb --- /dev/null +++ b/act/container/docker_platform.go @@ -0,0 +1,39 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Copyright 2025 The nektos/act Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +//go:build !(WITHOUT_DOCKER || !(linux || darwin || windows || netbsd)) + +package container + +import ( + "fmt" + "strings" + + specs "github.com/opencontainers/image-spec/specs-go/v1" +) + +// parsePlatform parses an "os/arch[/variant]" string into a Platform. An empty input +// returns (nil, nil), meaning "no platform constraint". A non-empty but malformed +// string is rejected explicitly so it cannot silently fall through to the daemon's +// default architecture. +func parsePlatform(platform string) (*specs.Platform, error) { + if platform == "" { + return nil, nil //nolint:nilnil // no platform constraint requested + } + + parts := strings.Split(platform, "/") + if len(parts) < 2 || len(parts) > 3 || parts[0] == "" || parts[1] == "" || (len(parts) == 3 && parts[2] == "") { + return nil, fmt.Errorf("invalid platform %q: expected os/arch[/variant]", platform) + } + + spec := &specs.Platform{ + OS: strings.ToLower(parts[0]), + Architecture: strings.ToLower(parts[1]), + } + if len(parts) == 3 { + spec.Variant = strings.ToLower(parts[2]) + } + + return spec, nil +} diff --git a/act/container/docker_platform_test.go b/act/container/docker_platform_test.go new file mode 100644 index 00000000..46005a5c --- /dev/null +++ b/act/container/docker_platform_test.go @@ -0,0 +1,63 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package container + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParsePlatform(t *testing.T) { + t.Run("empty input returns nil platform without error", func(t *testing.T) { + got, err := parsePlatform("") + require.NoError(t, err) + assert.Nil(t, got) + }) + + t.Run("os/arch", func(t *testing.T) { + got, err := parsePlatform("linux/amd64") + require.NoError(t, err) + require.NotNil(t, got) + assert.Equal(t, "linux", got.OS) + assert.Equal(t, "amd64", got.Architecture) + assert.Empty(t, got.Variant) + }) + + t.Run("os/arch/variant", func(t *testing.T) { + got, err := parsePlatform("linux/arm/v7") + require.NoError(t, err) + require.NotNil(t, got) + assert.Equal(t, "linux", got.OS) + assert.Equal(t, "arm", got.Architecture) + assert.Equal(t, "v7", got.Variant) + }) + + t.Run("input is lowercased", func(t *testing.T) { + got, err := parsePlatform("Linux/AMD64/V8") + require.NoError(t, err) + require.NotNil(t, got) + assert.Equal(t, "linux", got.OS) + assert.Equal(t, "amd64", got.Architecture) + assert.Equal(t, "v8", got.Variant) + }) + + for _, bad := range []string{ + "amd64", + "linux", + "linux/", + "/amd64", + "/", + "//", + "linux/arm/", + "linux/arm/v7/extra", + } { + t.Run("rejects "+bad, func(t *testing.T) { + got, err := parsePlatform(bad) + require.Error(t, err) + assert.Nil(t, got) + }) + } +} diff --git a/act/container/docker_pull.go b/act/container/docker_pull.go index eaf98a78..dc0f28f4 100644 --- a/act/container/docker_pull.go +++ b/act/container/docker_pull.go @@ -8,16 +8,16 @@ package container import ( "context" - "encoding/base64" - "encoding/json" "fmt" "strings" "gitea.com/gitea/runner/act/common" "github.com/distribution/reference" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/registry" + "github.com/moby/moby/api/pkg/authconfig" + "github.com/moby/moby/api/types/registry" + "github.com/moby/moby/client" + specs "github.com/opencontainers/image-spec/specs-go/v1" ) // NewDockerPullExecutor function to create a run executor for the container @@ -78,26 +78,29 @@ func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor { } } -func getImagePullOptions(ctx context.Context, input NewDockerPullExecutorInput) (types.ImagePullOptions, error) { - imagePullOptions := types.ImagePullOptions{ - Platform: input.Platform, +func getImagePullOptions(ctx context.Context, input NewDockerPullExecutorInput) (client.ImagePullOptions, error) { + imagePullOptions := client.ImagePullOptions{} + platform, err := parsePlatform(input.Platform) + if err != nil { + return imagePullOptions, err + } + if platform != nil { + imagePullOptions.Platforms = []specs.Platform{*platform} } logger := common.Logger(ctx) if input.Username != "" && input.Password != "" { logger.Debugf("using authentication for docker pull") - authConfig := registry.AuthConfig{ + encodedAuth, err := authconfig.Encode(registry.AuthConfig{ Username: input.Username, Password: input.Password, - } - - encodedJSON, err := json.Marshal(authConfig) + }) if err != nil { return imagePullOptions, err } - imagePullOptions.RegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON) + imagePullOptions.RegistryAuth = encodedAuth } else { authConfig, err := LoadDockerAuthConfig(ctx, input.Image) if err != nil { @@ -108,19 +111,17 @@ func getImagePullOptions(ctx context.Context, input NewDockerPullExecutorInput) } logger.Info("using DockerAuthConfig authentication for docker pull") - encodedJSON, err := json.Marshal(authConfig) + imagePullOptions.RegistryAuth, err = authconfig.Encode(authConfig) if err != nil { return imagePullOptions, err } - - imagePullOptions.RegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON) } return imagePullOptions, nil } -func cleanImage(ctx context.Context, image string) string { - ref, err := reference.ParseAnyReference(image) +func cleanImage(ctx context.Context, imageName string) string { + ref, err := reference.ParseAnyReference(imageName) if err != nil { common.Logger(ctx).Error(err) return "" diff --git a/act/container/docker_run.go b/act/container/docker_run.go index 077ae7b1..1959ab24 100644 --- a/act/container/docker_run.go +++ b/act/container/docker_run.go @@ -27,18 +27,18 @@ import ( "github.com/Masterminds/semver" "github.com/docker/cli/cli/compose/loader" "github.com/docker/cli/cli/connhelper" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/mount" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/client" - "github.com/docker/docker/pkg/stdcopy" "github.com/go-git/go-billy/v5/helper/polyfill" "github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-git/v5/plumbing/format/gitignore" "github.com/gobwas/glob" "github.com/joho/godotenv" "github.com/kballard/go-shellquote" + "github.com/moby/moby/api/pkg/stdcopy" + "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/mount" + "github.com/moby/moby/api/types/network" + "github.com/moby/moby/api/types/system" + "github.com/moby/moby/client" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/pflag" "golang.org/x/term" @@ -64,9 +64,13 @@ func (cr *containerReference) ConnectToNetwork(name string) common.Executor { func (cr *containerReference) connectToNetwork(name string, aliases []string) common.Executor { return func(ctx context.Context) error { - return cr.cli.NetworkConnect(ctx, name, cr.input.Name, &network.EndpointSettings{ - Aliases: aliases, + _, err := cr.cli.NetworkConnect(ctx, name, client.NetworkConnectOptions{ + Container: cr.input.Name, + EndpointConfig: &network.EndpointSettings{ + Aliases: aliases, + }, }) + return err } } @@ -74,7 +78,7 @@ func (cr *containerReference) connectToNetwork(name string, aliases []string) co // API version is 1.41 and beyond func supportsContainerImagePlatform(ctx context.Context, cli client.APIClient) bool { logger := common.Logger(ctx) - ver, err := cli.ServerVersion(ctx) + ver, err := cli.ServerVersion(ctx, client.ServerVersionOptions{}) if err != nil { logger.Panicf("Failed to get Docker API Version: %s", err) return false @@ -163,8 +167,11 @@ func (cr *containerReference) GetContainerArchive(ctx context.Context, srcPath s if common.Dryrun(ctx) { return nil, errors.New("DRYRUN is not supported in GetContainerArchive") } - a, _, err := cr.cli.CopyFromContainer(ctx, cr.id, srcPath) - return a, err + result, err := cr.cli.CopyFromContainer(ctx, cr.id, client.CopyFromContainerOptions{SourcePath: srcPath}) + if err != nil { + return nil, err + } + return result.Content, nil } func (cr *containerReference) UpdateFromEnv(srcPath string, env *map[string]string) common.Executor { @@ -222,22 +229,27 @@ func GetDockerClient(ctx context.Context) (cli client.APIClient, err error) { if err != nil { return nil, err } - cli, err = client.NewClientWithOpts( + cli, err = client.New( client.WithHost(helper.Host), client.WithDialContext(helper.Dialer), ) } else { - cli, err = client.NewClientWithOpts(client.FromEnv) + cli, err = client.New(client.FromEnv) } if err != nil { return nil, fmt.Errorf("failed to connect to docker daemon: %w", err) } - cli.NegotiateAPIVersion(ctx) + // Best-effort API version negotiation, matching the old client.NegotiateAPIVersion + // behaviour. Ping failures here are non-fatal: the connection is exercised again on + // the first real call, and the client falls back to the daemon's default API version. + if _, err := cli.Ping(ctx, client.PingOptions{NegotiateAPIVersion: true}); err != nil { + common.Logger(ctx).Warnf("docker daemon ping during version negotiation failed, continuing: %v", err) + } return cli, nil } -func GetHostInfo(ctx context.Context) (info types.Info, err error) { //nolint:staticcheck // pre-existing issue from nektos/act +func GetHostInfo(ctx context.Context) (info system.Info, err error) { var cli client.APIClient cli, err = GetDockerClient(ctx) if err != nil { @@ -245,12 +257,12 @@ func GetHostInfo(ctx context.Context) (info types.Info, err error) { //nolint:st } defer cli.Close() - info, err = cli.Info(ctx) + result, err := cli.Info(ctx, client.InfoOptions{}) if err != nil { return info, err } - return info, nil + return result.Info, nil } // Arch fetches values from docker info and translates architecture to @@ -307,14 +319,14 @@ func (cr *containerReference) find() common.Executor { if cr.id != "" { return nil } - containers, err := cr.cli.ContainerList(ctx, types.ContainerListOptions{ //nolint:staticcheck // pre-existing issue from nektos/act + containers, err := cr.cli.ContainerList(ctx, client.ContainerListOptions{ All: true, }) if err != nil { return fmt.Errorf("failed to list containers: %w", err) } - for _, c := range containers { + for _, c := range containers.Items { for _, name := range c.Names { if name[1:] == cr.input.Name { cr.id = c.ID @@ -335,7 +347,7 @@ func (cr *containerReference) remove() common.Executor { } logger := common.Logger(ctx) - err := cr.cli.ContainerRemove(ctx, cr.id, types.ContainerRemoveOptions{ //nolint:staticcheck // pre-existing issue from nektos/act + _, err := cr.cli.ContainerRemove(ctx, cr.id, client.ContainerRemoveOptions{ RemoveVolumes: true, Force: true, }) @@ -440,12 +452,20 @@ func (cr *containerReference) create(capAdd, capDrop []string) common.Executor { logger := common.Logger(ctx) isTerminal := term.IsTerminal(int(os.Stdout.Fd())) input := cr.input + exposedPorts, err := convertPortSet(input.ExposedPorts) + if err != nil { + return err + } + portBindings, err := convertPortMap(input.PortBindings) + if err != nil { + return err + } config := &container.Config{ Image: input.Image, WorkingDir: input.WorkingDir, Env: input.Env, - ExposedPorts: input.ExposedPorts, + ExposedPorts: exposedPorts, Tty: isTerminal, } // For Gitea, reduce log noise @@ -470,15 +490,9 @@ func (cr *containerReference) create(capAdd, capDrop []string) common.Executor { var platSpecs *specs.Platform if supportsContainerImagePlatform(ctx, cr.cli) && cr.input.Platform != "" { - desiredPlatform := strings.SplitN(cr.input.Platform, `/`, 2) - - if len(desiredPlatform) != 2 { - return fmt.Errorf("incorrect container platform option '%s'", cr.input.Platform) - } - - platSpecs = &specs.Platform{ - Architecture: desiredPlatform[1], - OS: desiredPlatform[0], + platSpecs, err = parsePlatform(cr.input.Platform) + if err != nil { + return err } } @@ -490,13 +504,13 @@ func (cr *containerReference) create(capAdd, capDrop []string) common.Executor { NetworkMode: container.NetworkMode(input.NetworkMode), Privileged: input.Privileged, UsernsMode: container.UsernsMode(input.UsernsMode), - PortBindings: input.PortBindings, + PortBindings: portBindings, AutoRemove: input.AutoRemove, } // For Gitea, reduce log noise // logger.Debugf("Common container.HostConfig ==> %+v", hostConfig) - config, hostConfig, err := cr.mergeContainerConfigs(ctx, config, hostConfig) + config, hostConfig, err = cr.mergeContainerConfigs(ctx, config, hostConfig) if err != nil { return err } @@ -520,7 +534,13 @@ func (cr *containerReference) create(capAdd, capDrop []string) common.Executor { } } - resp, err := cr.cli.ContainerCreate(ctx, config, hostConfig, networkingConfig, platSpecs, input.Name) + resp, err := cr.cli.ContainerCreate(ctx, client.ContainerCreateOptions{ + Config: config, + HostConfig: hostConfig, + NetworkingConfig: networkingConfig, + Platform: platSpecs, + Name: input.Name, + }) if err != nil { return fmt.Errorf("failed to create container: '%w'", err) } @@ -538,7 +558,7 @@ func (cr *containerReference) extractFromImageEnv(env *map[string]string) common return func(ctx context.Context) error { logger := common.Logger(ctx) - inspect, _, err := cr.cli.ImageInspectWithRaw(ctx, cr.input.Image) + inspect, err := cr.cli.ImageInspect(ctx, cr.input.Image) if err != nil { logger.Error(err) return fmt.Errorf("inspect image: %w", err) @@ -602,12 +622,12 @@ func (cr *containerReference) exec(cmd []string, env map[string]string, user, wo } logger.Debugf("Working directory '%s'", wd) - idResp, err := cr.cli.ContainerExecCreate(ctx, cr.id, types.ExecConfig{ + idResp, err := cr.cli.ExecCreate(ctx, cr.id, client.ExecCreateOptions{ User: user, Cmd: cmd, WorkingDir: wd, Env: envList, - Tty: isTerminal, + TTY: isTerminal, AttachStderr: true, AttachStdout: true, }) @@ -615,20 +635,20 @@ func (cr *containerReference) exec(cmd []string, env map[string]string, user, wo return fmt.Errorf("failed to create exec: %w", err) } - resp, err := cr.cli.ContainerExecAttach(ctx, idResp.ID, types.ExecStartCheck{ - Tty: isTerminal, + resp, err := cr.cli.ExecAttach(ctx, idResp.ID, client.ExecAttachOptions{ + TTY: isTerminal, }) if err != nil { return fmt.Errorf("failed to attach to exec: %w", err) } defer resp.Close() - err = cr.waitForCommand(ctx, isTerminal, resp, idResp, user, workdir) + err = cr.waitForCommand(ctx, isTerminal, resp.HijackedResponse, idResp, user, workdir) if err != nil { return err } - inspectResp, err := cr.cli.ContainerExecInspect(ctx, idResp.ID) + inspectResp, err := cr.cli.ExecInspect(ctx, idResp.ID, client.ExecInspectOptions{}) if err != nil { return fmt.Errorf("failed to inspect exec: %w", err) } @@ -642,7 +662,7 @@ func (cr *containerReference) exec(cmd []string, env map[string]string, user, wo func (cr *containerReference) tryReadID(opt string, cbk func(id int)) common.Executor { return func(ctx context.Context) error { - idResp, err := cr.cli.ContainerExecCreate(ctx, cr.id, types.ExecConfig{ + idResp, err := cr.cli.ExecCreate(ctx, cr.id, client.ExecCreateOptions{ Cmd: []string{"id", opt}, AttachStdout: true, AttachStderr: true, @@ -651,7 +671,7 @@ func (cr *containerReference) tryReadID(opt string, cbk func(id int)) common.Exe return nil } - resp, err := cr.cli.ContainerExecAttach(ctx, idResp.ID, types.ExecStartCheck{}) + resp, err := cr.cli.ExecAttach(ctx, idResp.ID, client.ExecAttachOptions{}) if err != nil { return nil } @@ -681,7 +701,7 @@ func (cr *containerReference) tryReadGID() common.Executor { return cr.tryReadID("-g", func(id int) { cr.GID = id }) } -func (cr *containerReference) waitForCommand(ctx context.Context, isTerminal bool, resp types.HijackedResponse, _ types.IDResponse, _, _ string) error { +func (cr *containerReference) waitForCommand(ctx context.Context, isTerminal bool, resp client.HijackedResponse, _ client.ExecCreateResult, _, _ string) error { logger := common.Logger(ctx) cmdResponse := make(chan error) @@ -736,12 +756,18 @@ func (cr *containerReference) CopyTarStream(ctx context.Context, destPath string Typeflag: tar.TypeDir, }) tw.Close() - err := cr.cli.CopyToContainer(ctx, cr.id, "/", buf, types.CopyToContainerOptions{}) + _, err := cr.cli.CopyToContainer(ctx, cr.id, client.CopyToContainerOptions{ + DestinationPath: "/", + Content: buf, + }) if err != nil { return fmt.Errorf("failed to mkdir to copy content to container: %w", err) } // Copy Content - err = cr.cli.CopyToContainer(ctx, cr.id, destPath, tarStream, types.CopyToContainerOptions{}) + _, err = cr.cli.CopyToContainer(ctx, cr.id, client.CopyToContainerOptions{ + DestinationPath: destPath, + Content: tarStream, + }) if err != nil { return fmt.Errorf("failed to copy content to container: %w", err) } @@ -815,7 +841,10 @@ func (cr *containerReference) copyDir(dstPath, srcPath string, useGitIgnore bool if err != nil { return fmt.Errorf("failed to seek tar archive: %w", err) } - err = cr.cli.CopyToContainer(ctx, cr.id, "/", tarFile, types.CopyToContainerOptions{}) + _, err = cr.cli.CopyToContainer(ctx, cr.id, client.CopyToContainerOptions{ + DestinationPath: "/", + Content: tarFile, + }) if err != nil { return fmt.Errorf("failed to copy content to container: %w", err) } @@ -849,7 +878,10 @@ func (cr *containerReference) copyContent(dstPath string, files ...*FileEntry) c } logger.Debugf("Extracting content to '%s'", dstPath) - err := cr.cli.CopyToContainer(ctx, cr.id, dstPath, &buf, types.CopyToContainerOptions{}) + _, err := cr.cli.CopyToContainer(ctx, cr.id, client.CopyToContainerOptions{ + DestinationPath: dstPath, + Content: &buf, + }) if err != nil { return fmt.Errorf("failed to copy content to container: %w", err) } @@ -859,7 +891,7 @@ func (cr *containerReference) copyContent(dstPath string, files ...*FileEntry) c func (cr *containerReference) attach() common.Executor { return func(ctx context.Context) error { - out, err := cr.cli.ContainerAttach(ctx, cr.id, types.ContainerAttachOptions{ //nolint:staticcheck // pre-existing issue from nektos/act + out, err := cr.cli.ContainerAttach(ctx, cr.id, client.ContainerAttachOptions{ Stream: true, Stdout: true, Stderr: true, @@ -897,7 +929,7 @@ func (cr *containerReference) start() common.Executor { logger := common.Logger(ctx) logger.Debugf("Starting container: %v", cr.id) - if err := cr.cli.ContainerStart(ctx, cr.id, types.ContainerStartOptions{}); err != nil { //nolint:staticcheck // pre-existing issue from nektos/act + if _, err := cr.cli.ContainerStart(ctx, cr.id, client.ContainerStartOptions{}); err != nil { return fmt.Errorf("failed to start container: %w", err) } @@ -909,14 +941,16 @@ func (cr *containerReference) start() common.Executor { func (cr *containerReference) wait() common.Executor { return func(ctx context.Context) error { logger := common.Logger(ctx) - statusCh, errCh := cr.cli.ContainerWait(ctx, cr.id, container.WaitConditionNotRunning) + waitResult := cr.cli.ContainerWait(ctx, cr.id, client.ContainerWaitOptions{ + Condition: container.WaitConditionNotRunning, + }) var statusCode int64 select { - case err := <-errCh: + case err := <-waitResult.Error: if err != nil { return fmt.Errorf("failed to wait for container: %w", err) } - case status := <-statusCh: + case status := <-waitResult.Result: statusCode = status.StatusCode } diff --git a/act/container/docker_run_test.go b/act/container/docker_run_test.go index 9194d8c4..d8a23fc5 100644 --- a/act/container/docker_run_test.go +++ b/act/container/docker_run_test.go @@ -17,9 +17,8 @@ import ( "gitea.com/gitea/runner/act/common" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/client" + "github.com/moby/moby/api/types/container" + mobyclient "github.com/moby/moby/client" "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -27,9 +26,14 @@ import ( ) func TestDocker(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } ctx := context.Background() client, err := GetDockerClient(ctx) - assert.NoError(t, err) //nolint:testifylint // pre-existing issue from nektos/act + if err != nil { + t.Skipf("skipping integration test: %v", err) + } defer client.Close() dockerBuild := NewDockerBuildExecutor(NewDockerBuildExecutorInput{ @@ -67,33 +71,33 @@ func TestDocker(t *testing.T) { } type mockDockerClient struct { - client.APIClient + mobyclient.APIClient mock.Mock } -func (m *mockDockerClient) ContainerExecCreate(ctx context.Context, id string, opts types.ExecConfig) (types.IDResponse, error) { +func (m *mockDockerClient) ExecCreate(ctx context.Context, id string, opts mobyclient.ExecCreateOptions) (mobyclient.ExecCreateResult, error) { args := m.Called(ctx, id, opts) - return args.Get(0).(types.IDResponse), args.Error(1) + return args.Get(0).(mobyclient.ExecCreateResult), args.Error(1) } -func (m *mockDockerClient) ContainerExecAttach(ctx context.Context, id string, opts types.ExecStartCheck) (types.HijackedResponse, error) { +func (m *mockDockerClient) ExecAttach(ctx context.Context, id string, opts mobyclient.ExecAttachOptions) (mobyclient.ExecAttachResult, error) { args := m.Called(ctx, id, opts) - return args.Get(0).(types.HijackedResponse), args.Error(1) + return args.Get(0).(mobyclient.ExecAttachResult), args.Error(1) } -func (m *mockDockerClient) ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error) { - args := m.Called(ctx, execID) - return args.Get(0).(types.ContainerExecInspect), args.Error(1) +func (m *mockDockerClient) ExecInspect(ctx context.Context, execID string, opts mobyclient.ExecInspectOptions) (mobyclient.ExecInspectResult, error) { + args := m.Called(ctx, execID, opts) + return args.Get(0).(mobyclient.ExecInspectResult), args.Error(1) } -func (m *mockDockerClient) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) { - args := m.Called(ctx, containerID, condition) - return args.Get(0).(<-chan container.WaitResponse), args.Get(1).(<-chan error) +func (m *mockDockerClient) ContainerWait(ctx context.Context, containerID string, opts mobyclient.ContainerWaitOptions) mobyclient.ContainerWaitResult { + args := m.Called(ctx, containerID, opts) + return args.Get(0).(mobyclient.ContainerWaitResult) } -func (m *mockDockerClient) CopyToContainer(ctx context.Context, id, path string, content io.Reader, options types.CopyToContainerOptions) error { - args := m.Called(ctx, id, path, content, options) - return args.Error(0) +func (m *mockDockerClient) CopyToContainer(ctx context.Context, id string, options mobyclient.CopyToContainerOptions) (mobyclient.CopyToContainerResult, error) { + args := m.Called(ctx, id, options) + return args.Get(0).(mobyclient.CopyToContainerResult), args.Error(1) } type endlessReader struct { @@ -125,10 +129,12 @@ func TestDockerExecAbort(t *testing.T) { conn.On("Write", mock.AnythingOfType("[]uint8")).Return(1, nil) client := &mockDockerClient{} - client.On("ContainerExecCreate", ctx, "123", mock.AnythingOfType("types.ExecConfig")).Return(types.IDResponse{ID: "id"}, nil) - client.On("ContainerExecAttach", ctx, "id", mock.AnythingOfType("types.ExecStartCheck")).Return(types.HijackedResponse{ - Conn: conn, - Reader: bufio.NewReader(endlessReader{}), + client.On("ExecCreate", ctx, "123", mock.AnythingOfType("client.ExecCreateOptions")).Return(mobyclient.ExecCreateResult{ID: "id"}, nil) + client.On("ExecAttach", ctx, "id", mock.AnythingOfType("client.ExecAttachOptions")).Return(mobyclient.ExecAttachResult{ + HijackedResponse: mobyclient.HijackedResponse{ + Conn: conn, + Reader: bufio.NewReader(endlessReader{}), + }, }, nil) cr := &containerReference{ @@ -162,12 +168,14 @@ func TestDockerExecFailure(t *testing.T) { conn := &mockConn{} client := &mockDockerClient{} - client.On("ContainerExecCreate", ctx, "123", mock.AnythingOfType("types.ExecConfig")).Return(types.IDResponse{ID: "id"}, nil) - client.On("ContainerExecAttach", ctx, "id", mock.AnythingOfType("types.ExecStartCheck")).Return(types.HijackedResponse{ - Conn: conn, - Reader: bufio.NewReader(strings.NewReader("output")), + client.On("ExecCreate", ctx, "123", mock.AnythingOfType("client.ExecCreateOptions")).Return(mobyclient.ExecCreateResult{ID: "id"}, nil) + client.On("ExecAttach", ctx, "id", mock.AnythingOfType("client.ExecAttachOptions")).Return(mobyclient.ExecAttachResult{ + HijackedResponse: mobyclient.HijackedResponse{ + Conn: conn, + Reader: bufio.NewReader(strings.NewReader("output")), + }, }, nil) - client.On("ContainerExecInspect", ctx, "id").Return(types.ContainerExecInspect{ + client.On("ExecInspect", ctx, "id", mobyclient.ExecInspectOptions{}).Return(mobyclient.ExecInspectResult{ ExitCode: 1, }, nil) @@ -197,8 +205,11 @@ func TestDockerWaitFailure(t *testing.T) { errCh := make(chan error, 1) client := &mockDockerClient{} - client.On("ContainerWait", ctx, "123", container.WaitConditionNotRunning). - Return((<-chan container.WaitResponse)(statusCh), (<-chan error)(errCh)) + client.On("ContainerWait", ctx, "123", mobyclient.ContainerWaitOptions{Condition: container.WaitConditionNotRunning}). + Return(mobyclient.ContainerWaitResult{ + Result: (<-chan container.WaitResponse)(statusCh), + Error: (<-chan error)(errCh), + }) cr := &containerReference{ id: "123", @@ -220,11 +231,13 @@ func TestDockerWaitFailure(t *testing.T) { func TestDockerCopyTarStream(t *testing.T) { ctx := context.Background() - conn := &mockConn{} - client := &mockDockerClient{} - client.On("CopyToContainer", ctx, "123", "/", mock.Anything, mock.AnythingOfType("types.CopyToContainerOptions")).Return(nil) - client.On("CopyToContainer", ctx, "123", "/var/run/act", mock.Anything, mock.AnythingOfType("types.CopyToContainerOptions")).Return(nil) + client.On("CopyToContainer", ctx, "123", mock.MatchedBy(func(opts mobyclient.CopyToContainerOptions) bool { + return opts.DestinationPath == "/" && opts.Content != nil + })).Return(mobyclient.CopyToContainerResult{}, nil) + client.On("CopyToContainer", ctx, "123", mock.MatchedBy(func(opts mobyclient.CopyToContainerOptions) bool { + return opts.DestinationPath == "/var/run/act" && opts.Content != nil + })).Return(mobyclient.CopyToContainerResult{}, nil) cr := &containerReference{ id: "123", cli: client, @@ -235,20 +248,18 @@ func TestDockerCopyTarStream(t *testing.T) { _ = cr.CopyTarStream(ctx, "/var/run/act", &bytes.Buffer{}) - conn.AssertExpectations(t) client.AssertExpectations(t) } func TestDockerCopyTarStreamErrorInCopyFiles(t *testing.T) { ctx := context.Background() - conn := &mockConn{} - merr := errors.New("Failure") client := &mockDockerClient{} - client.On("CopyToContainer", ctx, "123", "/", mock.Anything, mock.AnythingOfType("types.CopyToContainerOptions")).Return(merr) - client.On("CopyToContainer", ctx, "123", "/", mock.Anything, mock.AnythingOfType("types.CopyToContainerOptions")).Return(merr) + client.On("CopyToContainer", ctx, "123", mock.MatchedBy(func(opts mobyclient.CopyToContainerOptions) bool { + return opts.DestinationPath == "/" && opts.Content != nil + })).Return(mobyclient.CopyToContainerResult{}, merr) cr := &containerReference{ id: "123", cli: client, @@ -260,20 +271,21 @@ func TestDockerCopyTarStreamErrorInCopyFiles(t *testing.T) { err := cr.CopyTarStream(ctx, "/var/run/act", &bytes.Buffer{}) assert.ErrorIs(t, err, merr) //nolint:testifylint // pre-existing issue from nektos/act - conn.AssertExpectations(t) client.AssertExpectations(t) } func TestDockerCopyTarStreamErrorInMkdir(t *testing.T) { ctx := context.Background() - conn := &mockConn{} - merr := errors.New("Failure") client := &mockDockerClient{} - client.On("CopyToContainer", ctx, "123", "/", mock.Anything, mock.AnythingOfType("types.CopyToContainerOptions")).Return(nil) - client.On("CopyToContainer", ctx, "123", "/var/run/act", mock.Anything, mock.AnythingOfType("types.CopyToContainerOptions")).Return(merr) + client.On("CopyToContainer", ctx, "123", mock.MatchedBy(func(opts mobyclient.CopyToContainerOptions) bool { + return opts.DestinationPath == "/" && opts.Content != nil + })).Return(mobyclient.CopyToContainerResult{}, nil) + client.On("CopyToContainer", ctx, "123", mock.MatchedBy(func(opts mobyclient.CopyToContainerOptions) bool { + return opts.DestinationPath == "/var/run/act" && opts.Content != nil + })).Return(mobyclient.CopyToContainerResult{}, merr) cr := &containerReference{ id: "123", cli: client, @@ -285,7 +297,6 @@ func TestDockerCopyTarStreamErrorInMkdir(t *testing.T) { err := cr.CopyTarStream(ctx, "/var/run/act", &bytes.Buffer{}) assert.ErrorIs(t, err, merr) //nolint:testifylint // pre-existing issue from nektos/act - conn.AssertExpectations(t) client.AssertExpectations(t) } diff --git a/act/container/docker_stub.go b/act/container/docker_stub.go index 74fd4a2a..004ca17e 100644 --- a/act/container/docker_stub.go +++ b/act/container/docker_stub.go @@ -12,7 +12,7 @@ import ( "gitea.com/gitea/runner/act/common" - "github.com/docker/docker/api/types" + "github.com/moby/moby/api/types/system" "github.com/pkg/errors" ) @@ -51,8 +51,8 @@ func RunnerArch(ctx context.Context) string { return runtime.GOOS } -func GetHostInfo(ctx context.Context) (info types.Info, err error) { - return types.Info{}, nil +func GetHostInfo(ctx context.Context) (info system.Info, err error) { + return system.Info{}, nil } func NewDockerVolumeRemoveExecutor(volume string, force bool) common.Executor { diff --git a/act/container/docker_volume.go b/act/container/docker_volume.go index f91bce83..30046ea7 100644 --- a/act/container/docker_volume.go +++ b/act/container/docker_volume.go @@ -11,8 +11,7 @@ import ( "gitea.com/gitea/runner/act/common" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/volume" + "github.com/moby/moby/client" ) func NewDockerVolumeRemoveExecutor(volumeName string, force bool) common.Executor { @@ -23,12 +22,12 @@ func NewDockerVolumeRemoveExecutor(volumeName string, force bool) common.Executo } defer cli.Close() - list, err := cli.VolumeList(ctx, volume.ListOptions{Filters: filters.NewArgs()}) + list, err := cli.VolumeList(ctx, client.VolumeListOptions{}) if err != nil { return err } - for _, vol := range list.Volumes { + for _, vol := range list.Items { if vol.Name == volumeName { return removeExecutor(volumeName, force)(ctx) } @@ -54,6 +53,7 @@ func removeExecutor(volume string, force bool) common.Executor { } defer cli.Close() - return cli.VolumeRemove(ctx, volume, force) + _, err = cli.VolumeRemove(ctx, volume, client.VolumeRemoveOptions{Force: force}) + return err } } diff --git a/act/runner/runner.go b/act/runner/runner.go index 29f89715..ba88ad4a 100644 --- a/act/runner/runner.go +++ b/act/runner/runner.go @@ -16,7 +16,7 @@ import ( "gitea.com/gitea/runner/act/common" "gitea.com/gitea/runner/act/model" - docker_container "github.com/docker/docker/api/types/container" + docker_container "github.com/moby/moby/api/types/container" log "github.com/sirupsen/logrus" ) diff --git a/go.mod b/go.mod index a2e3a1f6..7c787829 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( code.gitea.io/actions-proto-go v0.4.1 connectrpc.com/connect v1.19.2 github.com/avast/retry-go/v4 v4.7.0 - github.com/docker/docker v25.0.15+incompatible github.com/joho/godotenv v1.5.1 github.com/mattn/go-isatty v0.0.22 github.com/sirupsen/logrus v1.9.4 @@ -14,7 +13,6 @@ require ( github.com/stretchr/testify v1.11.1 go.yaml.in/yaml/v4 v4.0.0-rc.3 golang.org/x/term v0.43.0 - golang.org/x/time v0.14.0 // indirect google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.2 @@ -23,16 +21,20 @@ require ( require ( dario.cat/mergo v1.0.2 github.com/Masterminds/semver v1.5.0 + github.com/containerd/errdefs v1.0.0 github.com/creack/pty v1.1.24 github.com/distribution/reference v0.6.0 - github.com/docker/cli v25.0.7+incompatible - github.com/docker/go-connections v0.6.0 + github.com/docker/cli v29.4.3+incompatible + github.com/docker/go-connections v0.7.0 github.com/go-git/go-billy/v5 v5.9.0 github.com/go-git/go-git/v5 v5.19.0 github.com/gobwas/glob v0.2.3 + github.com/google/go-cmp v0.7.0 github.com/julienschmidt/httprouter v1.3.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/moby/buildkit v0.13.2 + github.com/moby/go-archive v0.2.0 + github.com/moby/moby/api v1.54.2 + github.com/moby/moby/client v0.4.1 github.com/moby/patternmatcher v0.6.1 github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/selinux v1.14.1 @@ -42,11 +44,11 @@ require ( github.com/spf13/pflag v1.0.10 github.com/timshannon/bolthold v0.0.0-20240314194003-30aac6950928 go.etcd.io/bbolt v1.4.3 + tags.cncf.io/container-device-interface v1.1.0 ) require ( cyphar.com/go-pathrs v0.2.3 // indirect - github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -54,11 +56,11 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/cloudflare/circl v1.6.3 // indirect - github.com/containerd/containerd v1.7.29 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/docker-credential-helpers v0.9.5 // indirect + github.com/docker/docker-credential-helpers v0.9.6 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.19.0 // indirect @@ -66,30 +68,28 @@ require ( github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/gogo/protobuf v1.3.2 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/google/go-cmp v0.7.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.6.0 // indirect - github.com/klauspost/compress v1.18.4 // indirect + github.com/klauspost/compress v1.18.5 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-runewidth v0.0.21 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect - github.com/mitchellh/mapstructure v1.1.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect - github.com/moby/term v0.5.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pjbgf/sha1cd v0.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sergi/go-diff v1.4.0 // indirect github.com/skeema/knownhosts v1.3.2 // indirect @@ -100,16 +100,16 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect - go.opentelemetry.io/otel v1.40.0 // indirect - go.opentelemetry.io/otel/metric v1.40.0 // indirect - go.opentelemetry.io/otel/trace v1.40.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.50.0 // indirect golang.org/x/net v0.53.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.44.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 8638b38b..e30f2e63 100644 --- a/go.sum +++ b/go.sum @@ -8,15 +8,11 @@ dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ= -github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -29,16 +25,16 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= -github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE= -github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -51,14 +47,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v25.0.7+incompatible h1:scW/AbGafKmANsonsFckFHTwpz2QypoPA/zpoLnDs/E= -github.com/docker/cli v25.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v25.0.15+incompatible h1:JhRD6vZdk0Ms3SEMztefBISJL13NbxudQnGix6l+T5M= -github.com/docker/docker v25.0.15+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= -github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= -github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= -github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/cli v29.4.3+incompatible h1:u+UliYm2J/rYrIh2FqHQg32neRG8GjbvNuwQRTzGspU= +github.com/docker/cli v29.4.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker-credential-helpers v0.9.6 h1:cT2PbRPSlnMmNTfT2TDMXRyQ1KMWHG7xoTLBcn1ZNv0= +github.com/docker/docker-credential-helpers v0.9.6/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= @@ -84,10 +78,10 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -96,8 +90,6 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -110,10 +102,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY= github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= -github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -133,10 +123,14 @@ github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEj github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/moby/buildkit v0.13.2 h1:nXNszM4qD9E7QtG7bFWPnDI1teUQFQglBzon/IU3SzI= -github.com/moby/buildkit v0.13.2/go.mod h1:2cyVOv9NoHM7arphK9ZfHIWKn9YVZRFd1wXB8kKmEzY= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= +github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= +github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= +github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= +github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= @@ -145,10 +139,6 @@ github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= -github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= @@ -173,8 +163,8 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rhysd/actionlint v1.7.12 h1:vQ4GeJN86C0QH+gTUQcs8McmK62OLT3kmakPMtEWYnY= github.com/rhysd/actionlint v1.7.12/go.mod h1:krOUhujIsJusovkaYzQ/VNH8PFexjNKqU0q5XI/4w+g= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= @@ -218,8 +208,6 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= @@ -228,55 +216,35 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go= go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -289,28 +257,10 @@ golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= -google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= -google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -320,10 +270,13 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= +tags.cncf.io/container-device-interface v1.1.0 h1:RnxNhxF1JOu6CJUVpetTYvrXHdxw9j9jFYgZpI+anSY= +tags.cncf.io/container-device-interface v1.1.0/go.mod h1:76Oj0Yqp9FwTx/pySDc8Bxjpg+VqXfDb50cKAXVJ34Q= diff --git a/internal/app/cmd/exec.go b/internal/app/cmd/exec.go index 7a448296..fb6dc856 100644 --- a/internal/app/cmd/exec.go +++ b/internal/app/cmd/exec.go @@ -23,8 +23,8 @@ import ( "gitea.com/gitea/runner/act/model" "gitea.com/gitea/runner/act/runner" - "github.com/docker/docker/api/types/container" "github.com/joho/godotenv" + "github.com/moby/moby/api/types/container" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/term" diff --git a/internal/app/run/runner.go b/internal/app/run/runner.go index 3b7b2b0e..e7edaef6 100644 --- a/internal/app/run/runner.go +++ b/internal/app/run/runner.go @@ -33,7 +33,7 @@ import ( runnerv1 "code.gitea.io/actions-proto-go/runner/v1" "connectrpc.com/connect" - "github.com/docker/docker/api/types/container" + "github.com/moby/moby/api/types/container" log "github.com/sirupsen/logrus" ) diff --git a/internal/pkg/envcheck/docker.go b/internal/pkg/envcheck/docker.go index 65aae4de..6212323a 100644 --- a/internal/pkg/envcheck/docker.go +++ b/internal/pkg/envcheck/docker.go @@ -7,7 +7,7 @@ import ( "context" "fmt" - "github.com/docker/docker/client" + "github.com/moby/moby/client" ) func CheckIfDockerRunning(ctx context.Context, configDockerHost string) error { @@ -19,13 +19,13 @@ func CheckIfDockerRunning(ctx context.Context, configDockerHost string) error { opts = append(opts, client.WithHost(configDockerHost)) } - cli, err := client.NewClientWithOpts(opts...) + cli, err := client.New(opts...) if err != nil { return err } defer cli.Close() - _, err = cli.Ping(ctx) + _, err = cli.Ping(ctx, client.PingOptions{}) if err != nil { return fmt.Errorf("cannot ping the docker daemon, is it running? %w", err) }