mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-08-07 09:24:22 +02:00
Revert #1136 and use actionslib instead. revert chore: bump the module path to `/v3`, take the version from the VCS stamp (#1136) gitea can not consume the runner's api by version while it's version mismatches the module version: ``` go: gitea.com/gitea/runner@v3.0.1: invalid version: module contains a go.mod file, so module path must match major version ("gitea.com/gitea/runner/v3") ``` Fix that by bumping the module version now. The existing `v3.0.0` and `v3.0.1` tags stay unusable, so a new tag is needed after this lands. Also drop the version `-X` linker flags, which would otherwise have to repeat the new path in both `Makefile` and `.goreleaser.yaml`, where a stale path makes injection silently no-op. Go has recorded the module version in the build info since 1.24, so `Version()` reads it from there, keeping the variable as an override for builds without a VCS stamp. That part started as https://gitea.com/gitea/runner/pulls/1137 but belongs here: the stamp resolves against the tags that are legal for the module path, so without the `/v3` bump it would report `v1.0.9-0.<ts>-<sha>`. Since `release-nightly.yml` triggers on every push to `main`, splitting them would publish a nightly with a `v1` version. --------- Co-authored-by: bircni <bircni@icloud.com> Reviewed-on: https://gitea.com/gitea/runner/pulls/1136 Reviewed-by: techknowlogick <9+techknowlogick@noreply.gitea.com> Reviewed-by: bircni <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io> Reviewed-on: https://gitea.com/gitea/runner/pulls/1148 Reviewed-by: Zettat123 <39446+zettat123@noreply.gitea.com>
175 lines
5.7 KiB
Go
175 lines
5.7 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// Copyright 2023 The nektos/act Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
//go:build !(WITHOUT_DOCKER || !(linux || darwin || windows || netbsd))
|
|
|
|
package container
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitea.com/gitea/runner/act/common"
|
|
|
|
"github.com/moby/moby/client"
|
|
)
|
|
|
|
const (
|
|
networkCreateAttempts = 3
|
|
networkCreateRetryDelay = time.Second
|
|
|
|
// marks the networks a runner creates for its jobs, so it can tell its own leftovers from
|
|
// those of another runner sharing the daemon
|
|
runnerUUIDLabel = "com.gitea.runner.uuid"
|
|
)
|
|
|
|
// RemoveOrphanNetworks removes the networks this runner created for jobs whose teardown did
|
|
// not get to them: the runner died with the job, the teardown timed out, or the network still
|
|
// had an endpoint on it at the time. Each one holds a subnet of the daemon's address pool
|
|
// until it is removed. Networks created after createdBefore are left alone, so a job starting
|
|
// while this runs cannot lose the network it has created but not yet attached a container to.
|
|
func RemoveOrphanNetworks(ctx context.Context, runnerUUID string, createdBefore time.Time) error {
|
|
cli, err := GetDockerClient(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to connect to the docker daemon: %w", err)
|
|
}
|
|
defer cli.Close()
|
|
|
|
return removeOrphanNetworks(ctx, cli, runnerUUID, createdBefore)
|
|
}
|
|
|
|
func removeOrphanNetworks(ctx context.Context, cli client.APIClient, runnerUUID string, createdBefore time.Time) error {
|
|
networks, err := cli.NetworkList(ctx, client.NetworkListOptions{
|
|
Filters: make(client.Filters).Add("label", runnerUUIDLabel+"="+runnerUUID),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var errs []error
|
|
for _, n := range networks.Items {
|
|
result, err := cli.NetworkInspect(ctx, n.ID, client.NetworkInspectOptions{})
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("failed to inspect network %s: %w", n.Name, err))
|
|
continue
|
|
}
|
|
// the emptiness check, not the label, is what keeps a live job of another process
|
|
// sharing this registration safe
|
|
if len(result.Network.Containers) != 0 || result.Network.Created.After(createdBefore) {
|
|
continue
|
|
}
|
|
if _, err := cli.NetworkRemove(ctx, n.ID, client.NetworkRemoveOptions{}); err != nil {
|
|
errs = append(errs, fmt.Errorf("failed to remove network %s: %w", n.Name, err))
|
|
continue
|
|
}
|
|
common.Logger(ctx).Infof("removed docker network %s left behind by an earlier job", n.Name)
|
|
}
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
func NewDockerNetworkCreateExecutor(name string, opts NewDockerNetworkCreateExecutorInput) common.Executor {
|
|
return func(ctx context.Context) error {
|
|
cli, err := GetDockerClient(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cli.Close()
|
|
|
|
// Only create the network if it doesn't exist
|
|
networks, err := cli.NetworkList(ctx, client.NetworkListOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// For Gitea, reduce log noise
|
|
// common.Logger(ctx).Debugf("%v", networks)
|
|
for _, n := range networks.Items {
|
|
if n.Name == name {
|
|
common.Logger(ctx).Debugf("Network %v exists", name)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
for i := range networkCreateAttempts {
|
|
if i > 0 {
|
|
common.Logger(ctx).Infof("Waiting for a free docker address pool to create network %s", name)
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-time.After(time.Duration(i) * networkCreateRetryDelay):
|
|
}
|
|
}
|
|
if _, err = cli.NetworkCreate(ctx, name, client.NetworkCreateOptions{
|
|
Driver: "bridge",
|
|
Scope: "local",
|
|
EnableIPv4: opts.EnableIPv4,
|
|
EnableIPv6: opts.EnableIPv6,
|
|
Labels: runnerLabels(opts.RunnerUUID),
|
|
}); err == nil {
|
|
return nil
|
|
}
|
|
if !isAddressPoolExhausted(err) {
|
|
return err
|
|
}
|
|
}
|
|
return fmt.Errorf("docker has no address pool left for this job's network, lower runner.capacity or widen default-address-pools in the docker daemon config: %w", err)
|
|
}
|
|
}
|
|
|
|
func runnerLabels(runnerUUID string) map[string]string {
|
|
if runnerUUID == "" {
|
|
return nil
|
|
}
|
|
return map[string]string{runnerUUIDLabel: runnerUUID}
|
|
}
|
|
|
|
// The daemon reports this as a plain invalid-parameter error, the same kind it uses for every
|
|
// malformed request, so the message is the only discriminator.
|
|
func isAddressPoolExhausted(err error) bool {
|
|
msg := err.Error()
|
|
return strings.Contains(msg, "all predefined address pools have been fully subnetted") ||
|
|
strings.Contains(msg, "could not find an available, non-overlapping IPv4 address pool among the defaults") // docker 24 and older
|
|
}
|
|
|
|
func NewDockerNetworkRemoveExecutor(name string) common.Executor {
|
|
return func(ctx context.Context) error {
|
|
cli, err := GetDockerClient(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cli.Close()
|
|
|
|
// 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, client.NetworkListOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// For Gitea, reduce log noise
|
|
// common.Logger(ctx).Debugf("%v", networks)
|
|
var errs []error
|
|
for _, n := range networks.Items {
|
|
if n.Name == name {
|
|
result, err := cli.NetworkInspect(ctx, n.ID, client.NetworkInspectOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// it holds a subnet out of the daemon's pool until something reclaims it
|
|
if len(result.Network.Containers) != 0 {
|
|
common.Logger(ctx).Warnf("Refusing to remove network %s because it still has active endpoints, the idle cleanup reclaims it once they are gone", name)
|
|
continue
|
|
}
|
|
if _, err = cli.NetworkRemove(ctx, n.ID, client.NetworkRemoveOptions{}); err != nil {
|
|
errs = append(errs, fmt.Errorf("failed to remove network %s: %w", name, err))
|
|
}
|
|
}
|
|
}
|
|
|
|
return errors.Join(errs...)
|
|
}
|
|
}
|