mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-26 06:33:42 +09:00
Updates actionslib to https://gitea.com/gitea/actionslib/releases/tag/v1.2.1, moves workflow parsing into it and aligns behaviour with GitHub. 1. `uses:` supports `self:` (Gitea-only feature) and `$/` paths. 1. `strategy`, `matrix`, `max-parallel` and `fail-fast` accept expressions, including over `needs`. A job whose `name`, `runs-on` or `continue-on-error` reads `needs` is resolved once they finish. 1. A job `if:` may only read `github`, `needs`, `vars` and `inputs` and is decided before the matrix, as on github.com. 1. Matrix `fail-fast` cancels the other combinations, and `always()` jobs keep running when a run is cancelled. 1. Invalid workflow files, including a malformed `on:` and unknown or cyclic `needs`, show up on push as failed runs with the error. 1. A job whose `if:` or `concurrency:` fails to evaluate is skipped or failed with the error, instead of staying blocked. 1. Reusable workflows: a missing and an unreadable repository fail alike, public callers cannot use private workflows, nested jobs cannot exceed the caller's token permissions. 1. Runner labels match case-insensitively, and `runs-on` accepts an array from an expression. Runner PR: https://gitea.com/gitea/runner/pulls/1247 Docs PR: https://gitea.com/gitea/docs/pulls/553 Fixes: https://github.com/go-gitea/gitea/issues/38990 Fixes: https://github.com/go-gitea/gitea/issues/39382 Fixes: https://github.com/go-gitea/gitea/issues/32364 Fixes: https://github.com/go-gitea/gitea/issues/36077 Fixes: https://github.com/go-gitea/gitea/issues/23277 Fixes: https://github.com/go-gitea/gitea/issues/29020 Co-authored-by: Claude (Opus 5) <noreply@anthropic.com> Co-authored-by: Zettat123 <zettat123@gmail.com>
112 lines
3.8 KiB
Go
112 lines
3.8 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package jobparser
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"gitea.dev/actionslib/pkg/model"
|
|
|
|
"go.yaml.in/yaml/v4"
|
|
)
|
|
|
|
// ResolveCallerInputs evaluates the caller's `with` against its own contexts and types it by the called workflow's inputs.
|
|
func ResolveCallerInputs(
|
|
jobID string,
|
|
job *Job,
|
|
config *model.WorkflowCall,
|
|
gitCtx map[string]any,
|
|
results map[string]*JobResult,
|
|
vars map[string]string,
|
|
inputs map[string]any,
|
|
) (map[string]any, error) {
|
|
if job.With.Kind != 0 && job.With.Kind != yaml.MappingNode {
|
|
return nil, errors.New("caller with must be a mapping")
|
|
}
|
|
evaluator, err := newJobEvaluator(jobID, job, gitCtx, results, vars, inputs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get caller %q matrix: %w", jobID, err)
|
|
}
|
|
var with map[string]any
|
|
if err := model.DecodeEvaluated("with", job.With, evaluator.EvaluateYamlNode, &with); err != nil {
|
|
return nil, err
|
|
}
|
|
declared := make(map[string]model.WorkflowCallInput, len(config.Inputs))
|
|
for name, input := range config.Inputs {
|
|
declared[strings.ToLower(name)] = input
|
|
}
|
|
for name, value := range with {
|
|
input, ok := declared[strings.ToLower(name)]
|
|
if !ok {
|
|
continue // ignored as in released Gitea, github.com rejects an undeclared input
|
|
}
|
|
switch input.Type {
|
|
case "boolean":
|
|
if _, ok := value.(bool); !ok {
|
|
return nil, fmt.Errorf("input %s: expected a boolean, got %T", name, value)
|
|
}
|
|
case "number":
|
|
switch value.(type) {
|
|
case float64, int, int64, uint64:
|
|
default:
|
|
return nil, fmt.Errorf("input %s: expected a number, got %T", name, value)
|
|
}
|
|
}
|
|
}
|
|
return evaluator.ResolveWorkflowCallInputs(config, with)
|
|
}
|
|
|
|
// ParseWorkflowCallConfig reads the workflow_call configuration of a called workflow, decoding only its `on`.
|
|
func ParseWorkflowCallConfig(content []byte) (*model.WorkflowCall, error) {
|
|
var workflow struct {
|
|
RawOn yaml.Node `yaml:"on"`
|
|
}
|
|
if err := decodeResolved(content, &workflow); err != nil {
|
|
return nil, err
|
|
}
|
|
return (&model.Workflow{RawOn: workflow.RawOn}).ParseWorkflowCallConfig()
|
|
}
|
|
|
|
// SecretsInherit is the literal keyword used in a caller's `secrets: inherit` directive
|
|
const SecretsInherit = "inherit"
|
|
|
|
// callerSecretValueRegexp matches the `${{ secrets.NAME }}` form expected for each value in a caller's `secrets:` mapping.
|
|
var callerSecretValueRegexp = regexp.MustCompile(`^\s*\$\{\{\s*secrets\.([A-Za-z_][A-Za-z0-9_]*)\s*\}\}\s*$`)
|
|
|
|
// ParseCallerSecrets decodes a caller's "secrets:" YAML node into one of two forms:
|
|
// - inherit == true: the caller wrote `secrets: inherit`; mapping is nil
|
|
// - inherit == false, mapping == {alias: source_name}: explicit mapping. Each value must be of the form `${{ secrets.NAME }}`.
|
|
//
|
|
// Both alias and source name are upper-cased: secret names are case-insensitive (matching GitHub),
|
|
// and Gitea stores secrets upper-cased, so this keeps lookups and schema validation consistent.
|
|
func ParseCallerSecrets(node yaml.Node) (inherit bool, mapping map[string]string, err error) {
|
|
if node.IsZero() {
|
|
return false, nil, nil
|
|
}
|
|
if node.Kind == yaml.ScalarNode && strings.TrimSpace(node.Value) == SecretsInherit {
|
|
return true, nil, nil
|
|
}
|
|
if node.Kind != yaml.MappingNode {
|
|
return false, nil, errors.New("invalid secrets: section, expected mapping or 'inherit'")
|
|
}
|
|
out := make(map[string]string, len(node.Content)/2)
|
|
for i := 0; i+1 < len(node.Content); i += 2 {
|
|
k := node.Content[i]
|
|
v := node.Content[i+1]
|
|
var sv string
|
|
if err := v.Decode(&sv); err != nil {
|
|
return false, nil, fmt.Errorf("decode secret %q: %w", k.Value, err)
|
|
}
|
|
matches := callerSecretValueRegexp.FindStringSubmatch(sv)
|
|
if len(matches) != 2 {
|
|
return false, nil, fmt.Errorf("caller secret %q value must be of the form ${{ secrets.NAME }}", k.Value)
|
|
}
|
|
out[strings.ToUpper(k.Value)] = strings.ToUpper(matches[1])
|
|
}
|
|
return false, out, nil
|
|
}
|