mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-05 06:45:44 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a8ee4ce57 | ||
|
|
bf03f36d5a | ||
|
|
5a38a3d0c4 | ||
|
|
6618e67645 | ||
|
|
2b599fae7f | ||
|
|
990ebaa2ac |
No files matched your search
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: pre-pull test images
|
||||
env:
|
||||
TEST_JOB_IMAGE: node:24-bookworm-slim@sha256:0e0ff40c39bc087845bfb27465a0df4ea419520094bc35842ff83dd8cbe6f9b6 # renovate: datasource=docker
|
||||
TEST_SERVICE_IMAGE: nginx:alpine@sha256:62ff2089abf5a9ed33bd232895bef5e22f7bb4b200675cec49a5ebc48e3d4ac8 # renovate: datasource=docker
|
||||
TEST_SERVICE_IMAGE: nginx:alpine@sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2 # renovate: datasource=docker
|
||||
run: |
|
||||
for image in "$TEST_JOB_IMAGE" "$TEST_SERVICE_IMAGE"; do
|
||||
for attempt in 1 2 3; do
|
||||
|
||||
@@ -6,7 +6,7 @@ SHASUM ?= shasum -a 256
|
||||
HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/[email protected] # renovate: datasource=go
|
||||
XGO_VERSION := go-1.27.x
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/[email protected]6 # renovate: datasource=go
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/[email protected]7 # renovate: datasource=go
|
||||
|
||||
PODMAN_TEST_IMAGE ?= quay.io/containers/podman:v5.8.4-immutable@sha256:90405e3c6a86f33c5a6f8dbdfa8e02a98143a8ba4337b84bdaec5336d2ef008a # renovate: datasource=docker
|
||||
E2E_JOB_IMAGE ?= node:24-bookworm@sha256:64af3819f9275802414d7cdc38c27e9d82bd564dec4d4da87d008255d36c63b4 # renovate: datasource=docker
|
||||
|
||||
@@ -298,7 +298,7 @@ These hosts are added to `no_proxy` for jobs, so they are always reached directl
|
||||
|
||||
Gitea is not added. Add it to `no_proxy` yourself if it should be reached directly.
|
||||
|
||||
To change a value for one job, set it in a step's `env:` or in the job's `container.env`. Setting it at workflow or job level has no effect. To change it for the whole runner, set it in `runner.envs`. A `no_proxy` set there is added to the list above instead of replacing it.
|
||||
For one step, set it in the step's `env:`, for the rest of a job, write it to `$GITHUB_ENV`. Setting it at workflow or job level or in `container.env` has no effect. To change it for the whole runner, set it in `runner.envs`. A `no_proxy` set there is added to the list above instead of replacing it.
|
||||
|
||||
Images are pulled by the Docker daemon, which needs its own proxy setting. In the `dind` images the daemon runs in the same container and reads the variables above. For any other daemon, see [the Docker documentation](https://docs.docker.com/engine/daemon/proxy/). The runner logs a warning at startup if it has a proxy and the daemon does not.
|
||||
|
||||
|
||||
@@ -593,7 +593,8 @@ func (cr *containerReference) mergeContainerConfigs(ctx context.Context, config
|
||||
|
||||
// If the `privileged` config has been disabled, `copts.privileged` need to be forced to false,
|
||||
// even if the user specifies `--privileged` in the options string.
|
||||
if !hostConfig.Privileged {
|
||||
if !hostConfig.Privileged && copts.privileged {
|
||||
logger.Warnf("container option %q is not allowed when privileged mode is disabled and will be ignored", "--privileged")
|
||||
copts.privileged = false
|
||||
}
|
||||
|
||||
@@ -629,6 +630,7 @@ func (cr *containerReference) mergeContainerConfigs(ctx context.Context, config
|
||||
|
||||
logger.Debugf("Custom container.HostConfig from options ==> %+v", containerConfig.HostConfig)
|
||||
|
||||
cr.sanitizeConfig(ctx, nil, containerConfig.HostConfig) // a dropped volume must not displace the runner's own
|
||||
overlayVolumes(hostConfig, containerConfig.HostConfig)
|
||||
binds := hostConfig.Binds
|
||||
mounts := hostConfig.Mounts
|
||||
@@ -1261,6 +1263,12 @@ func (cr *containerReference) sanitizeConfig(ctx context.Context, config *contai
|
||||
return config, hostConfig
|
||||
}
|
||||
|
||||
func SanitizeBinds(ctx context.Context, validVolumes, binds []string) []string {
|
||||
cr := &containerReference{input: &NewContainerInput{ValidVolumes: validVolumes}}
|
||||
_, hostConfig := cr.sanitizeConfig(ctx, nil, &container.HostConfig{Binds: binds})
|
||||
return hostConfig.Binds
|
||||
}
|
||||
|
||||
// bindTarget returns the container path a bind mounts onto, empty if it cannot be parsed.
|
||||
func bindTarget(bind string) string {
|
||||
parsed, err := loader.ParseVolume(bind)
|
||||
|
||||
@@ -682,6 +682,7 @@ func mergeOptions(t *testing.T, runnerOptions, workflowOptions string, privilege
|
||||
WorkflowOptions: workflowOptions,
|
||||
NetworkMode: "bridge",
|
||||
UsernsMode: "private",
|
||||
ValidVolumes: []string{"**"},
|
||||
}}
|
||||
|
||||
_, hostConfig, err := cr.mergeContainerConfigs(common.WithLogger(context.Background(), logger), &container.Config{}, &container.HostConfig{
|
||||
@@ -844,8 +845,10 @@ func TestMergeContainerConfigsVolumesReplaceRunnerMounts(t *testing.T) {
|
||||
ctx := common.WithLogger(context.Background(), logger)
|
||||
cr := &containerReference{
|
||||
input: &NewContainerInput{
|
||||
NetworkMode: "bridge",
|
||||
RunnerOptions: "--volume /host/tools:/opt/hostedtoolcache",
|
||||
NetworkMode: "bridge",
|
||||
RunnerOptions: "--volume /host/tools:/opt/hostedtoolcache",
|
||||
WorkflowOptions: "--volume /unapproved:/var/run/docker.sock",
|
||||
ValidVolumes: []string{"/host/tools"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -867,6 +870,7 @@ func TestMergeContainerConfigsWarnsOnlyAboutOptionsThatWereGiven(t *testing.T) {
|
||||
assert.Zero(t, warnings("--volume /host/tools:/opt/hostedtoolcache", ""))
|
||||
assert.Zero(t, warnings("", "--shm-size 1g"))
|
||||
assert.Equal(t, 1, warnings("--network host", ""))
|
||||
assert.Equal(t, 1, warnings("", "--privileged"))
|
||||
}
|
||||
|
||||
func TestMergeContainerConfigsKeepsNetworkAliasesFromOptions(t *testing.T) {
|
||||
|
||||
@@ -107,6 +107,10 @@ func RemoveOrphanNetworks(_ context.Context, _, _ string, _ time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func SanitizeBinds(_ context.Context, _, binds []string) []string {
|
||||
return binds
|
||||
}
|
||||
|
||||
func CreateJobVolumes(ctx context.Context, runnerUUID string, volumeNames []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -447,7 +447,7 @@ func newStepContainer(ctx context.Context, step step, image string, cmd, entrypo
|
||||
|
||||
envList = append(envList, rc.runnerEnv(ctx)...)
|
||||
|
||||
binds, mounts := rc.GetBindsAndMounts()
|
||||
binds, mounts := rc.GetBindsAndMounts(ctx)
|
||||
networkMode := "container:" + rc.jobContainerName()
|
||||
if rc.IsHostEnv() {
|
||||
networkMode = "default"
|
||||
|
||||
@@ -248,6 +248,14 @@ func inputsFromEnv(env map[string]string) map[string]any {
|
||||
func getEvaluatorInputs(rc *RunContext, stepInputs map[string]any, ghc *model.GithubContext) map[string]any {
|
||||
inputs := map[string]any{}
|
||||
|
||||
// only a called workflow's job itself takes the inputs Gitea resolved,
|
||||
// composite actions copy its Config but have their own `inputs`
|
||||
if rc.Config.WorkflowCallInputs != nil && rc.Parent == nil {
|
||||
maps.Copy(inputs, rc.Config.WorkflowCallInputs)
|
||||
maps.Copy(inputs, stepInputs)
|
||||
return inputs
|
||||
}
|
||||
|
||||
maps.Copy(inputs, rc.workflowCallInputs)
|
||||
maps.Copy(inputs, stepInputs)
|
||||
|
||||
|
||||
@@ -392,6 +392,28 @@ jobs:
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetEvaluatorInputsResolvedByGitea(t *testing.T) {
|
||||
wf, err := model.ReadWorkflow(strings.NewReader(`
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
name: {type: string, default: callee}
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
name: {type: string}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
config := &Config{Workdir: ".", WorkflowCallInputs: map[string]any{"name": "caller", "target": "dispatch"}}
|
||||
ghc := &model.GithubContext{EventName: "workflow_dispatch", Event: map[string]any{"inputs": map[string]any{"name": "dispatch"}}}
|
||||
|
||||
// the event's own inputs must not override those Gitea resolved
|
||||
job := &RunContext{Config: config, Run: &model.Run{JobID: "job1", Workflow: wf}}
|
||||
assert.Equal(t, map[string]any{"name": "caller", "target": "dispatch"}, getEvaluatorInputs(job, nil, ghc))
|
||||
|
||||
composite := &RunContext{Config: config, Run: &model.Run{JobID: "job1", Workflow: &model.Workflow{}}, Parent: job}
|
||||
assert.Equal(t, map[string]any{"arg": "x"}, getEvaluatorInputs(composite, map[string]any{"arg": "x"}, ghc))
|
||||
}
|
||||
|
||||
func TestResolveWorkflowCallToleratesMismatchesAndPassesInheritedSecretsVerbatim(t *testing.T) {
|
||||
callerWorkflow, err := model.ReadWorkflow(strings.NewReader(`
|
||||
jobs:
|
||||
|
||||
@@ -435,11 +435,7 @@ func evaluateJobEnvAndDefaults(ctx context.Context, rc *RunContext) error {
|
||||
return err
|
||||
}
|
||||
rc.jobRunDefaults = defaults.Run
|
||||
if rc.containerSpec.Image == "" {
|
||||
return nil
|
||||
}
|
||||
_, containerEnv := splitContainerEnv(rc.Run.Job().RawContainer)
|
||||
return model.DecodeEvaluated("container env", containerEnv, rc.ExprEval.shared(ctx).EvaluateYamlNode, &rc.containerSpec.Env)
|
||||
return model.DecodeEvaluated("container env", rc.containerEnv, rc.ExprEval.shared(ctx).EvaluateYamlNode, &rc.containerSpec.Env)
|
||||
}
|
||||
|
||||
func setJobOutputs(ctx context.Context, rc *RunContext) error {
|
||||
|
||||
+64
-16
@@ -64,7 +64,8 @@ type RunContext struct {
|
||||
ExprEval *expressionEvaluator
|
||||
JobContainer container.ExecutionsEnvironment
|
||||
serviceContainers []*serviceContainer
|
||||
containerSpec model.ContainerSpec // container:, resolved once with platformImage, zero without a job container
|
||||
containerSpec model.ContainerSpec // container:, resolved once with platformImage
|
||||
containerEnv yaml.Node // container.env of the kept containerSpec, decoded once the job env resolves
|
||||
JobName string
|
||||
ActionPath string
|
||||
Parent *RunContext
|
||||
@@ -113,6 +114,7 @@ type serviceContainer struct {
|
||||
name string
|
||||
image string
|
||||
container container.ExecutionsEnvironment
|
||||
created bool
|
||||
logsDumped bool
|
||||
info *container.Info // last poll, the source of the `job.services` entry
|
||||
}
|
||||
@@ -334,12 +336,12 @@ func splitVolumes(specs []string) ([]string, map[string]string, map[string]bool)
|
||||
}
|
||||
|
||||
// Returns the binds and mounts for the container, resolving paths as appopriate
|
||||
func (rc *RunContext) GetBindsAndMounts() ([]string, map[string]string) {
|
||||
func (rc *RunContext) GetBindsAndMounts(ctx context.Context) ([]string, map[string]string) {
|
||||
name := rc.jobContainerName()
|
||||
ext := container.LinuxContainerEnvironmentExtensions{}
|
||||
|
||||
// the runner's own mounts below yield to the targets the job claims
|
||||
binds, mounts, claimed := splitVolumes(rc.containerSpec.Volumes)
|
||||
binds, mounts, claimed := splitVolumes(container.SanitizeBinds(ctx, rc.validVolumes(), rc.containerSpec.Volumes))
|
||||
|
||||
if rc.containerDaemonSocket() != "-" && !claimed["/var/run/docker.sock"] {
|
||||
binds = append(binds, rc.jobDockerSocket()+":/var/run/docker.sock")
|
||||
@@ -497,10 +499,12 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
||||
if err := model.DecodeEvaluated("job services", rc.Run.Job().RawServices, rc.ExprEval.shared(ctx).EvaluateYamlNode, &services); err != nil {
|
||||
return err
|
||||
}
|
||||
for serviceID, spec := range services {
|
||||
for _, serviceID := range slices.Sorted(maps0.Keys(services)) {
|
||||
spec := services[serviceID]
|
||||
if spec == nil {
|
||||
return fmt.Errorf("service %s has no container definition", serviceID)
|
||||
}
|
||||
spec.Image = strings.TrimPrefix(spec.Image, "docker://")
|
||||
// GitHub compatibility: skip services whose image evaluates to an
|
||||
// empty string, enabling conditional services via expressions
|
||||
if spec.Image == "" {
|
||||
@@ -537,6 +541,7 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
||||
}
|
||||
|
||||
serviceContainerName := createContainerName(rc.jobContainerName(), serviceID)
|
||||
serviceLog := common.NewLineWriter(rawLogLine(ctx)) // raw, so a service's output cannot set the job's env, outputs or state
|
||||
c := newContainer(&container.NewContainerInput{
|
||||
Name: serviceContainerName,
|
||||
Image: spec.Image,
|
||||
@@ -547,8 +552,8 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
||||
Env: envs,
|
||||
Mounts: serviceMounts,
|
||||
Binds: serviceBinds,
|
||||
Stdout: logWriter,
|
||||
Stderr: logWriter,
|
||||
Stdout: serviceLog,
|
||||
Stderr: serviceLog,
|
||||
Privileged: rc.Config.Privileged,
|
||||
UsernsMode: rc.Config.UsernsMode,
|
||||
Platform: rc.Config.ContainerArchitecture,
|
||||
@@ -570,7 +575,7 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
||||
ext := container.LinuxContainerEnvironmentExtensions{}
|
||||
containerInput := &container.NewContainerInput{
|
||||
Cmd: nil,
|
||||
Entrypoint: []string{"/bin/sleep", fmt.Sprint(rc.Config.ContainerMaxLifetime.Round(time.Second).Seconds())},
|
||||
Entrypoint: []string{"sleep", fmt.Sprint(rc.Config.ContainerMaxLifetime.Round(time.Second).Seconds())},
|
||||
WorkingDir: ext.ToContainerPath(rc.Config.Workdir),
|
||||
Image: image,
|
||||
Username: username,
|
||||
@@ -602,7 +607,7 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
||||
return err
|
||||
}
|
||||
rc.startDockerProxy(ctx)
|
||||
containerInput.Binds, containerInput.Mounts = rc.GetBindsAndMounts()
|
||||
containerInput.Binds, containerInput.Mounts = rc.GetBindsAndMounts(ctx)
|
||||
containerInput.ValidVolumes = rc.validVolumes()
|
||||
|
||||
rc.jobNetworkName = networkName
|
||||
@@ -633,16 +638,21 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
||||
}
|
||||
|
||||
func (rc *RunContext) commandLogWriter(ctx context.Context) io.Writer {
|
||||
return common.NewLineWriter(rc.commandHandler(ctx), rawLogLine(ctx))
|
||||
}
|
||||
|
||||
func rawLogLine(ctx context.Context) common.LineHandler {
|
||||
rawLogger := common.Logger(ctx).WithField(rawOutputField, true)
|
||||
return common.NewLineWriter(rc.commandHandler(ctx), func(line string) bool {
|
||||
return func(line string) bool {
|
||||
rawLogger.Infof("%s", line)
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *RunContext) cleanupJobResources(networkName string, createAndDeleteNetwork, preclean bool) common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
logger := common.Logger(ctx)
|
||||
rc.printServiceLogs(ctx)
|
||||
errs := []error{rc.closeDockerProxy(ctx)}
|
||||
if rc.JobContainer != nil {
|
||||
errs = append(errs, rc.JobContainer.Remove()(ctx))
|
||||
@@ -789,6 +799,7 @@ func (rc *RunContext) startServiceContainers() common.Executor {
|
||||
for _, svc := range rc.serviceContainers {
|
||||
execs = append(execs, common.NewPipelineExecutor(
|
||||
svc.container.Create(rc.Config.ContainerCapAdd, rc.Config.ContainerCapDrop),
|
||||
func(context.Context) error { svc.created = true; return nil },
|
||||
svc.container.Start(false),
|
||||
))
|
||||
}
|
||||
@@ -927,6 +938,26 @@ func (svc *serviceContainer) dumpLogs(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
const serviceLogsTimeout = 15 * time.Second // cleanup gets one minute in total
|
||||
|
||||
func (rc *RunContext) printServiceLogs(ctx context.Context) {
|
||||
ctx, cancel := context.WithTimeout(ctx, serviceLogsTimeout)
|
||||
defer cancel()
|
||||
rawLogger := common.Logger(ctx).WithField(rawOutputField, true)
|
||||
for _, svc := range rc.serviceContainers {
|
||||
if !svc.created || svc.logsDumped {
|
||||
continue
|
||||
}
|
||||
rawLogger.Infof("::group::Print service container logs: %s", EscapeCommandData(svc.name))
|
||||
svc.dumpLogs(ctx)
|
||||
rawLogger.Infof("::endgroup::")
|
||||
if ctx.Err() != nil {
|
||||
common.Logger(ctx).Warnf("Timed out printing the service logs after %s", serviceLogsTimeout)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// inspect also records the state for the `job.services` context.
|
||||
func (svc *serviceContainer) inspect(ctx context.Context) (*container.Info, error) {
|
||||
info, err := svc.container.Inspect(ctx)
|
||||
@@ -1039,7 +1070,7 @@ func (rc *RunContext) cleanupFailedStart(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (rc *RunContext) IsHostEnv() bool {
|
||||
return strings.EqualFold(rc.platformImage, "-self-hosted")
|
||||
return rc.containerSpec.Image == "" && strings.EqualFold(rc.platformImage, "-self-hosted")
|
||||
}
|
||||
|
||||
func (rc *RunContext) stopContainer() common.Executor {
|
||||
@@ -1159,15 +1190,19 @@ func (rc *RunContext) runsOnPlatformNames(ctx context.Context) []string {
|
||||
return model.RunsOnFromNode(rawRunsOn)
|
||||
}
|
||||
|
||||
// resolvePlatformImage evaluates the job's container once for every consumer, ignoring one without an image as GitHub does.
|
||||
// resolvePlatformImage evaluates the job's container once for every consumer, skipping one whose image is empty as GitHub does.
|
||||
func (rc *RunContext) resolvePlatformImage(ctx context.Context) error {
|
||||
withoutEnv, _ := splitContainerEnv(rc.Run.Job().RawContainer)
|
||||
var spec *model.ContainerSpec
|
||||
withoutEnv, env := splitContainerEnv(rc.Run.Job().RawContainer)
|
||||
var spec model.ContainerSpec
|
||||
if err := model.DecodeEvaluated("container", withoutEnv, rc.ExprEval.shared(ctx).EvaluateYamlNode, &spec); err != nil {
|
||||
return err
|
||||
}
|
||||
if spec != nil && spec.Image != "" {
|
||||
rc.containerSpec, rc.platformImage = *spec, spec.Image
|
||||
spec.Image = strings.TrimPrefix(spec.Image, "docker://")
|
||||
if spec.Image != "" || imagelessContainer(withoutEnv) {
|
||||
rc.containerSpec, rc.containerEnv = spec, env
|
||||
}
|
||||
if rc.containerSpec.Image != "" {
|
||||
rc.platformImage = rc.containerSpec.Image
|
||||
return nil
|
||||
}
|
||||
image, err := rc.runsOnImage(ctx)
|
||||
@@ -1175,6 +1210,19 @@ func (rc *RunContext) resolvePlatformImage(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// imagelessContainer reports a container: mapping without an image key, which configures the runs-on image.
|
||||
func imagelessContainer(container yaml.Node) bool {
|
||||
if container.Kind != yaml.MappingNode {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(container.Content); i += 2 {
|
||||
if container.Content[i].Value == "image" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// splitContainerEnv takes `env` out of a container mapping, since env reads the job env that resolves after the job environment starts.
|
||||
func splitContainerEnv(container yaml.Node) (withoutEnv, env yaml.Node) {
|
||||
if container.Kind != yaml.MappingNode {
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/docker/cli/cli/compose/loader"
|
||||
"github.com/moby/moby/api/types/volume"
|
||||
log "github.com/sirupsen/logrus"
|
||||
logrustest "github.com/sirupsen/logrus/hooks/test"
|
||||
assert "github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
require "github.com/stretchr/testify/require"
|
||||
@@ -342,12 +343,12 @@ jobs:
|
||||
require.Nil(t, redis.Cmd)
|
||||
}
|
||||
|
||||
func TestStartJobContainerEvaluatesContainersOnce(t *testing.T) {
|
||||
func TestStartJobContainerBuildsEvaluatedJobAndServiceInputs(t *testing.T) {
|
||||
inputs := startJobContainerInputs(t, `
|
||||
jobs:
|
||||
job:
|
||||
container: ${{ fromJSON('{"image":"node:20","options":"--label ${{ github.job }}","credentials":{"username":"user","password":"${{ vars.TITLE }}"}}') }}
|
||||
services: ${{ fromJSON('{"redis":{"image":"redis:latest","volumes":["data:/data"],"env":{"TITLE":"${{ github.job }}"},"credentials":{"username":"user","password":"${{ github.job }}"},"entrypoint":"/entry.sh","command":"redis-server --port 6380"}}') }}
|
||||
container: ${{ fromJSON('{"image":"docker://node:20","options":"--label ${{ github.job }}","credentials":{"username":"user","password":"${{ vars.TITLE }}"}}') }}
|
||||
services: ${{ fromJSON('{"redis":{"image":"docker://redis:latest","volumes":["data:/data"],"env":{"TITLE":"${{ github.job }}"},"credentials":{"username":"user","password":"${{ github.job }}"},"entrypoint":"/entry.sh","command":"redis-server --port 6380"}}') }}
|
||||
`, &Config{})
|
||||
redis, job := inputs[0], inputs[1]
|
||||
|
||||
@@ -358,6 +359,7 @@ jobs:
|
||||
require.Equal(t, []string{"/entry.sh"}, redis.Entrypoint)
|
||||
require.Equal(t, []string{"redis-server", "--port", "6380"}, redis.Cmd)
|
||||
require.Equal(t, "node:20", job.Image)
|
||||
require.Equal(t, "sleep", job.Entrypoint[0])
|
||||
require.Equal(t, "--label ${{ github.job }}", job.WorkflowOptions)
|
||||
require.Equal(t, "${{ vars.TITLE }}", job.Password)
|
||||
}
|
||||
@@ -412,17 +414,14 @@ jobs:
|
||||
steps: []
|
||||
`, &Config{ProxyEnv: map[string]string{"http_proxy": "http://proxy:3128", "no_proxy": "internal.example"}})
|
||||
|
||||
env := map[string][]string{}
|
||||
for _, in := range inputs {
|
||||
env[in.Image] = in.Env
|
||||
}
|
||||
db, redis := inputs[0], inputs[1]
|
||||
|
||||
require.Contains(t, env["redis:latest"], "http_proxy=http://proxy:3128")
|
||||
require.Contains(t, env["redis:latest"], "no_proxy=internal.example")
|
||||
require.Contains(t, redis.Env, "http_proxy=http://proxy:3128")
|
||||
require.Contains(t, redis.Env, "no_proxy=internal.example")
|
||||
// the service's own env wins over what the runner injected, without dropping the rest
|
||||
require.Contains(t, env["postgres:latest"], "no_proxy=db-only.example")
|
||||
require.NotContains(t, env["postgres:latest"], "no_proxy=internal.example")
|
||||
require.Contains(t, env["postgres:latest"], "http_proxy=http://proxy:3128")
|
||||
require.Contains(t, db.Env, "no_proxy=db-only.example")
|
||||
require.NotContains(t, db.Env, "no_proxy=internal.example")
|
||||
require.Contains(t, db.Env, "http_proxy=http://proxy:3128")
|
||||
}
|
||||
|
||||
// act builds Dockerfile actions through the API, which does not pre-populate the proxy
|
||||
@@ -483,7 +482,7 @@ func TestRunContext_GetBindsAndMounts(t *testing.T) {
|
||||
config := testcase.rc.Config
|
||||
config.Workdir = testcase.name
|
||||
config.BindWorkdir = bindWorkDir
|
||||
gotbind, gotmount := rctemplate.GetBindsAndMounts()
|
||||
gotbind, gotmount := rctemplate.GetBindsAndMounts(t.Context())
|
||||
|
||||
// Name binds/mounts are either/or
|
||||
if config.BindWorkdir {
|
||||
@@ -533,7 +532,8 @@ func TestRunContext_GetBindsAndMounts(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Config: &Config{
|
||||
BindWorkdir: false,
|
||||
BindWorkdir: false,
|
||||
ValidVolumes: []string{"**"},
|
||||
Secrets: map[string]string{
|
||||
"MAME": "/host/mame/roms",
|
||||
},
|
||||
@@ -544,7 +544,7 @@ func TestRunContext_GetBindsAndMounts(t *testing.T) {
|
||||
rc.ExprEval = rc.NewExpressionEvaluator(context.Background())
|
||||
require.NoError(t, rc.resolvePlatformImage(context.Background()))
|
||||
|
||||
gotbind, gotmount := rc.GetBindsAndMounts()
|
||||
gotbind, gotmount := rc.GetBindsAndMounts(t.Context())
|
||||
assert.Contains(t, gotbind, "/host/mame/roms:/root/.mame/roms:ro")
|
||||
assert.NotContains(t, gotbind, "${{ secrets.MAME }}")
|
||||
assert.NotContains(t, gotmount, "${{ secrets.MAME }}")
|
||||
@@ -562,12 +562,13 @@ func TestRunContext_GetBindsAndMounts(t *testing.T) {
|
||||
Config: &Config{
|
||||
BindWorkdir: false,
|
||||
SharedToolCache: true, // so OverridesToolCache has a mount to displace
|
||||
ValidVolumes: []string{"**"},
|
||||
},
|
||||
containerSpec: model.ContainerSpec{Volumes: testcase.volumes},
|
||||
}
|
||||
rc.Run.JobID = "job1"
|
||||
|
||||
gotbind, gotmount := rc.GetBindsAndMounts()
|
||||
gotbind, gotmount := rc.GetBindsAndMounts(t.Context())
|
||||
|
||||
if len(testcase.wantbind) > 0 {
|
||||
assert.Contains(t, gotbind, testcase.wantbind)
|
||||
@@ -602,11 +603,12 @@ func TestRunContext_GetBindsAndMounts(t *testing.T) {
|
||||
Config: &Config{},
|
||||
}
|
||||
|
||||
_, gotmount := rc.GetBindsAndMounts()
|
||||
_, gotmount := rc.GetBindsAndMounts(t.Context())
|
||||
assert.NotContains(t, gotmount, sharedToolCacheVolume)
|
||||
|
||||
rc.Config.SharedToolCache = true
|
||||
_, gotmount = rc.GetBindsAndMounts()
|
||||
rc.containerSpec.Volumes = []string{"/unapproved:" + container.DefaultToolCache}
|
||||
_, gotmount = rc.GetBindsAndMounts(t.Context())
|
||||
assert.Equal(t, container.DefaultToolCache, gotmount[sharedToolCacheVolume])
|
||||
})
|
||||
|
||||
@@ -616,18 +618,18 @@ func TestRunContext_GetBindsAndMounts(t *testing.T) {
|
||||
}
|
||||
rc := &RunContext{
|
||||
Run: &model.Run{JobID: "job1", Workflow: &model.Workflow{Jobs: map[string]*model.Job{"job1": {}}}},
|
||||
Config: &Config{BindWorkdir: true, Workdir: "/workspace/1/owner/repo", PresetGitHubContext: &model.GithubContext{}},
|
||||
Config: &Config{BindWorkdir: true, Workdir: "/workspace/1/owner/repo", PresetGitHubContext: &model.GithubContext{}, ValidVolumes: []string{"claimed"}},
|
||||
}
|
||||
|
||||
gotbind, _ := rc.GetBindsAndMounts()
|
||||
gotbind, _ := rc.GetBindsAndMounts(t.Context())
|
||||
assert.True(t, slices.ContainsFunc(gotbind, func(bind string) bool { return strings.HasPrefix(bind, "/workspace/1:/workspace/1") }), gotbind)
|
||||
|
||||
rc.Config.BindWorkdir = false
|
||||
_, gotmount := rc.GetBindsAndMounts()
|
||||
_, gotmount := rc.GetBindsAndMounts(t.Context())
|
||||
assert.Equal(t, "/workspace/1", gotmount[rc.jobContainerName()])
|
||||
|
||||
rc.containerSpec.Volumes = []string{"claimed:/workspace/1"}
|
||||
_, gotmount = rc.GetBindsAndMounts()
|
||||
_, gotmount = rc.GetBindsAndMounts(t.Context())
|
||||
assert.Equal(t, "/workspace/1/owner/repo", gotmount[rc.jobContainerName()])
|
||||
})
|
||||
}
|
||||
@@ -1272,12 +1274,13 @@ func TestRunContext_cleanupFailedStart(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, testcase := range []struct {
|
||||
name, health string
|
||||
pullError error
|
||||
name, health string
|
||||
pullError, startError error
|
||||
}{
|
||||
{name: "healthy", health: container.HealthHealthy},
|
||||
{name: "unhealthy", health: container.HealthUnhealthy},
|
||||
{name: "pull failure", pullError: errors.New("pull failed")},
|
||||
{name: "start failure", startError: errors.New("start failed")},
|
||||
} {
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
var operations []string
|
||||
@@ -1291,7 +1294,7 @@ func TestRunContext_cleanupFailedStart(t *testing.T) {
|
||||
for name, instance := range map[string]*containerMock{"job": job, "service": service} {
|
||||
instance.On("Pull", true).Return(record(name+".Pull", map[string]error{"job": testcase.pullError}[name]))
|
||||
instance.On("Create", mock.Anything, mock.Anything).Return(record(name+".Create", nil))
|
||||
instance.On("Start", false).Return(record(name+".Start", nil))
|
||||
instance.On("Start", false).Return(record(name+".Start", map[string]error{"service": testcase.startError}[name]))
|
||||
instance.On("Remove").Return(record(name+".Remove", nil))
|
||||
instance.On("Close").Return(record(name+".Close", nil))
|
||||
}
|
||||
@@ -1302,10 +1305,14 @@ func TestRunContext_cleanupFailedStart(t *testing.T) {
|
||||
service.On("Inspect", mock.Anything).Return(&container.Info{State: "running", Health: health}, nil).
|
||||
Run(func(mock.Arguments) { operations = append(operations, "service.Inspect") }).Once()
|
||||
}
|
||||
service.On("DumpLogs", mock.Anything).Return(nil).
|
||||
Run(func(mock.Arguments) { operations = append(operations, "service.DumpLogs") })
|
||||
stdouts := map[string]io.Writer{}
|
||||
service.On("DumpLogs", mock.Anything).Return(nil).Run(func(mock.Arguments) {
|
||||
operations = append(operations, "service.DumpLogs")
|
||||
fmt.Fprintln(stdouts["postgres:latest"], "::add-mask::leak")
|
||||
})
|
||||
origNewContainer := newContainer
|
||||
newContainer = func(input *container.NewContainerInput) container.ExecutionsEnvironment {
|
||||
stdouts[input.Image] = input.Stdout
|
||||
return map[string]*containerMock{"postgres:latest": service, "node:20": job}[input.Image]
|
||||
}
|
||||
t.Cleanup(func() { newContainer = origNewContainer })
|
||||
@@ -1316,22 +1323,30 @@ func TestRunContext_cleanupFailedStart(t *testing.T) {
|
||||
Run: &model.Run{JobID: "job", Workflow: workflow},
|
||||
platformImage: "node:20",
|
||||
}
|
||||
ctx := common.WithDryrun(t.Context(), true)
|
||||
logger, hook := logrustest.NewNullLogger()
|
||||
ctx := common.WithLogger(common.WithDryrun(t.Context(), true), logger)
|
||||
rc.ExprEval = rc.NewExpressionEvaluator(ctx)
|
||||
err = rc.startContainer().Then(rc.stopContainer()).Finally(rc.closeContainer())(ctx)
|
||||
want := "job.Remove service.Remove service.Close service.Pull job.Pull"
|
||||
if testcase.pullError != nil {
|
||||
switch {
|
||||
case testcase.pullError != nil:
|
||||
require.ErrorIs(t, err, testcase.pullError)
|
||||
} else {
|
||||
want += " service.Create service.Start service.Inspect job.Create job.Start job.Inspect job.Copy service.Inspect"
|
||||
case testcase.startError != nil:
|
||||
require.ErrorIs(t, err, testcase.startError)
|
||||
want += " service.Create service.Start service.DumpLogs"
|
||||
default:
|
||||
want += " service.Create service.Start service.Inspect job.Create job.Start job.Inspect job.Copy service.Inspect service.DumpLogs"
|
||||
if testcase.health == container.HealthUnhealthy {
|
||||
require.ErrorContains(t, err, "the service 'postgres' is unhealthy")
|
||||
want += " service.DumpLogs"
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, strings.Fields(want+" job.Remove service.Remove service.Close job.Close"), operations)
|
||||
assert.Empty(t, rc.Masks)
|
||||
assert.Equal(t, testcase.pullError == nil && testcase.health != container.HealthUnhealthy, slices.ContainsFunc(hook.AllEntries(), func(entry *log.Entry) bool {
|
||||
return strings.HasPrefix(entry.Message, "::group::Print service container logs")
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1587,12 +1602,39 @@ func TestImageOSFromImage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePlatformImageAppliesImagelessContainerToRunsOnImage(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name, image string
|
||||
want model.ContainerSpec
|
||||
}{
|
||||
{"without image key", "", model.ContainerSpec{Volumes: []string{"/host/data:/data"}, Env: map[string]string{"KEY": "value"}}},
|
||||
{"with empty image", "image: '', ", model.ContainerSpec{}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rc := createIfTestRunContext(map[string]*model.Job{
|
||||
"job1": createJob(t, "runs-on: toolkit\ncontainer: {"+tc.image+"volumes: [/host/data:/data], env: {KEY: value}}", ""),
|
||||
})
|
||||
require.NoError(t, rc.resolvePlatformImage(t.Context()))
|
||||
require.NoError(t, evaluateJobEnvAndDefaults(t.Context(), rc))
|
||||
|
||||
assert.Equal(t, "ubuntu-latest", rc.platformImage)
|
||||
assert.Equal(t, tc.want, rc.containerSpec)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createRunsOnRunContext(t *testing.T, runsOn string) *RunContext {
|
||||
return createIfTestRunContext(map[string]*model.Job{
|
||||
"job1": createJob(t, "runs-on: "+runsOn, ""),
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunContextContainerImageCannotSelectHost(t *testing.T) {
|
||||
rc := createIfTestRunContext(map[string]*model.Job{"job1": createJob(t, "container: -self-hosted", "")})
|
||||
require.NoError(t, rc.resolvePlatformImage(t.Context()))
|
||||
assert.False(t, rc.IsHostEnv())
|
||||
}
|
||||
|
||||
func TestRunContextImageOS(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ type Config struct {
|
||||
NoActionPatch bool // run actions exactly as published, applying no compatibility patches, see patch_actions.go
|
||||
|
||||
PresetGitHubContext *model.GithubContext // overrides actor, ref, repository, token and related context fields
|
||||
WorkflowCallInputs map[string]any // the `inputs` Gitea resolved for a called workflow's job, nil for other jobs
|
||||
EventJSON string // the content of JSON file to use for event.json in containers, overrides EventPath
|
||||
ContainerNamePrefix string // the prefix of container name
|
||||
ContainerMaxLifetime time.Duration // the max lifetime of job containers
|
||||
|
||||
@@ -69,6 +69,7 @@ func testPayloads(t *testing.T) {
|
||||
"cell-a-2",
|
||||
"cell-b-1",
|
||||
"cell-b-2",
|
||||
"imageless env=imageless-env-value host=imageless-host",
|
||||
} {
|
||||
if !strings.Contains(logs, want) {
|
||||
t.Errorf("stored logs are missing %q", want)
|
||||
|
||||
Vendored
+5
@@ -3,6 +3,10 @@ on: push
|
||||
jobs:
|
||||
verify:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
env:
|
||||
IMAGELESS_ENV: imageless-env-value
|
||||
options: --hostname imageless-host
|
||||
services:
|
||||
web:
|
||||
image: ${{ vars.E2E_SERVICE_IMAGE }}
|
||||
@@ -19,6 +23,7 @@ jobs:
|
||||
echo '::notice::encoded-first%0Aencoded-second'
|
||||
echo "the variable is ${{ vars.GREETING }}"
|
||||
echo "the secret is ${{ secrets.FOO }}"
|
||||
echo "imageless env=$IMAGELESS_ENV host=$(hostname)"
|
||||
curl --connect-timeout 1 --max-time 2 --retry 30 --retry-delay 1 --retry-max-time 30 --retry-all-errors -fsS -o /dev/null http://web/
|
||||
produce:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -9,7 +9,7 @@ require (
|
||||
dario.cat/mergo v1.0.2
|
||||
gitea.dev/actionslib v1.2.1
|
||||
github.com/avast/retry-go/v5 v5.0.0
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.2
|
||||
github.com/containerd/errdefs v1.0.0
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/distribution/reference v0.6.0
|
||||
|
||||
@@ -47,8 +47,8 @@ github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
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/bmatcuk/doublestar/v4 v4.10.2 h1:eF7W7HWKg3z9NrWV9pTLnNeoXaqq3Tq9DNKXVMfoCnw=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
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/cevatbarisyilmaz/ara v0.0.4 h1:SGH10hXpBJhhTlObuZzTuFn1rrdmjQImITXnZVPSodc=
|
||||
|
||||
@@ -27,6 +27,19 @@ func loadConfigCmd(configFile *string) *cobra.Command {
|
||||
|
||||
configCmd.AddCommand(loadGenerateConfigCmd("generate"))
|
||||
configCmd.AddCommand(loadInitConfigCmd(configFile))
|
||||
configCmd.AddCommand(&cobra.Command{
|
||||
Use: "runner-file",
|
||||
Short: "Print the registration file path used by the runner",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
cfg, err := config.LoadDefault(*configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(cmd.OutOrStdout(), cfg.Runner.File)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
configCmd.AddCommand(&cobra.Command{
|
||||
Use: "get <key>",
|
||||
|
||||
@@ -97,3 +97,14 @@ func TestConfigCmdResolvesTheConfigFile(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), "--config")
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigCmdRunnerFileUsesConfiguredPathOrDefault(t *testing.T) {
|
||||
out, _, err := runConfigCmd(t, "", "runner-file")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ".runner\n", out)
|
||||
file := filepath.Join(t.TempDir(), "config.yaml")
|
||||
require.NoError(t, os.WriteFile(file, []byte("runner:\n file: 'state dir/registration.json'\n"), 0o600))
|
||||
out, _, err = runConfigCmd(t, file, "runner-file")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "state dir/registration.json\n", out)
|
||||
}
|
||||
@@ -350,6 +350,25 @@ func (r *Runner) cloneEnvs() map[string]string {
|
||||
return envs
|
||||
}
|
||||
|
||||
// restoreCalledWorkflowContext undoes Gitea's rewrite of `event_name` and `event.inputs` for older runners,
|
||||
// and returns the job's `inputs` from `gitea_workflow_call`, nil if it is not a called workflow's job.
|
||||
// See https://github.com/go-gitea/gitea/pull/39452
|
||||
func restoreCalledWorkflowContext(task *runnerv1.Task) map[string]any {
|
||||
workflowCall := task.GetContext().GetFields()["gitea_workflow_call"].GetStructValue().GetFields()
|
||||
if workflowCall == nil {
|
||||
return nil
|
||||
}
|
||||
task.Context.Fields["event_name"] = workflowCall["original_event_name"]
|
||||
if event := task.Context.Fields["event"].GetStructValue(); event != nil {
|
||||
if inputs, ok := workflowCall["original_event_inputs"]; ok {
|
||||
event.Fields["inputs"] = inputs
|
||||
} else {
|
||||
delete(event.Fields, "inputs")
|
||||
}
|
||||
}
|
||||
return workflowCall["inputs"].GetStructValue().AsMap()
|
||||
}
|
||||
|
||||
// getDefaultActionsURL
|
||||
// when DEFAULT_ACTIONS_URL == "https://github.com" and GithubMirror is not blank,
|
||||
// it should be set to GithubMirror first.
|
||||
@@ -398,6 +417,7 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
||||
}
|
||||
}()
|
||||
|
||||
workflowCallInputs := restoreCalledWorkflowContext(task)
|
||||
r.reportSetup(reporter, task)
|
||||
|
||||
workflow, jobID, err := generateWorkflow(task)
|
||||
@@ -536,6 +556,7 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
||||
NoSkipCheckout: true,
|
||||
DisableActEnv: r.cfg.Runner.SetActEnv != nil && !*r.cfg.Runner.SetActEnv,
|
||||
PresetGitHubContext: preset,
|
||||
WorkflowCallInputs: workflowCallInputs,
|
||||
EventJSON: string(eventJSON),
|
||||
ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id),
|
||||
ContainerMaxLifetime: maxLifetime,
|
||||
|
||||
@@ -358,3 +358,48 @@ func TestNewRunnerCacheServiceV2(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestoreCalledWorkflowContext(t *testing.T) {
|
||||
newTask := func(workflowCall map[string]any) *runnerv1.Task {
|
||||
values := map[string]any{
|
||||
"event_name": "workflow_call",
|
||||
"event": map[string]any{"ref": "refs/heads/main", "inputs": map[string]any{"env": "leaf"}},
|
||||
}
|
||||
if workflowCall != nil {
|
||||
values["gitea_workflow_call"] = workflowCall
|
||||
}
|
||||
taskCtx, err := structpb.NewStruct(values)
|
||||
require.NoError(t, err)
|
||||
return &runnerv1.Task{Context: taskCtx}
|
||||
}
|
||||
|
||||
t.Run("dispatched run", func(t *testing.T) {
|
||||
task := newTask(map[string]any{
|
||||
"original_event_name": "workflow_dispatch",
|
||||
"original_event_inputs": map[string]any{"target": "prod"},
|
||||
"inputs": map[string]any{"target": "prod", "env": "leaf"},
|
||||
})
|
||||
assert.Equal(t, map[string]any{"target": "prod", "env": "leaf"}, restoreCalledWorkflowContext(task))
|
||||
taskCtx := task.Context.AsMap()
|
||||
assert.Equal(t, "workflow_dispatch", taskCtx["event_name"])
|
||||
assert.Equal(t, map[string]any{"ref": "refs/heads/main", "inputs": map[string]any{"target": "prod"}}, taskCtx["event"])
|
||||
assert.Equal(t, []string{"::group::Inputs", "env: leaf", "target: prod", "::endgroup::"}, inputLines(task.Context.Fields))
|
||||
})
|
||||
|
||||
t.Run("event without inputs", func(t *testing.T) {
|
||||
task := newTask(map[string]any{
|
||||
"original_event_name": "push",
|
||||
"inputs": map[string]any{"env": "leaf"},
|
||||
})
|
||||
assert.Equal(t, map[string]any{"env": "leaf"}, restoreCalledWorkflowContext(task))
|
||||
taskCtx := task.Context.AsMap()
|
||||
assert.Equal(t, "push", taskCtx["event_name"])
|
||||
assert.Equal(t, map[string]any{"ref": "refs/heads/main"}, taskCtx["event"])
|
||||
})
|
||||
|
||||
t.Run("not a called workflow's job", func(t *testing.T) {
|
||||
task := newTask(nil)
|
||||
assert.Nil(t, restoreCalledWorkflowContext(task))
|
||||
assert.Equal(t, "workflow_call", task.Context.AsMap()["event_name"])
|
||||
})
|
||||
}
|
||||
@@ -56,10 +56,15 @@ func (r *Runner) setupLines(task *runnerv1.Task) []string {
|
||||
return append(lines, "::endgroup::")
|
||||
}
|
||||
|
||||
// inputLines lists the inputs the run was triggered with, as workflow_dispatch and workflow_call
|
||||
// carry them in the event payload. Empty when the event has none.
|
||||
// inputLines lists the inputs of the job: those Gitea resolved for a called workflow's job,
|
||||
// or the ones a workflow_dispatch event carries in its payload. Empty when there are none.
|
||||
func inputLines(fields map[string]*structpb.Value) []string {
|
||||
inputs := fields["event"].GetStructValue().GetFields()["inputs"].GetStructValue().GetFields()
|
||||
// For a called workflow's job, restore its inputs from `gitea_workflow_call`.
|
||||
// See https://github.com/go-gitea/gitea/pull/39452.
|
||||
if workflowCall := fields["gitea_workflow_call"].GetStructValue(); workflowCall != nil {
|
||||
inputs = workflowCall.GetFields()["inputs"].GetStructValue().GetFields()
|
||||
}
|
||||
if len(inputs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
+1
-2
@@ -6,12 +6,11 @@ fi
|
||||
|
||||
cd /data
|
||||
|
||||
RUNNER_STATE_FILE=${RUNNER_STATE_FILE:-'.runner'}
|
||||
|
||||
CONFIG_ARG=""
|
||||
if [[ ! -z "${CONFIG_FILE}" ]]; then
|
||||
CONFIG_ARG="--config ${CONFIG_FILE}"
|
||||
fi
|
||||
RUNNER_STATE_FILE=${RUNNER_STATE_FILE:-$(gitea-runner ${CONFIG_ARG} config runner-file)}
|
||||
LABEL_ARGS=""
|
||||
if [[ ! -z "${GITEA_RUNNER_LABELS}" ]]; then
|
||||
LABEL_ARGS="--labels ${GITEA_RUNNER_LABELS}"
|
||||
|
||||
Reference in new issue
Block a user