mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-06-15 14:24:22 +02:00
feat: ipv6 options for network container creation (#1029)
Here is a final proposal for ipv6 enablement on temporary network created by gitea runner --------- Co-authored-by: Nicolas <bircni@icloud.com> Co-authored-by: Nicolas Schwartz <9308314+StarAurryon@users.noreply.github.com> Reviewed-on: https://gitea.com/gitea/runner/pulls/1029 Reviewed-by: Nicolas <bircni@icloud.com> Co-authored-by: StarAurryon <206206+staraurryon@noreply.gitea.com> Co-committed-by: StarAurryon <206206+staraurryon@noreply.gitea.com>
This commit is contained in:
@@ -84,6 +84,12 @@ type NewDockerBuildExecutorInput struct {
|
|||||||
Platform string
|
Platform string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDockerNetworkCreateExecutorInput the input for the NewDockerNetworkCreateExecutor function
|
||||||
|
type NewDockerNetworkCreateExecutorInput struct {
|
||||||
|
EnableIPv4 *bool
|
||||||
|
EnableIPv6 *bool
|
||||||
|
}
|
||||||
|
|
||||||
// NewDockerPullExecutorInput the input for the NewDockerPullExecutor function
|
// NewDockerPullExecutorInput the input for the NewDockerPullExecutor function
|
||||||
type NewDockerPullExecutorInput struct {
|
type NewDockerPullExecutorInput struct {
|
||||||
Image string
|
Image string
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
"github.com/moby/moby/client"
|
"github.com/moby/moby/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDockerNetworkCreateExecutor(name string) common.Executor {
|
func NewDockerNetworkCreateExecutor(name string, opts NewDockerNetworkCreateExecutorInput) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
cli, err := GetDockerClient(ctx)
|
cli, err := GetDockerClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -37,8 +37,10 @@ func NewDockerNetworkCreateExecutor(name string) common.Executor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err = cli.NetworkCreate(ctx, name, client.NetworkCreateOptions{
|
_, err = cli.NetworkCreate(ctx, name, client.NetworkCreateOptions{
|
||||||
Driver: "bridge",
|
Driver: "bridge",
|
||||||
Scope: "local",
|
Scope: "local",
|
||||||
|
EnableIPv4: opts.EnableIPv4,
|
||||||
|
EnableIPv6: opts.EnableIPv6,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func NewDockerVolumeRemoveExecutor(volume string, force bool) common.Executor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDockerNetworkCreateExecutor(name string) common.Executor {
|
func NewDockerNetworkCreateExecutor(name string, opts NewDockerNetworkCreateExecutorInput) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -471,7 +471,8 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
|||||||
rc.pullServicesImages(rc.Config.ForcePull),
|
rc.pullServicesImages(rc.Config.ForcePull),
|
||||||
rc.JobContainer.Pull(rc.Config.ForcePull),
|
rc.JobContainer.Pull(rc.Config.ForcePull),
|
||||||
rc.stopJobContainer(),
|
rc.stopJobContainer(),
|
||||||
container.NewDockerNetworkCreateExecutor(networkName).IfBool(createAndDeleteNetwork),
|
container.NewDockerNetworkCreateExecutor(networkName, rc.Config.ContainerNetworkCreateOptions).
|
||||||
|
IfBool(createAndDeleteNetwork),
|
||||||
rc.startServiceContainers(networkName),
|
rc.startServiceContainers(networkName),
|
||||||
rc.JobContainer.Create(rc.Config.ContainerCapAdd, rc.Config.ContainerCapDrop),
|
rc.JobContainer.Create(rc.Config.ContainerCapAdd, rc.Config.ContainerCapDrop),
|
||||||
rc.JobContainer.Start(false),
|
rc.JobContainer.Start(false),
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.com/gitea/runner/act/common"
|
"gitea.com/gitea/runner/act/common"
|
||||||
|
"gitea.com/gitea/runner/act/container"
|
||||||
"gitea.com/gitea/runner/act/model"
|
"gitea.com/gitea/runner/act/model"
|
||||||
|
|
||||||
docker_container "github.com/moby/moby/api/types/container"
|
docker_container "github.com/moby/moby/api/types/container"
|
||||||
@@ -28,47 +29,48 @@ type Runner interface {
|
|||||||
|
|
||||||
// Config contains the config for a new runner
|
// Config contains the config for a new runner
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Actor string // the user that triggered the event
|
Actor string // the user that triggered the event
|
||||||
Workdir string // path to working directory
|
Workdir string // path to working directory
|
||||||
ActionCacheDir string // path used for caching action contents
|
ActionCacheDir string // path used for caching action contents
|
||||||
ActionOfflineMode bool // when offline, use cached action contents
|
ActionOfflineMode bool // when offline, use cached action contents
|
||||||
BindWorkdir bool // bind the workdir to the job container
|
BindWorkdir bool // bind the workdir to the job container
|
||||||
EventName string // name of event to run
|
EventName string // name of event to run
|
||||||
EventPath string // path to JSON file to use for event.json in containers
|
EventPath string // path to JSON file to use for event.json in containers
|
||||||
DefaultBranch string // name of the main branch for this repository
|
DefaultBranch string // name of the main branch for this repository
|
||||||
ReuseContainers bool // reuse containers to maintain state
|
ReuseContainers bool // reuse containers to maintain state
|
||||||
ForcePull bool // force pulling of the image, even if already present
|
ForcePull bool // force pulling of the image, even if already present
|
||||||
ForceRebuild bool // force rebuilding local docker image action
|
ForceRebuild bool // force rebuilding local docker image action
|
||||||
LogOutput bool // log the output from docker run
|
LogOutput bool // log the output from docker run
|
||||||
JSONLogger bool // use json or text logger
|
JSONLogger bool // use json or text logger
|
||||||
LogPrefixJobID bool // switches from the full job name to the job id
|
LogPrefixJobID bool // switches from the full job name to the job id
|
||||||
Env map[string]string // env for containers
|
Env map[string]string // env for containers
|
||||||
Inputs map[string]string // manually passed action inputs
|
Inputs map[string]string // manually passed action inputs
|
||||||
Secrets map[string]string // list of secrets
|
Secrets map[string]string // list of secrets
|
||||||
Vars map[string]string // list of vars
|
Vars map[string]string // list of vars
|
||||||
Token string // GitHub token
|
Token string // GitHub token
|
||||||
InsecureSecrets bool // switch hiding output when printing to terminal
|
InsecureSecrets bool // switch hiding output when printing to terminal
|
||||||
Platforms map[string]string // list of platforms
|
Platforms map[string]string // list of platforms
|
||||||
Privileged bool // use privileged mode
|
Privileged bool // use privileged mode
|
||||||
UsernsMode string // user namespace to use
|
UsernsMode string // user namespace to use
|
||||||
ContainerArchitecture string // Desired OS/architecture platform for running containers
|
ContainerArchitecture string // Desired OS/architecture platform for running containers
|
||||||
ContainerDaemonSocket string // Path to Docker daemon socket
|
ContainerDaemonSocket string // Path to Docker daemon socket
|
||||||
ContainerOptions string // Options for the job container
|
ContainerOptions string // Options for the job container
|
||||||
UseGitIgnore bool // controls if paths in .gitignore should not be copied into container, default true
|
UseGitIgnore bool // controls if paths in .gitignore should not be copied into container, default true
|
||||||
GitHubInstance string // GitHub instance to use, default "github.com"
|
GitHubInstance string // GitHub instance to use, default "github.com"
|
||||||
ContainerCapAdd []string // list of kernel capabilities to add to the containers
|
ContainerCapAdd []string // list of kernel capabilities to add to the containers
|
||||||
ContainerCapDrop []string // list of kernel capabilities to remove from the containers
|
ContainerCapDrop []string // list of kernel capabilities to remove from the containers
|
||||||
AutoRemove bool // controls if the container is automatically removed upon workflow completion
|
AutoRemove bool // controls if the container is automatically removed upon workflow completion
|
||||||
ArtifactServerPath string // the path where the artifact server stores uploads
|
ArtifactServerPath string // the path where the artifact server stores uploads
|
||||||
ArtifactServerAddr string // the address the artifact server binds to
|
ArtifactServerAddr string // the address the artifact server binds to
|
||||||
ArtifactServerPort string // the port the artifact server binds to
|
ArtifactServerPort string // the port the artifact server binds to
|
||||||
NoSkipCheckout bool // do not skip actions/checkout
|
NoSkipCheckout bool // do not skip actions/checkout
|
||||||
RemoteName string // remote name in local git repo config
|
RemoteName string // remote name in local git repo config
|
||||||
ReplaceGheActionWithGithubCom []string // Use actions from GitHub Enterprise instance to GitHub
|
ReplaceGheActionWithGithubCom []string // Use actions from GitHub Enterprise instance to GitHub
|
||||||
ReplaceGheActionTokenWithGithubCom string // Token of private action repo on GitHub.
|
ReplaceGheActionTokenWithGithubCom string // Token of private action repo on GitHub.
|
||||||
Matrix map[string]map[string]bool // Matrix config to run
|
Matrix map[string]map[string]bool // Matrix config to run
|
||||||
ContainerNetworkMode docker_container.NetworkMode // the network mode of job containers (the value of --network)
|
ContainerNetworkMode docker_container.NetworkMode // the network mode of job containers (the value of --network)
|
||||||
ActionCache ActionCache // Use a custom ActionCache Implementation
|
ContainerNetworkCreateOptions container.NewDockerNetworkCreateExecutorInput // the default network create options
|
||||||
|
ActionCache ActionCache // Use a custom ActionCache Implementation
|
||||||
|
|
||||||
PresetGitHubContext *model.GithubContext // the preset github context, overrides some fields like DefaultBranch, Env, Secrets etc.
|
PresetGitHubContext *model.GithubContext // the preset github context, overrides some fields like DefaultBranch, Env, Secrets etc.
|
||||||
EventJSON string // the content of JSON file to use for event.json in containers, overrides EventPath
|
EventJSON string // the content of JSON file to use for event.json in containers, overrides EventPath
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
"gitea.com/gitea/runner/act/artifactcache"
|
"gitea.com/gitea/runner/act/artifactcache"
|
||||||
"gitea.com/gitea/runner/act/common"
|
"gitea.com/gitea/runner/act/common"
|
||||||
|
"gitea.com/gitea/runner/act/container"
|
||||||
"gitea.com/gitea/runner/act/model"
|
"gitea.com/gitea/runner/act/model"
|
||||||
"gitea.com/gitea/runner/act/runner"
|
"gitea.com/gitea/runner/act/runner"
|
||||||
"gitea.com/gitea/runner/internal/pkg/client"
|
"gitea.com/gitea/runner/internal/pkg/client"
|
||||||
@@ -33,7 +34,7 @@ import (
|
|||||||
|
|
||||||
"connectrpc.com/connect"
|
"connectrpc.com/connect"
|
||||||
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
|
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
|
||||||
"github.com/moby/moby/api/types/container"
|
docker_container "github.com/moby/moby/api/types/container"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -418,22 +419,26 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
|||||||
AllocatePTY: r.cfg.Runner.AllocatePTY,
|
AllocatePTY: r.cfg.Runner.AllocatePTY,
|
||||||
ActionOfflineMode: r.cfg.Cache.OfflineMode,
|
ActionOfflineMode: r.cfg.Cache.OfflineMode,
|
||||||
|
|
||||||
ReuseContainers: false,
|
ReuseContainers: false,
|
||||||
ForcePull: r.cfg.Container.ForcePull,
|
ForcePull: r.cfg.Container.ForcePull,
|
||||||
ForceRebuild: r.cfg.Container.ForceRebuild,
|
ForceRebuild: r.cfg.Container.ForceRebuild,
|
||||||
LogOutput: true,
|
LogOutput: true,
|
||||||
JSONLogger: false,
|
JSONLogger: false,
|
||||||
Env: envs,
|
Env: envs,
|
||||||
Secrets: task.Secrets,
|
Secrets: task.Secrets,
|
||||||
GitHubInstance: strings.TrimSuffix(r.client.Address(), "/"),
|
GitHubInstance: strings.TrimSuffix(r.client.Address(), "/"),
|
||||||
AutoRemove: true,
|
AutoRemove: true,
|
||||||
NoSkipCheckout: true,
|
NoSkipCheckout: true,
|
||||||
PresetGitHubContext: preset,
|
PresetGitHubContext: preset,
|
||||||
EventJSON: string(eventJSON),
|
EventJSON: string(eventJSON),
|
||||||
ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id),
|
ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id),
|
||||||
ContainerMaxLifetime: maxLifetime,
|
ContainerMaxLifetime: maxLifetime,
|
||||||
CleanWorkdir: true,
|
CleanWorkdir: true,
|
||||||
ContainerNetworkMode: container.NetworkMode(r.cfg.Container.Network),
|
ContainerNetworkMode: docker_container.NetworkMode(r.cfg.Container.Network),
|
||||||
|
ContainerNetworkCreateOptions: container.NewDockerNetworkCreateExecutorInput{
|
||||||
|
EnableIPv4: r.cfg.Container.NetworkCreateOptions.EnableIPv4,
|
||||||
|
EnableIPv6: r.cfg.Container.NetworkCreateOptions.EnableIPv6,
|
||||||
|
},
|
||||||
ContainerOptions: r.cfg.Container.Options,
|
ContainerOptions: r.cfg.Container.Options,
|
||||||
ContainerDaemonSocket: r.cfg.Container.DockerHost,
|
ContainerDaemonSocket: r.cfg.Container.DockerHost,
|
||||||
Privileged: r.cfg.Container.Privileged,
|
Privileged: r.cfg.Container.Privileged,
|
||||||
|
|||||||
@@ -116,6 +116,13 @@ container:
|
|||||||
# If it's empty, runner will create a network automatically.
|
# If it's empty, runner will create a network automatically.
|
||||||
# Deprecated: `network_mode` is still accepted for old configs; use `network` instead.
|
# Deprecated: `network_mode` is still accepted for old configs; use `network` instead.
|
||||||
network: ""
|
network: ""
|
||||||
|
# network_create_options only apply when `network` is left empty and the runner
|
||||||
|
# auto-creates a per-job network that does not already exist. They have no effect
|
||||||
|
# when a custom `network` name is set, because that network is used as-is and never
|
||||||
|
# created by the runner. Omit the entire block to use Docker's defaults.
|
||||||
|
network_create_options:
|
||||||
|
enable_ipv4: true # Omit to use Docker's default (IPv4 enabled). Set false to disable IPv4.
|
||||||
|
enable_ipv6: false # Omit to use Docker's default (IPv6 disabled). Enabling it requires dockerd started with --ipv6.
|
||||||
# Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker).
|
# Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker).
|
||||||
privileged: false
|
privileged: false
|
||||||
# Any other options to be used when the container is started (e.g., --add-host=my.gitea.url:host-gateway).
|
# Any other options to be used when the container is started (e.g., --add-host=my.gitea.url:host-gateway).
|
||||||
|
|||||||
@@ -58,18 +58,24 @@ type Cache struct {
|
|||||||
|
|
||||||
// Container represents the configuration for the container.
|
// Container represents the configuration for the container.
|
||||||
type Container struct {
|
type Container struct {
|
||||||
Network string `yaml:"network"` // Network specifies the network for the container.
|
Network string `yaml:"network"` // Network specifies the network for the container.
|
||||||
NetworkMode string `yaml:"network_mode"` // Deprecated: use Network instead. Could be removed after Gitea 1.20
|
NetworkCreateOptions ContainerNetworkCreateOptions `yaml:"network_create_options"` // Add options when the network need to be created by the runner
|
||||||
Privileged bool `yaml:"privileged"` // Privileged indicates whether the container runs in privileged mode.
|
NetworkMode string `yaml:"network_mode"` // Deprecated: use Network instead. Could be removed after Gitea 1.20
|
||||||
Options string `yaml:"options"` // Options specifies additional options for the container.
|
Privileged bool `yaml:"privileged"` // Privileged indicates whether the container runs in privileged mode.
|
||||||
WorkdirParent string `yaml:"workdir_parent"` // WorkdirParent specifies the parent directory for the container's working directory.
|
Options string `yaml:"options"` // Options specifies additional options for the container.
|
||||||
ValidVolumes []string `yaml:"valid_volumes"` // ValidVolumes specifies the volumes (including bind mounts) can be mounted to containers.
|
WorkdirParent string `yaml:"workdir_parent"` // WorkdirParent specifies the parent directory for the container's working directory.
|
||||||
DockerHost string `yaml:"docker_host"` // DockerHost specifies the Docker host. It overrides the value specified in environment variable DOCKER_HOST.
|
ValidVolumes []string `yaml:"valid_volumes"` // ValidVolumes specifies the volumes (including bind mounts) can be mounted to containers.
|
||||||
ForcePull bool `yaml:"force_pull"` // Pull docker image(s) even if already present
|
DockerHost string `yaml:"docker_host"` // DockerHost specifies the Docker host. It overrides the value specified in environment variable DOCKER_HOST.
|
||||||
ForceRebuild bool `yaml:"force_rebuild"` // Rebuild docker image(s) even if already present
|
ForcePull bool `yaml:"force_pull"` // Pull docker image(s) even if already present
|
||||||
RequireDocker bool `yaml:"require_docker"` // Always require a reachable docker daemon, even if not required by runner
|
ForceRebuild bool `yaml:"force_rebuild"` // Rebuild docker image(s) even if already present
|
||||||
DockerTimeout time.Duration `yaml:"docker_timeout"` // Timeout to wait for the docker daemon to be reachable, if docker is required by require_docker or runner
|
RequireDocker bool `yaml:"require_docker"` // Always require a reachable docker daemon, even if not required by runner
|
||||||
BindWorkdir bool `yaml:"bind_workdir"` // BindWorkdir binds the workspace to the host filesystem instead of using Docker volumes. Required for DinD when jobs use docker compose with bind mounts.
|
DockerTimeout time.Duration `yaml:"docker_timeout"` // Timeout to wait for the docker daemon to be reachable, if docker is required by require_docker or runner
|
||||||
|
BindWorkdir bool `yaml:"bind_workdir"` // BindWorkdir binds the workspace to the host filesystem instead of using Docker volumes. Required for DinD when jobs use docker compose with bind mounts.
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContainerNetworkCreateOptions struct {
|
||||||
|
EnableIPv4 *bool `yaml:"enable_ipv4"` // Enable or disable IPv4 for the network (true for docker by default)
|
||||||
|
EnableIPv6 *bool `yaml:"enable_ipv6"` // Enable or disable IPv6 for the network (false for docker by default)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Host represents the configuration for the host.
|
// Host represents the configuration for the host.
|
||||||
|
|||||||
@@ -117,3 +117,50 @@ func TestLoadDefault_MalformedYAMLReturnsParseError(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "parse config file")
|
assert.Contains(t, err.Error(), "parse config file")
|
||||||
assert.NotContains(t, err.Error(), "defaults metadata")
|
assert.NotContains(t, err.Error(), "defaults metadata")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContainerNetworkCreateOptions(t *testing.T) {
|
||||||
|
// Verify that the enable_ipv4/enable_ipv6 YAML keys unmarshal into the *bool fields,
|
||||||
|
// distinguishing an explicit true/false from an omitted key (nil). A nil here is
|
||||||
|
// forwarded as-is to Docker, which applies its own default.
|
||||||
|
loadOptions := func(t *testing.T, yaml string) ContainerNetworkCreateOptions {
|
||||||
|
t.Helper()
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "config.yaml")
|
||||||
|
require.NoError(t, os.WriteFile(path, []byte(yaml), 0o600))
|
||||||
|
|
||||||
|
cfg, err := LoadDefault(path)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return cfg.Container.NetworkCreateOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("enable_ipv6 true unmarshals to non-nil true", func(t *testing.T) {
|
||||||
|
opts := loadOptions(t, "container:\n network_create_options:\n enable_ipv6: true\n")
|
||||||
|
require.NotNil(t, opts.EnableIPv6)
|
||||||
|
assert.True(t, *opts.EnableIPv6)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("enable_ipv6 false unmarshals to non-nil false", func(t *testing.T) {
|
||||||
|
opts := loadOptions(t, "container:\n network_create_options:\n enable_ipv6: false\n")
|
||||||
|
require.NotNil(t, opts.EnableIPv6)
|
||||||
|
assert.False(t, *opts.EnableIPv6)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("enable_ipv4 false unmarshals to non-nil false", func(t *testing.T) {
|
||||||
|
opts := loadOptions(t, "container:\n network_create_options:\n enable_ipv4: false\n")
|
||||||
|
require.NotNil(t, opts.EnableIPv4)
|
||||||
|
assert.False(t, *opts.EnableIPv4)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("omitted keys stay nil", func(t *testing.T) {
|
||||||
|
opts := loadOptions(t, "container:\n network_create_options:\n enable_ipv4: true\n")
|
||||||
|
require.NotNil(t, opts.EnableIPv4)
|
||||||
|
assert.True(t, *opts.EnableIPv4)
|
||||||
|
assert.Nil(t, opts.EnableIPv6, "an omitted enable_ipv6 must remain nil so Docker's default applies")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("omitted block leaves both nil", func(t *testing.T) {
|
||||||
|
opts := loadOptions(t, "container:\n network: \"\"\n")
|
||||||
|
assert.Nil(t, opts.EnableIPv4)
|
||||||
|
assert.Nil(t, opts.EnableIPv6)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user