fix: use actions/runner hashfiles in container (#1940)
* fix: use actions/runner hashfiles in container Previously hashfiles ran on the host, this don't work for container generated content * fix: lint * fix: lint * fix assign follow symlink flag Co-authored-by: Jason Song <i@wolfogre.com> --------- Co-authored-by: Jason Song <i@wolfogre.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
2f479ba024
commit
7c7d80ebdd
3 changed files with 5200 additions and 20 deletions
|
@ -12,18 +12,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type EvaluationEnvironment struct {
|
type EvaluationEnvironment struct {
|
||||||
Github *model.GithubContext
|
Github *model.GithubContext
|
||||||
Env map[string]string
|
Env map[string]string
|
||||||
Job *model.JobContext
|
Job *model.JobContext
|
||||||
Jobs *map[string]*model.WorkflowCallResult
|
Jobs *map[string]*model.WorkflowCallResult
|
||||||
Steps map[string]*model.StepResult
|
Steps map[string]*model.StepResult
|
||||||
Runner map[string]interface{}
|
Runner map[string]interface{}
|
||||||
Secrets map[string]string
|
Secrets map[string]string
|
||||||
Vars map[string]string
|
Vars map[string]string
|
||||||
Strategy map[string]interface{}
|
Strategy map[string]interface{}
|
||||||
Matrix map[string]interface{}
|
Matrix map[string]interface{}
|
||||||
Needs map[string]Needs
|
Needs map[string]Needs
|
||||||
Inputs map[string]interface{}
|
Inputs map[string]interface{}
|
||||||
|
HashFiles func([]reflect.Value) (interface{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Needs struct {
|
type Needs struct {
|
||||||
|
@ -607,6 +608,9 @@ func (impl *interperterImpl) evaluateFuncCall(funcCallNode *actionlint.FuncCallN
|
||||||
case "fromjson":
|
case "fromjson":
|
||||||
return impl.fromJSON(args[0])
|
return impl.fromJSON(args[0])
|
||||||
case "hashfiles":
|
case "hashfiles":
|
||||||
|
if impl.env.HashFiles != nil {
|
||||||
|
return impl.env.HashFiles(args)
|
||||||
|
}
|
||||||
return impl.hashFiles(args...)
|
return impl.hashFiles(args...)
|
||||||
case "always":
|
case "always":
|
||||||
return impl.always()
|
return impl.always()
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
package runner
|
package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/common"
|
"github.com/nektos/act/pkg/common"
|
||||||
|
"github.com/nektos/act/pkg/container"
|
||||||
"github.com/nektos/act/pkg/exprparser"
|
"github.com/nektos/act/pkg/exprparser"
|
||||||
"github.com/nektos/act/pkg/model"
|
"github.com/nektos/act/pkg/model"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
@ -75,13 +82,14 @@ func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map
|
||||||
Jobs: &workflowCallResult,
|
Jobs: &workflowCallResult,
|
||||||
// todo: should be unavailable
|
// todo: should be unavailable
|
||||||
// but required to interpolate/evaluate the step outputs on the job
|
// but required to interpolate/evaluate the step outputs on the job
|
||||||
Steps: rc.getStepsContext(),
|
Steps: rc.getStepsContext(),
|
||||||
Secrets: getWorkflowSecrets(ctx, rc),
|
Secrets: getWorkflowSecrets(ctx, rc),
|
||||||
Vars: getWorkflowVars(ctx, rc),
|
Vars: getWorkflowVars(ctx, rc),
|
||||||
Strategy: strategy,
|
Strategy: strategy,
|
||||||
Matrix: rc.Matrix,
|
Matrix: rc.Matrix,
|
||||||
Needs: using,
|
Needs: using,
|
||||||
Inputs: inputs,
|
Inputs: inputs,
|
||||||
|
HashFiles: getHashFilesFunction(ctx, rc),
|
||||||
}
|
}
|
||||||
if rc.JobContainer != nil {
|
if rc.JobContainer != nil {
|
||||||
ee.Runner = rc.JobContainer.GetRunnerContext(ctx)
|
ee.Runner = rc.JobContainer.GetRunnerContext(ctx)
|
||||||
|
@ -95,6 +103,9 @@ func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:embed hashfiles/index.js
|
||||||
|
var hashfiles string
|
||||||
|
|
||||||
// NewExpressionEvaluator creates a new evaluator
|
// NewExpressionEvaluator creates a new evaluator
|
||||||
func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step) ExpressionEvaluator {
|
func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step) ExpressionEvaluator {
|
||||||
// todo: cleanup EvaluationEnvironment creation
|
// todo: cleanup EvaluationEnvironment creation
|
||||||
|
@ -131,7 +142,8 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
|
||||||
Needs: using,
|
Needs: using,
|
||||||
// todo: should be unavailable
|
// todo: should be unavailable
|
||||||
// but required to interpolate/evaluate the inputs in actions/composite
|
// but required to interpolate/evaluate the inputs in actions/composite
|
||||||
Inputs: inputs,
|
Inputs: inputs,
|
||||||
|
HashFiles: getHashFilesFunction(ctx, rc),
|
||||||
}
|
}
|
||||||
if rc.JobContainer != nil {
|
if rc.JobContainer != nil {
|
||||||
ee.Runner = rc.JobContainer.GetRunnerContext(ctx)
|
ee.Runner = rc.JobContainer.GetRunnerContext(ctx)
|
||||||
|
@ -145,6 +157,67 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getHashFilesFunction(ctx context.Context, rc *RunContext) func(v []reflect.Value) (interface{}, error) {
|
||||||
|
hashFiles := func(v []reflect.Value) (interface{}, 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{}
|
||||||
|
for k, v := range rc.Env {
|
||||||
|
env[k] = v
|
||||||
|
}
|
||||||
|
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 {
|
type expressionEvaluator struct {
|
||||||
interpreter exprparser.Interpreter
|
interpreter exprparser.Interpreter
|
||||||
}
|
}
|
||||||
|
|
5103
pkg/runner/hashfiles/index.js
Normal file
5103
pkg/runner/hashfiles/index.js
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue