mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-08-07 01:14:22 +02:00
Revert #1136 and use actionslib instead. revert chore: bump the module path to `/v3`, take the version from the VCS stamp (#1136) gitea can not consume the runner's api by version while it's version mismatches the module version: ``` go: gitea.com/gitea/runner@v3.0.1: invalid version: module contains a go.mod file, so module path must match major version ("gitea.com/gitea/runner/v3") ``` Fix that by bumping the module version now. The existing `v3.0.0` and `v3.0.1` tags stay unusable, so a new tag is needed after this lands. Also drop the version `-X` linker flags, which would otherwise have to repeat the new path in both `Makefile` and `.goreleaser.yaml`, where a stale path makes injection silently no-op. Go has recorded the module version in the build info since 1.24, so `Version()` reads it from there, keeping the variable as an override for builds without a VCS stamp. That part started as https://gitea.com/gitea/runner/pulls/1137 but belongs here: the stamp resolves against the tags that are legal for the module path, so without the `/v3` bump it would report `v1.0.9-0.<ts>-<sha>`. Since `release-nightly.yml` triggers on every push to `main`, splitting them would publish a nightly with a `v1` version. --------- Co-authored-by: bircni <bircni@icloud.com> Reviewed-on: https://gitea.com/gitea/runner/pulls/1136 Reviewed-by: techknowlogick <9+techknowlogick@noreply.gitea.com> Reviewed-by: bircni <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io> Reviewed-on: https://gitea.com/gitea/runner/pulls/1148 Reviewed-by: Zettat123 <39446+zettat123@noreply.gitea.com>
627 lines
17 KiB
Go
627 lines
17 KiB
Go
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
// Copyright 2020 The nektos/act Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package runner
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"maps"
|
|
"path"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitea.com/gitea/runner/act/common"
|
|
"gitea.com/gitea/runner/act/container"
|
|
"gitea.com/gitea/runner/act/exprparser"
|
|
"gitea.com/gitea/runner/act/model"
|
|
|
|
_ "embed"
|
|
|
|
"github.com/rhysd/actionlint"
|
|
"go.yaml.in/yaml/v4"
|
|
)
|
|
|
|
// ExpressionEvaluator is the interface for evaluating expressions
|
|
type ExpressionEvaluator interface {
|
|
evaluate(context.Context, string, exprparser.DefaultStatusCheck) (any, error)
|
|
interpolate(context.Context, string) (string, error)
|
|
EvaluateYamlNode(context.Context, *yaml.Node) error
|
|
Interpolate(context.Context, string) string
|
|
}
|
|
|
|
// NewExpressionEvaluator creates a new evaluator
|
|
func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEvaluator {
|
|
return rc.NewExpressionEvaluatorWithEnv(ctx, rc.GetEnv())
|
|
}
|
|
|
|
func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map[string]string) ExpressionEvaluator {
|
|
var workflowCallResult map[string]*model.WorkflowCallResult
|
|
|
|
// todo: cleanup EvaluationEnvironment creation
|
|
using := make(map[string]exprparser.Needs)
|
|
strategy := make(map[string]any)
|
|
if rc.Run != nil {
|
|
job := rc.Run.Job()
|
|
if job != nil && job.Strategy != nil {
|
|
strategy["fail-fast"] = job.Strategy.FailFast
|
|
strategy["max-parallel"] = job.Strategy.MaxParallel
|
|
}
|
|
|
|
jobs := rc.Run.Workflow.Jobs
|
|
jobNeeds := rc.Run.Job().Needs()
|
|
|
|
for _, needs := range jobNeeds {
|
|
using[needs] = exprparser.Needs{
|
|
Outputs: jobs[needs].Outputs,
|
|
Result: jobs[needs].NeedsResult(),
|
|
}
|
|
}
|
|
|
|
// only setup jobs context in case of workflow_call
|
|
// and existing expression evaluator (this means, jobs are at
|
|
// least ready to run)
|
|
if rc.caller != nil && rc.ExprEval != nil {
|
|
workflowCallResult = map[string]*model.WorkflowCallResult{}
|
|
|
|
for jobName, job := range jobs {
|
|
result := model.WorkflowCallResult{
|
|
Outputs: map[string]string{},
|
|
}
|
|
maps.Copy(result.Outputs, job.Outputs)
|
|
workflowCallResult[jobName] = &result
|
|
}
|
|
}
|
|
}
|
|
|
|
ghc := rc.getGithubContext(ctx)
|
|
inputs := getEvaluatorInputs(ctx, rc, nil, ghc)
|
|
|
|
ee := &exprparser.EvaluationEnvironment{
|
|
Github: ghc,
|
|
Env: env,
|
|
Job: rc.getJobContext(),
|
|
Jobs: &workflowCallResult,
|
|
// todo: should be unavailable
|
|
// but required to interpolate/evaluate the step outputs on the job
|
|
Steps: rc.getStepsContext(),
|
|
Secrets: getWorkflowSecrets(ctx, rc),
|
|
Vars: getWorkflowVars(ctx, rc),
|
|
Strategy: strategy,
|
|
Matrix: rc.Matrix,
|
|
Needs: using,
|
|
Inputs: inputs,
|
|
HashFiles: getHashFilesFunction(ctx, rc),
|
|
}
|
|
ee.Runner = rc.getRunnerContext(ctx)
|
|
return expressionEvaluator{
|
|
interpreter: exprparser.NewInterpeter(ee, exprparser.Config{
|
|
Run: rc.Run,
|
|
WorkingDir: rc.Config.Workdir,
|
|
Context: "job",
|
|
}),
|
|
}
|
|
}
|
|
|
|
//go:embed hashfiles/index.js
|
|
var hashfiles string
|
|
|
|
// NewStepExpressionEvaluator creates a new evaluator
|
|
func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step) ExpressionEvaluator {
|
|
// todo: cleanup EvaluationEnvironment creation
|
|
job := rc.Run.Job()
|
|
strategy := make(map[string]any)
|
|
if job.Strategy != nil {
|
|
strategy["fail-fast"] = job.Strategy.FailFast
|
|
strategy["max-parallel"] = job.Strategy.MaxParallel
|
|
}
|
|
|
|
jobs := rc.Run.Workflow.Jobs
|
|
jobNeeds := rc.Run.Job().Needs()
|
|
|
|
using := make(map[string]exprparser.Needs)
|
|
for _, needs := range jobNeeds {
|
|
using[needs] = exprparser.Needs{
|
|
Outputs: jobs[needs].Outputs,
|
|
Result: jobs[needs].NeedsResult(),
|
|
}
|
|
}
|
|
|
|
ghc := rc.getGithubContext(ctx)
|
|
inputs := getEvaluatorInputs(ctx, rc, step, ghc)
|
|
|
|
ee := &exprparser.EvaluationEnvironment{
|
|
Github: step.getGithubContext(ctx),
|
|
Env: *step.getEnv(),
|
|
Job: rc.getJobContext(),
|
|
Steps: rc.getStepsContext(),
|
|
Secrets: getWorkflowSecrets(ctx, rc),
|
|
Vars: getWorkflowVars(ctx, rc),
|
|
Strategy: strategy,
|
|
Matrix: rc.Matrix,
|
|
Needs: using,
|
|
// todo: should be unavailable
|
|
// but required to interpolate/evaluate the inputs in actions/composite
|
|
Inputs: inputs,
|
|
HashFiles: getHashFilesFunction(ctx, rc),
|
|
}
|
|
ee.Runner = rc.getRunnerContext(ctx)
|
|
return expressionEvaluator{
|
|
interpreter: exprparser.NewInterpeter(ee, exprparser.Config{
|
|
Run: rc.Run,
|
|
WorkingDir: rc.Config.Workdir,
|
|
Context: "step",
|
|
}),
|
|
}
|
|
}
|
|
|
|
func getHashFilesFunction(ctx context.Context, rc *RunContext) func(v []reflect.Value) (any, error) {
|
|
hashFiles := func(v []reflect.Value) (any, error) {
|
|
if rc.JobContainer != nil {
|
|
timeed, cancel := context.WithTimeout(ctx, time.Minute)
|
|
defer cancel()
|
|
name := "workflow/hashfiles/index.js"
|
|
hout := &bytes.Buffer{}
|
|
herr := &bytes.Buffer{}
|
|
patterns := []string{}
|
|
followSymlink := false
|
|
|
|
for i, p := range v {
|
|
s := p.String()
|
|
if i == 0 {
|
|
if strings.HasPrefix(s, "--") {
|
|
if strings.EqualFold(s, "--follow-symbolic-links") {
|
|
followSymlink = true
|
|
continue
|
|
}
|
|
return "", fmt.Errorf("Invalid glob option %s, available option: '--follow-symbolic-links'", s)
|
|
}
|
|
}
|
|
patterns = append(patterns, s)
|
|
}
|
|
env := map[string]string{}
|
|
maps.Copy(env, rc.Env)
|
|
env["patterns"] = strings.Join(patterns, "\n")
|
|
if followSymlink {
|
|
env["followSymbolicLinks"] = "true"
|
|
}
|
|
|
|
stdout, stderr := rc.JobContainer.ReplaceLogWriter(hout, herr)
|
|
_ = rc.JobContainer.Copy(rc.JobContainer.GetActPath(), &container.FileEntry{
|
|
Name: name,
|
|
Mode: 0o644,
|
|
Body: hashfiles,
|
|
}).
|
|
Then(rc.execJobContainer([]string{"node", path.Join(rc.JobContainer.GetActPath(), name)},
|
|
env, "", "")).
|
|
Finally(func(context.Context) error {
|
|
rc.JobContainer.ReplaceLogWriter(stdout, stderr)
|
|
return nil
|
|
})(timeed)
|
|
output := hout.String() + "\n" + herr.String()
|
|
guard := "__OUTPUT__"
|
|
outstart := strings.Index(output, guard)
|
|
if outstart != -1 {
|
|
outstart += len(guard)
|
|
outend := strings.Index(output[outstart:], guard)
|
|
if outend != -1 {
|
|
return output[outstart : outstart+outend], nil
|
|
}
|
|
}
|
|
}
|
|
return "", nil
|
|
}
|
|
return hashFiles
|
|
}
|
|
|
|
type expressionEvaluator struct {
|
|
interpreter exprparser.Interpreter
|
|
}
|
|
|
|
func (ee expressionEvaluator) evaluate(ctx context.Context, in string, defaultStatusCheck exprparser.DefaultStatusCheck) (any, error) {
|
|
logger := common.Logger(ctx)
|
|
logger.Debugf("evaluating expression '%s'", in)
|
|
evaluated, err := ee.interpreter.Evaluate(in, defaultStatusCheck)
|
|
|
|
// evaluated is an any: %t renders everything but a bool as "%!t(string=...)"
|
|
printable := regexp.MustCompile(`::add-mask::.*`).ReplaceAllString(fmt.Sprintf("%v", evaluated), "::add-mask::***)")
|
|
logger.Debugf("expression '%s' evaluated to '%s'", in, printable)
|
|
|
|
return evaluated, err
|
|
}
|
|
|
|
func (ee expressionEvaluator) evaluateScalarYamlNode(ctx context.Context, node *yaml.Node) (*yaml.Node, error) {
|
|
var in string
|
|
if err := node.Decode(&in); err != nil {
|
|
return nil, err
|
|
}
|
|
if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") {
|
|
return nil, nil //nolint:nilnil // pre-existing issue from nektos/act
|
|
}
|
|
res, err := ee.evaluateScalar(ctx, in)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret := &yaml.Node{}
|
|
if err := ret.Encode(res); err != nil {
|
|
return nil, err
|
|
}
|
|
return ret, err
|
|
}
|
|
|
|
func (ee expressionEvaluator) evaluateMappingYamlNode(ctx context.Context, node *yaml.Node) (*yaml.Node, error) {
|
|
var ret *yaml.Node
|
|
// GitHub has this undocumented feature to merge maps, called insert directive
|
|
insertDirective := regexp.MustCompile(`\${{\s*insert\s*}}`)
|
|
for i := 0; i < len(node.Content)/2; i++ {
|
|
changed := func() error {
|
|
if ret == nil {
|
|
ret = &yaml.Node{}
|
|
if err := ret.Encode(node); err != nil {
|
|
return err
|
|
}
|
|
ret.Content = ret.Content[:i*2]
|
|
}
|
|
return nil
|
|
}
|
|
k := node.Content[i*2]
|
|
v := node.Content[i*2+1]
|
|
ev, err := ee.evaluateYamlNodeInternal(ctx, v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ev != nil {
|
|
if err := changed(); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
ev = v
|
|
}
|
|
var sk string
|
|
// Merge the nested map of the insert directive
|
|
if k.Decode(&sk) == nil && insertDirective.MatchString(sk) {
|
|
if ev.Kind != yaml.MappingNode {
|
|
return nil, fmt.Errorf("failed to insert node %v into mapping %v unexpected type %v expected MappingNode", ev, node, ev.Kind)
|
|
}
|
|
if err := changed(); err != nil {
|
|
return nil, err
|
|
}
|
|
ret.Content = append(ret.Content, ev.Content...)
|
|
} else {
|
|
ek, err := ee.evaluateYamlNodeInternal(ctx, k)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ek != nil {
|
|
if err := changed(); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
ek = k
|
|
}
|
|
if ret != nil {
|
|
ret.Content = append(ret.Content, ek, ev)
|
|
}
|
|
}
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func (ee expressionEvaluator) evaluateSequenceYamlNode(ctx context.Context, node *yaml.Node) (*yaml.Node, error) {
|
|
var ret *yaml.Node
|
|
for i := 0; i < len(node.Content); i++ {
|
|
v := node.Content[i]
|
|
// Preserve nested sequences
|
|
wasseq := v.Kind == yaml.SequenceNode
|
|
ev, err := ee.evaluateYamlNodeInternal(ctx, v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ev != nil {
|
|
if ret == nil {
|
|
ret = &yaml.Node{}
|
|
if err := ret.Encode(node); err != nil {
|
|
return nil, err
|
|
}
|
|
ret.Content = ret.Content[:i]
|
|
}
|
|
// GitHub has this undocumented feature to merge sequences / arrays
|
|
// We have a nested sequence via evaluation, merge the arrays
|
|
if ev.Kind == yaml.SequenceNode && !wasseq {
|
|
ret.Content = append(ret.Content, ev.Content...)
|
|
} else {
|
|
ret.Content = append(ret.Content, ev)
|
|
}
|
|
} else if ret != nil {
|
|
ret.Content = append(ret.Content, v)
|
|
}
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func (ee expressionEvaluator) evaluateYamlNodeInternal(ctx context.Context, node *yaml.Node) (*yaml.Node, error) {
|
|
switch node.Kind {
|
|
case yaml.ScalarNode:
|
|
return ee.evaluateScalarYamlNode(ctx, node)
|
|
case yaml.MappingNode:
|
|
return ee.evaluateMappingYamlNode(ctx, node)
|
|
case yaml.SequenceNode:
|
|
return ee.evaluateSequenceYamlNode(ctx, node)
|
|
default:
|
|
return nil, nil //nolint:nilnil // pre-existing issue from nektos/act
|
|
}
|
|
}
|
|
|
|
func (ee expressionEvaluator) EvaluateYamlNode(ctx context.Context, node *yaml.Node) error {
|
|
ret, err := ee.evaluateYamlNodeInternal(ctx, node)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ret != nil {
|
|
return ret.Decode(node)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ee expressionEvaluator) Interpolate(ctx context.Context, in string) string {
|
|
out, err := ee.interpolate(ctx, in)
|
|
if err != nil {
|
|
common.Logger(ctx).Errorf("Unable to interpolate expression '%s': %s", in, err)
|
|
return ""
|
|
}
|
|
return out
|
|
}
|
|
|
|
// interpolate evaluates every part on its own, so a malformed one cannot restructure its neighbours
|
|
func (ee expressionEvaluator) interpolate(ctx context.Context, in string) (string, error) {
|
|
parts, err := splitSubExpressions(in)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(parts) == 1 && !parts[0].isExpr {
|
|
return in, nil
|
|
}
|
|
var out strings.Builder
|
|
out.Grow(len(in))
|
|
for _, part := range parts {
|
|
if !part.isExpr {
|
|
out.WriteString(part.text)
|
|
continue
|
|
}
|
|
evaluated, err := ee.evaluate(ctx, part.text, exprparser.DefaultStatusCheckNone)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
out.WriteString(exprparser.CoerceToString(evaluated))
|
|
}
|
|
return out.String(), nil
|
|
}
|
|
|
|
// evaluateScalar keeps the type of a lone expression, so `${{ fromJSON('[1,2]') }}` stays an array
|
|
func (ee expressionEvaluator) evaluateScalar(ctx context.Context, in string) (any, error) {
|
|
parts, err := splitSubExpressions(in)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(parts) == 1 && parts[0].isExpr {
|
|
return ee.evaluate(ctx, parts[0].text, exprparser.DefaultStatusCheckNone)
|
|
}
|
|
return ee.interpolate(ctx, in)
|
|
}
|
|
|
|
// EvalBool evaluates an expression against given evaluator. An `if:` is an expression even without
|
|
// `${{ }}`, while literal text around one makes the whole value a string.
|
|
func EvalBool(ctx context.Context, evaluator ExpressionEvaluator, expr string, defaultStatusCheck exprparser.DefaultStatusCheck) (bool, error) {
|
|
parts, err := splitSubExpressions(expr)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if len(parts) == 1 {
|
|
evaluated, err := evaluator.evaluate(ctx, parts[0].text, defaultStatusCheck)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return exprparser.IsTruthy(evaluated), nil
|
|
}
|
|
|
|
// mixed content is a string, so the status check applies to it separately
|
|
if defaultStatusCheck != exprparser.DefaultStatusCheckNone && !callsStatusFunction(parts) {
|
|
status, err := evaluator.evaluate(ctx, "", defaultStatusCheck)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if !exprparser.IsTruthy(status) {
|
|
return false, nil
|
|
}
|
|
}
|
|
interpolated, err := evaluator.interpolate(ctx, expr)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return exprparser.IsTruthy(interpolated), nil
|
|
}
|
|
|
|
// callsStatusFunction reports whether any part calls a status function. A part that does not parse
|
|
// counts as one, so the evaluation reports it against the real values.
|
|
func callsStatusFunction(parts []exprPart) bool {
|
|
for _, part := range parts {
|
|
if !part.isExpr {
|
|
continue
|
|
}
|
|
// The lexer needs the closing `}}` that the scanner strips.
|
|
exprNode, err := actionlint.NewExprParser().Parse(actionlint.NewExprLexer(part.text + "}}"))
|
|
if err != nil || exprparser.CallsStatusFunction(exprNode) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
type exprPart struct {
|
|
text string
|
|
isExpr bool
|
|
}
|
|
|
|
// splitSubExpressions splits in the way GitHub's template reader does, leaving a value without a
|
|
// complete expression literal.
|
|
func splitSubExpressions(in string) ([]exprPart, error) {
|
|
if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") {
|
|
return []exprPart{{text: in}}, nil
|
|
}
|
|
|
|
parts := make([]exprPart, 0, 2*strings.Count(in, "${{")+1)
|
|
for {
|
|
start := strings.Index(in, "${{")
|
|
if start < 0 {
|
|
if in != "" {
|
|
parts = append(parts, exprPart{text: in})
|
|
}
|
|
return parts, nil
|
|
}
|
|
if start > 0 {
|
|
parts = append(parts, exprPart{text: in[:start]})
|
|
}
|
|
rest := in[start+len("${{"):]
|
|
end := indexExprEnd(rest)
|
|
if end < 0 {
|
|
return nil, errors.New("unclosed expression")
|
|
}
|
|
parts = append(parts, exprPart{text: strings.TrimSpace(rest[:end]), isExpr: true})
|
|
in = rest[end+len("}}"):]
|
|
}
|
|
}
|
|
|
|
// indexExprEnd returns the offset of the `}}` ending an expression, or -1. A quote toggles string
|
|
// state, so a `}}` inside a string does not end it.
|
|
func indexExprEnd(in string) int {
|
|
inString := false
|
|
for i := range len(in) {
|
|
switch {
|
|
case in[i] == '\'':
|
|
inString = !inString
|
|
case !inString && in[i] == '}' && i+1 < len(in) && in[i+1] == '}':
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func getEvaluatorInputs(ctx context.Context, rc *RunContext, step step, ghc *model.GithubContext) map[string]any {
|
|
inputs := map[string]any{}
|
|
|
|
setupWorkflowInputs(ctx, &inputs, rc)
|
|
|
|
var env map[string]string
|
|
if step != nil {
|
|
env = *step.getEnv()
|
|
} else {
|
|
env = rc.GetEnv()
|
|
}
|
|
|
|
for k, v := range env {
|
|
if after, ok := strings.CutPrefix(k, "INPUT_"); ok {
|
|
inputs[strings.ToLower(after)] = v
|
|
}
|
|
}
|
|
|
|
if ghc.EventName == "workflow_dispatch" {
|
|
config := rc.Run.Workflow.WorkflowDispatchConfig()
|
|
if config != nil && config.Inputs != nil {
|
|
for k, v := range config.Inputs {
|
|
value := nestedMapLookup(ghc.Event, "inputs", k)
|
|
if value == nil {
|
|
value = v.Default
|
|
}
|
|
inputs[k] = coerceInputValue(value, v.Type)
|
|
}
|
|
}
|
|
}
|
|
|
|
if ghc.EventName == "workflow_call" {
|
|
config := rc.Run.Workflow.WorkflowCallConfig()
|
|
if config != nil && config.Inputs != nil {
|
|
for k, v := range config.Inputs {
|
|
value := nestedMapLookup(ghc.Event, "inputs", k)
|
|
if value == nil {
|
|
value = v.Default
|
|
}
|
|
inputs[k] = coerceInputValue(value, v.Type)
|
|
}
|
|
}
|
|
}
|
|
return inputs
|
|
}
|
|
|
|
// coerceInputValue converts an input value to the type declared by the workflow.
|
|
// The event payload carries natively typed JSON values on newer Gitea versions,
|
|
// while defaults and older servers provide strings.
|
|
func coerceInputValue(value any, inputType string) any {
|
|
if inputType != "boolean" {
|
|
return value
|
|
}
|
|
if b, ok := value.(bool); ok {
|
|
return b
|
|
}
|
|
return value == "true"
|
|
}
|
|
|
|
func setupWorkflowInputs(ctx context.Context, inputs *map[string]any, rc *RunContext) {
|
|
if rc.caller != nil {
|
|
config := rc.Run.Workflow.WorkflowCallConfig()
|
|
|
|
for name, input := range config.Inputs {
|
|
value := rc.caller.runContext.Run.Job().With[name]
|
|
if value != nil {
|
|
if str, ok := value.(string); ok {
|
|
// evaluate using the calling RunContext (outside)
|
|
value = rc.caller.runContext.ExprEval.Interpolate(ctx, str)
|
|
}
|
|
}
|
|
|
|
if value == nil && config != nil && config.Inputs != nil {
|
|
value = input.Default
|
|
if rc.ExprEval != nil {
|
|
if str, ok := value.(string); ok {
|
|
// evaluate using the called RunContext (inside)
|
|
value = rc.ExprEval.Interpolate(ctx, str)
|
|
}
|
|
}
|
|
}
|
|
|
|
(*inputs)[name] = coerceInputValue(value, input.Type)
|
|
}
|
|
}
|
|
}
|
|
|
|
func getWorkflowSecrets(ctx context.Context, rc *RunContext) map[string]string {
|
|
if rc.caller != nil {
|
|
job := rc.caller.runContext.Run.Job()
|
|
secrets := job.Secrets()
|
|
|
|
if secrets == nil && job.InheritSecrets() {
|
|
secrets = rc.caller.runContext.Config.Secrets
|
|
}
|
|
|
|
// Interpolate into a new map. secrets may be the shared Config.Secrets (or the job's
|
|
// map), which other parallel jobs read concurrently (e.g. log masking), so mutating it
|
|
// in place is a data race.
|
|
interpolated := make(map[string]string, len(secrets))
|
|
for k, v := range secrets {
|
|
interpolated[k] = rc.caller.runContext.ExprEval.Interpolate(ctx, v)
|
|
}
|
|
|
|
return interpolated
|
|
}
|
|
|
|
return rc.Config.Secrets
|
|
}
|
|
|
|
func getWorkflowVars(_ context.Context, rc *RunContext) map[string]string {
|
|
return rc.Config.Vars
|
|
}
|