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.<job_id>.container.volumes](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idcontainervolumes)
- [jobs.<job_id>.services.<service_id>.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 a72822b3f8/pkg/runner/run_context.go (L116-L166)
) will be added to `ValidVolumes`:
- `act-toolcache`
- `<container-name>` and `<container-name>-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 <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act/pulls/60
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>
This commit is contained in:
parent
183bb7af1b
commit
92b4d73376
4 changed files with 96 additions and 14 deletions
|
@ -31,6 +31,7 @@ type NewContainerInput struct {
|
||||||
AutoRemove bool
|
AutoRemove bool
|
||||||
|
|
||||||
NetworkAliases []string
|
NetworkAliases []string
|
||||||
|
ValidVolumes []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileEntry is a file to copy to a container
|
// FileEntry is a file to copy to a container
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/kballard/go-shellquote"
|
"github.com/kballard/go-shellquote"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/compose/loader"
|
||||||
"github.com/docker/cli/cli/connhelper"
|
"github.com/docker/cli/cli/connhelper"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
|
@ -483,6 +484,9 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For Gitea
|
||||||
|
config, hostConfig = cr.sanitizeConfig(ctx, config, hostConfig)
|
||||||
|
|
||||||
// For Gitea
|
// For Gitea
|
||||||
// network-scoped alias is supported only for containers in user defined networks
|
// network-scoped alias is supported only for containers in user defined networks
|
||||||
var networkingConfig *network.NetworkingConfig
|
var networkingConfig *network.NetworkingConfig
|
||||||
|
@ -878,3 +882,46 @@ func (cr *containerReference) wait() common.Executor {
|
||||||
return fmt.Errorf("exit with `FAILURE`: %v", statusCode)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -162,6 +162,14 @@ func (rc *RunContext) GetBindsAndMounts() ([]string, map[string]string) {
|
||||||
mounts[name] = ext.ToContainerPath(rc.Config.Workdir)
|
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
|
return binds, mounts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,22 +303,18 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to handle service %s credentials: %w", serviceId, err)
|
return fmt.Errorf("failed to handle service %s credentials: %w", serviceId, err)
|
||||||
}
|
}
|
||||||
|
serviceBinds, serviceMounts := rc.GetServiceBindsAndMounts(spec.Volumes)
|
||||||
serviceContainerName := createSimpleContainerName(rc.jobContainerName(), serviceId)
|
serviceContainerName := createSimpleContainerName(rc.jobContainerName(), serviceId)
|
||||||
c := container.NewContainer(&container.NewContainerInput{
|
c := container.NewContainer(&container.NewContainerInput{
|
||||||
Name: serviceContainerName,
|
Name: serviceContainerName,
|
||||||
WorkingDir: ext.ToContainerPath(rc.Config.Workdir),
|
WorkingDir: ext.ToContainerPath(rc.Config.Workdir),
|
||||||
Image: spec.Image,
|
Image: spec.Image,
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: password,
|
Password: password,
|
||||||
Cmd: interpolatedCmd,
|
Cmd: interpolatedCmd,
|
||||||
Env: envs,
|
Env: envs,
|
||||||
Mounts: map[string]string{
|
Mounts: serviceMounts,
|
||||||
// TODO merge volumes
|
Binds: serviceBinds,
|
||||||
serviceId: ext.ToContainerPath(rc.Config.Workdir),
|
|
||||||
"act-toolcache": "/toolcache",
|
|
||||||
"act-actions": "/actions",
|
|
||||||
},
|
|
||||||
Binds: binds,
|
|
||||||
Stdout: logWriter,
|
Stdout: logWriter,
|
||||||
Stderr: logWriter,
|
Stderr: logWriter,
|
||||||
Privileged: rc.Config.Privileged,
|
Privileged: rc.Config.Privileged,
|
||||||
|
@ -320,6 +324,7 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
||||||
Options: spec.Options,
|
Options: spec.Options,
|
||||||
NetworkMode: networkName,
|
NetworkMode: networkName,
|
||||||
NetworkAliases: []string{serviceId},
|
NetworkAliases: []string{serviceId},
|
||||||
|
ValidVolumes: rc.Config.ValidVolumes,
|
||||||
})
|
})
|
||||||
rc.ServiceContainers = append(rc.ServiceContainers, c)
|
rc.ServiceContainers = append(rc.ServiceContainers, c)
|
||||||
}
|
}
|
||||||
|
@ -353,6 +358,7 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
||||||
Platform: rc.Config.ContainerArchitecture,
|
Platform: rc.Config.ContainerArchitecture,
|
||||||
Options: rc.options(ctx),
|
Options: rc.options(ctx),
|
||||||
AutoRemove: rc.Config.AutoRemove,
|
AutoRemove: rc.Config.AutoRemove,
|
||||||
|
ValidVolumes: rc.Config.ValidVolumes,
|
||||||
})
|
})
|
||||||
if rc.JobContainer == nil {
|
if rc.JobContainer == nil {
|
||||||
return errors.New("Failed to create job container")
|
return errors.New("Failed to create job container")
|
||||||
|
@ -1028,3 +1034,30 @@ func (rc *RunContext) handleServiceCredentials(ctx context.Context, creds map[st
|
||||||
|
|
||||||
return
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -68,6 +68,7 @@ type Config struct {
|
||||||
PlatformPicker func(labels []string) string // platform picker, it will take precedence over Platforms if isn't nil
|
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
|
JobLoggerLevel *log.Level // the level of job logger
|
||||||
Vars map[string]string // the list of variables set at the repository, environment, or organization levels.
|
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
|
// GetToken: Adapt to Gitea
|
||||||
|
|
Loading…
Reference in a new issue