From 92b4d7337621c76e14796c6a5c21fbe4d7b47446 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Mon, 5 Jun 2023 09:21:59 +0000 Subject: [PATCH] Check volumes (#60) This PR adds a `ValidVolumes` config. Users can specify the volumes (including bind mounts) that can be mounted to containers by this config. Options related to volumes: - [jobs..container.volumes](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idcontainervolumes) - [jobs..services..volumes](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idservicesservice_idvolumes) In addition, volumes specified by `options` will also be checked. Currently, the following default volumes (see https://gitea.com/gitea/act/src/commit/a72822b3f83d3e68ffc697101b713b7badf57e2f/pkg/runner/run_context.go#L116-L166) will be added to `ValidVolumes`: - `act-toolcache` - `` and `-env` - `/var/run/docker.sock` (We need to add a new configuration to control whether the docker daemon can be mounted) Co-authored-by: Jason Song Reviewed-on: https://gitea.com/gitea/act/pulls/60 Reviewed-by: Jason Song Co-authored-by: Zettat123 Co-committed-by: Zettat123 --- pkg/container/container_types.go | 1 + pkg/container/docker_run.go | 47 ++++++++++++++++++++++++ pkg/runner/run_context.go | 61 ++++++++++++++++++++++++-------- pkg/runner/runner.go | 1 + 4 files changed, 96 insertions(+), 14 deletions(-) diff --git a/pkg/container/container_types.go b/pkg/container/container_types.go index ea0d5e7..9d69de9 100644 --- a/pkg/container/container_types.go +++ b/pkg/container/container_types.go @@ -31,6 +31,7 @@ type NewContainerInput struct { AutoRemove bool NetworkAliases []string + ValidVolumes []string } // FileEntry is a file to copy to a container diff --git a/pkg/container/docker_run.go b/pkg/container/docker_run.go index bfb6624..d61a21a 100644 --- a/pkg/container/docker_run.go +++ b/pkg/container/docker_run.go @@ -28,6 +28,7 @@ import ( "github.com/kballard/go-shellquote" "github.com/spf13/pflag" + "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" @@ -483,6 +484,9 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E return err } + // For Gitea + config, hostConfig = cr.sanitizeConfig(ctx, config, hostConfig) + // For Gitea // network-scoped alias is supported only for containers in user defined networks var networkingConfig *network.NetworkingConfig @@ -878,3 +882,46 @@ func (cr *containerReference) wait() common.Executor { return fmt.Errorf("exit with `FAILURE`: %v", statusCode) } } + +func (cr *containerReference) sanitizeConfig(ctx context.Context, config *container.Config, hostConfig *container.HostConfig) (*container.Config, *container.HostConfig) { + logger := common.Logger(ctx) + + if len(cr.input.ValidVolumes) > 0 { + vv := make(map[string]struct{}, len(cr.input.ValidVolumes)) + for _, volume := range cr.input.ValidVolumes { + vv[volume] = struct{}{} + } + // sanitize binds + sanitizedBinds := make([]string, 0, len(hostConfig.Binds)) + for _, bind := range hostConfig.Binds { + parsed, err := loader.ParseVolume(bind) + if err != nil { + logger.Warnf("parse volume [%s] error: %v", bind, err) + continue + } + if parsed.Source == "" { + // anonymous volume + sanitizedBinds = append(sanitizedBinds, bind) + continue + } + if _, ok := vv[parsed.Source]; ok { + sanitizedBinds = append(sanitizedBinds, bind) + } else { + logger.Warnf("[%s] is not a valid volume, will be ignored", bind) + } + } + hostConfig.Binds = sanitizedBinds + // sanitize mounts + sanitizedMounts := make([]mount.Mount, 0, len(hostConfig.Mounts)) + for _, mt := range hostConfig.Mounts { + if _, ok := vv[mt.Source]; ok { + sanitizedMounts = append(sanitizedMounts, mt) + } else { + logger.Warnf("[%s] is not a valid volume, will be ignored", mt.Source) + } + } + hostConfig.Mounts = sanitizedMounts + } + + return config, hostConfig +} diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go index 8909b5a..550173c 100644 --- a/pkg/runner/run_context.go +++ b/pkg/runner/run_context.go @@ -162,6 +162,14 @@ func (rc *RunContext) GetBindsAndMounts() ([]string, map[string]string) { mounts[name] = ext.ToContainerPath(rc.Config.Workdir) } + // For Gitea + // add some default binds and mounts to ValidVolumes + rc.Config.ValidVolumes = append(rc.Config.ValidVolumes, "act-toolcache") + rc.Config.ValidVolumes = append(rc.Config.ValidVolumes, name) + rc.Config.ValidVolumes = append(rc.Config.ValidVolumes, name+"-env") + // TODO: add a new configuration to control whether the docker daemon can be mounted + rc.Config.ValidVolumes = append(rc.Config.ValidVolumes, getDockerDaemonSocketMountPath(rc.Config.ContainerDaemonSocket)) + return binds, mounts } @@ -295,22 +303,18 @@ func (rc *RunContext) startJobContainer() common.Executor { if err != nil { return fmt.Errorf("failed to handle service %s credentials: %w", serviceId, err) } + serviceBinds, serviceMounts := rc.GetServiceBindsAndMounts(spec.Volumes) serviceContainerName := createSimpleContainerName(rc.jobContainerName(), serviceId) c := container.NewContainer(&container.NewContainerInput{ - Name: serviceContainerName, - WorkingDir: ext.ToContainerPath(rc.Config.Workdir), - Image: spec.Image, - Username: username, - Password: password, - Cmd: interpolatedCmd, - Env: envs, - Mounts: map[string]string{ - // TODO merge volumes - serviceId: ext.ToContainerPath(rc.Config.Workdir), - "act-toolcache": "/toolcache", - "act-actions": "/actions", - }, - Binds: binds, + Name: serviceContainerName, + WorkingDir: ext.ToContainerPath(rc.Config.Workdir), + Image: spec.Image, + Username: username, + Password: password, + Cmd: interpolatedCmd, + Env: envs, + Mounts: serviceMounts, + Binds: serviceBinds, Stdout: logWriter, Stderr: logWriter, Privileged: rc.Config.Privileged, @@ -320,6 +324,7 @@ func (rc *RunContext) startJobContainer() common.Executor { Options: spec.Options, NetworkMode: networkName, NetworkAliases: []string{serviceId}, + ValidVolumes: rc.Config.ValidVolumes, }) rc.ServiceContainers = append(rc.ServiceContainers, c) } @@ -353,6 +358,7 @@ func (rc *RunContext) startJobContainer() common.Executor { Platform: rc.Config.ContainerArchitecture, Options: rc.options(ctx), AutoRemove: rc.Config.AutoRemove, + ValidVolumes: rc.Config.ValidVolumes, }) if rc.JobContainer == nil { return errors.New("Failed to create job container") @@ -1028,3 +1034,30 @@ func (rc *RunContext) handleServiceCredentials(ctx context.Context, creds map[st return } + +// GetServiceBindsAndMounts returns the binds and mounts for the service container, resolving paths as appopriate +func (rc *RunContext) GetServiceBindsAndMounts(svcVolumes []string) ([]string, map[string]string) { + if rc.Config.ContainerDaemonSocket == "" { + rc.Config.ContainerDaemonSocket = "/var/run/docker.sock" + } + binds := []string{} + if rc.Config.ContainerDaemonSocket != "-" { + daemonPath := getDockerDaemonSocketMountPath(rc.Config.ContainerDaemonSocket) + binds = append(binds, fmt.Sprintf("%s:%s", daemonPath, "/var/run/docker.sock")) + } + + mounts := map[string]string{} + + for _, v := range svcVolumes { + if !strings.Contains(v, ":") || filepath.IsAbs(v) { + // Bind anonymous volume or host file. + binds = append(binds, v) + } else { + // Mount existing volume. + paths := strings.SplitN(v, ":", 2) + mounts[paths[0]] = paths[1] + } + } + + return binds, mounts +} diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 000d98c..0ede98a 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -68,6 +68,7 @@ type Config struct { PlatformPicker func(labels []string) string // platform picker, it will take precedence over Platforms if isn't nil JobLoggerLevel *log.Level // the level of job logger Vars map[string]string // the list of variables set at the repository, environment, or organization levels. + ValidVolumes []string // only volumes (and bind mounts) in this slice can be mounted on the job container or service containers } // GetToken: Adapt to Gitea