mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-31 16:15:13 +02:00
Backport #38520 by @darklight147 ## Problem Jobs that call a reusable workflow (`uses:`) whose steps contain a `run: |` block that **starts with blank lines** never start — the child jobs stay `Blocked` forever, the run never finishes, and nothing is shown to the user (the error is only logged at DEBUG). The reusable is valid YAML and worked on 1.26. ## Root cause `jobparser` serializes each expanded job via `SingleWorkflow.SetJob()`, which uses `yaml.NewEncoder(...).SetIndent(2)`, but `SingleWorkflow.Marshal()` used `yaml.Marshal`, whose default indentation is **4**. Re-emitting a multi-line literal block scalar at a different indentation makes the encoder write a wrong explicit indentation indicator (`run: |4`) whose declared indent doesn't match the actual content indent. The stored `workflow_payload` is then unparseable: ``` run: |4 while ... ``` `jobparser.Parse` / `model.ReadWorkflow` (both go.yaml.in/yaml/v4) reject it: `did not find expected key`. This surfaces in `services/actions/job_emitter.go` `resolve()` → `updateConcurrencyEvaluationForJobWithNeeds` → `ParseJob`, where the error is swallowed at `log.Debug` and the job is left `Blocked`. Encoding at indent 4 triggers the bad indicator; indent 2 does not — matching the value already used by `SetJob`. ## Fix Encode `SingleWorkflow.Marshal()` with `SetIndent(2)` so both encoders agree and the serialized single workflow round-trips. Adds a regression test (`Parse → Marshal → Parse` on a `run:` block with leading blank lines) that fails before the change with `did not find expected key`. ## Notes - This is the correctness fix. Related: #37116 added `ValidateWorkflowContent` to *report* such content for top-level workflows, but the reusable-expansion/concurrency-eval path is not covered and strands silently — this fix removes the failure mode entirely. - Consider a follow-up to elevate the swallowed error in `job_emitter.resolve()` from `log.Debug` to a user-visible job failure (there is an existing `// TODO`). Signed-off-by: quasimodo <mohamed.belkamel@intelcia.com> Co-authored-by: Mohamed Belkamel <39389636+darklight147@users.noreply.github.com> Co-authored-by: quasimodo <mohamed.belkamel@intelcia.com> Co-authored-by: Zettat123 <zettat123@gmail.com>
65 lines
1.9 KiB
Go
65 lines
1.9 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package jobparser
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// A step whose `run:` block starts with blank lines must still survive the
|
|
// Parse -> SingleWorkflow.Marshal -> Parse round-trip. Previously Marshal used a
|
|
// different indentation than SetJob, which made the encoder emit the block scalar
|
|
// with a wrong explicit indentation indicator (`run: |4`) that no longer parsed;
|
|
// the job then stayed silently blocked during concurrency evaluation.
|
|
func TestSingleWorkflowRoundTripRunBlockLeadingBlankLines(t *testing.T) {
|
|
const wf = `name: demo
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
app_name:
|
|
type: string
|
|
required: true
|
|
jobs:
|
|
build:
|
|
name: build
|
|
env:
|
|
IMAGE_TAG: ${{ inputs.app_name }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- if: ${{ inputs.app_name != '' }}
|
|
name: packages
|
|
run: |
|
|
|
|
|
|
echo start
|
|
echo done
|
|
`
|
|
sws, err := Parse([]byte(wf))
|
|
require.NoError(t, err)
|
|
require.Len(t, sws, 1)
|
|
|
|
// pin the original run block as the baseline
|
|
_, origJob := sws[0].Job()
|
|
require.Len(t, origJob.Steps, 1)
|
|
const wantRun = "\n\necho start\necho done\n"
|
|
require.Equal(t, wantRun, origJob.Steps[0].Run)
|
|
|
|
payload, err := sws[0].Marshal()
|
|
require.NoError(t, err)
|
|
|
|
// the serialized single workflow must be parseable again -- this is what the
|
|
// server does in EvaluateJobConcurrencyFillModel -> ParseJob. Before the fix
|
|
// Marshal emitted `run: |4`, which failed here and left the job blocked.
|
|
roundTripped, err := Parse(payload)
|
|
require.NoError(t, err, "serialized single workflow must round-trip; got payload:\n%s", payload)
|
|
require.Len(t, roundTripped, 1)
|
|
|
|
// the round-trip must preserve the run block byte-for-byte
|
|
_, gotJob := roundTripped[0].Job()
|
|
require.Len(t, gotJob.Steps, 1)
|
|
require.Equal(t, wantRun, gotJob.Steps[0].Run, "round-trip must preserve run content; got payload:\n%s", payload)
|
|
}
|