mirror of
https://gitea.com/gitea/runner.git
synced 2026-08-28 14:57:46 +00:00
fix: improve behaviour across masking, commands and status (#1194)
Fixes 51 bugs discovered via comparison with `actions/runner`. Every fix has test coverage.
### Secrets
- A short secret registered no shifted-base64 form, so `base64("user:$TOKEN")` printed in the clear
- Encoded forms came only from the whole trimmed value, missing padded and per-line spellings
- Masks split only on `\n`, so `::add-mask::a%0Db` registered neither half
- Adds XML, expression-string and quote-trimming encoders
### Workflow commands
- Split at the last `::` or `]` rather than the first, so `::add-mask::a::b` registered no mask
- A command on the last line without a newline was ignored, and `::ADD-MASK::` did nothing
- `##[...]` did not decode `%3B`/`%5D`, properties lost anything after a second `=`
- `$GITHUB_ENV` and `::set-env::` now refuse `NODE_OPTIONS`
### Status
- `continue-on-error` reported failed, a cancelled job reported success, an `if:` error reported cancelled
- File commands ran after `continue-on-error`, failing the job while the step stayed green
- A bad job output aborted the whole run instead of that job
### Steps and actions
- `${{ matrix.* }}` and `${{ strategy.* }}` were empty inside composite actions
- Composite inputs leaked into nested actions as `INPUT_*`, `with:` matched case-sensitively, `pre` failures were dropped
- Docker actions dropped `runs.env` when the caller passed `with: args:`, and caller `args`/`entrypoint` beat the manifest
- An implicit shell ran with `pipefail`, a `shell:` without `{0}` passed without running
- `container.env` overrode job env and every `$GITHUB_ENV` write, heredocs lost leading blank lines, `$GITHUB_PATH` was not BOM-decoded
Written by Claude Opus 5.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1194
Reviewed-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
+50
-21
@@ -492,8 +492,6 @@ func TestDockerActionImageTag(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
// Only the entrypoint is stage specific: every stage of a docker action receives runs.args
|
||||
// and runs.env, and the `entrypoint` input applies to the main stage alone.
|
||||
func TestExecAsDockerStageEntrypoint(t *testing.T) {
|
||||
orig := ContainerNewContainer
|
||||
defer func() { ContainerNewContainer = orig }()
|
||||
@@ -501,25 +499,62 @@ func TestExecAsDockerStageEntrypoint(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
stage stepStage
|
||||
with map[string]string
|
||||
runs model.ActionRuns
|
||||
env map[string]string
|
||||
wantCmd []string
|
||||
wantEntrypoint []string
|
||||
}{
|
||||
{
|
||||
name: "main stage prefers the entrypoint input",
|
||||
stage: stepStageMain,
|
||||
wantEntrypoint: []string{"input.sh"},
|
||||
name: "main stage prefers manifest values",
|
||||
stage: stepStageMain,
|
||||
with: map[string]string{"args": "caller", "entrypoint": "input.sh"},
|
||||
runs: model.ActionRuns{
|
||||
Entrypoint: "main.sh",
|
||||
Args: []string{"manifest"},
|
||||
Env: map[string]string{"ACTION_ONLY": "manifest"},
|
||||
},
|
||||
wantCmd: []string{"manifest"},
|
||||
wantEntrypoint: []string{"main.sh"},
|
||||
},
|
||||
{
|
||||
name: "pre stage uses runs.pre-entrypoint",
|
||||
stage: stepStagePre,
|
||||
name: "main stage uses caller fallbacks",
|
||||
stage: stepStageMain,
|
||||
with: map[string]string{"args": "caller --flag", "entrypoint": "input.sh --verbose"},
|
||||
runs: model.ActionRuns{Env: map[string]string{"ACTION_ONLY": "manifest"}},
|
||||
wantCmd: []string{"caller", "--flag"},
|
||||
wantEntrypoint: []string{"input.sh", "--verbose"},
|
||||
},
|
||||
{
|
||||
name: "explicit empty manifest args suppress caller args",
|
||||
stage: stepStageMain,
|
||||
with: map[string]string{"args": "caller"},
|
||||
runs: model.ActionRuns{Args: []string{}},
|
||||
wantCmd: []string{},
|
||||
},
|
||||
{
|
||||
name: "pre stage keeps step environment",
|
||||
stage: stepStagePre,
|
||||
with: map[string]string{"entrypoint": "input.sh"},
|
||||
runs: model.ActionRuns{
|
||||
PreEntrypoint: "pre.sh --verbose",
|
||||
Args: []string{"hello"},
|
||||
Env: map[string]string{"SHARED": "manifest"},
|
||||
},
|
||||
env: map[string]string{"SHARED": "step"},
|
||||
wantCmd: []string{"hello"},
|
||||
wantEntrypoint: []string{"pre.sh", "--verbose"},
|
||||
},
|
||||
{
|
||||
name: "post stage uses runs.post-entrypoint",
|
||||
name: "post stage uses manifest entrypoint",
|
||||
stage: stepStagePost,
|
||||
runs: model.ActionRuns{PostEntrypoint: "post.sh", Args: []string{"hello"}},
|
||||
wantCmd: []string{"hello"},
|
||||
wantEntrypoint: []string{"post.sh"},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.runs.Using, tc.runs.Image = "docker", "docker://node:14"
|
||||
cm := &containerMock{}
|
||||
var input *container.NewContainerInput
|
||||
ContainerNewContainer = func(in *container.NewContainerInput) container.ExecutionsEnvironment {
|
||||
@@ -528,22 +563,14 @@ func TestExecAsDockerStageEntrypoint(t *testing.T) {
|
||||
}
|
||||
|
||||
step := &stepActionRemote{
|
||||
Step: &model.Step{ID: "1", Uses: "org/action@v1", With: map[string]string{"entrypoint": "input.sh"}},
|
||||
Step: &model.Step{ID: "1", Uses: "org/action@v1", With: tc.with},
|
||||
RunContext: &RunContext{
|
||||
Config: &Config{},
|
||||
Run: &model.Run{JobID: "1", Workflow: &model.Workflow{Jobs: map[string]*model.Job{"1": {}}}},
|
||||
JobContainer: cm,
|
||||
},
|
||||
action: &model.Action{Runs: model.ActionRuns{
|
||||
Using: "docker",
|
||||
Image: "docker://node:14",
|
||||
PreEntrypoint: "pre.sh --verbose",
|
||||
Entrypoint: "main.sh",
|
||||
PostEntrypoint: "post.sh",
|
||||
Args: []string{"hello"},
|
||||
Env: map[string]string{"MY_VAR": "world"},
|
||||
}},
|
||||
env: map[string]string{},
|
||||
action: &model.Action{Runs: tc.runs},
|
||||
env: mergeMaps(tc.env),
|
||||
}
|
||||
|
||||
cm.On("Pull", false).Return(func(context.Context) error { return nil })
|
||||
@@ -554,9 +581,11 @@ func TestExecAsDockerStageEntrypoint(t *testing.T) {
|
||||
|
||||
require.NoError(t, execAsDocker(context.Background(), step, "action", t.TempDir(), t.TempDir(), false, tc.stage))
|
||||
require.NotNil(t, input)
|
||||
assert.Equal(t, tc.wantCmd, input.Cmd)
|
||||
assert.Equal(t, tc.wantEntrypoint, input.Entrypoint)
|
||||
assert.Equal(t, []string{"hello"}, input.Cmd)
|
||||
assert.Contains(t, input.Env, "MY_VAR=world")
|
||||
for key, value := range mergeMaps(tc.runs.Env, tc.env) {
|
||||
assert.Contains(t, input.Env, key+"="+value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user