Skip to content

Commit

Permalink
fix: use actions/runner hashfiles in container (#1940)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people committed Oct 3, 2023
1 parent 2f479ba commit 7c7d80e
Show file tree
Hide file tree
Showing 3 changed files with 5,200 additions and 20 deletions.
28 changes: 16 additions & 12 deletions pkg/exprparser/interpreter.go
Expand Up @@ -12,18 +12,19 @@ import (
)

type EvaluationEnvironment struct {
Github *model.GithubContext
Env map[string]string
Job *model.JobContext
Jobs *map[string]*model.WorkflowCallResult
Steps map[string]*model.StepResult
Runner map[string]interface{}
Secrets map[string]string
Vars map[string]string
Strategy map[string]interface{}
Matrix map[string]interface{}
Needs map[string]Needs
Inputs map[string]interface{}
Github *model.GithubContext
Env map[string]string
Job *model.JobContext
Jobs *map[string]*model.WorkflowCallResult
Steps map[string]*model.StepResult
Runner map[string]interface{}
Secrets map[string]string
Vars map[string]string
Strategy map[string]interface{}
Matrix map[string]interface{}
Needs map[string]Needs
Inputs map[string]interface{}
HashFiles func([]reflect.Value) (interface{}, error)
}

type Needs struct {
Expand Down Expand Up @@ -607,6 +608,9 @@ func (impl *interperterImpl) evaluateFuncCall(funcCallNode *actionlint.FuncCallN
case "fromjson":
return impl.fromJSON(args[0])
case "hashfiles":
if impl.env.HashFiles != nil {
return impl.env.HashFiles(args)
}
return impl.hashFiles(args...)
case "always":
return impl.always()
Expand Down
89 changes: 81 additions & 8 deletions pkg/runner/expression.go
@@ -1,12 +1,19 @@
package runner

import (
"bytes"
"context"
"fmt"
"path"
"reflect"
"regexp"
"strings"
"time"

_ "embed"

"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/exprparser"
"github.com/nektos/act/pkg/model"
"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -75,13 +82,14 @@ func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map
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,
Steps: rc.getStepsContext(),
Secrets: getWorkflowSecrets(ctx, rc),
Vars: getWorkflowVars(ctx, rc),
Strategy: strategy,
Matrix: rc.Matrix,
Needs: using,
Inputs: inputs,
HashFiles: getHashFilesFunction(ctx, rc),
}
if rc.JobContainer != nil {
ee.Runner = rc.JobContainer.GetRunnerContext(ctx)
Expand All @@ -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
func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step) ExpressionEvaluator {
// todo: cleanup EvaluationEnvironment creation
Expand Down Expand Up @@ -131,7 +142,8 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
Needs: using,
// todo: should be unavailable
// but required to interpolate/evaluate the inputs in actions/composite
Inputs: inputs,
Inputs: inputs,
HashFiles: getHashFilesFunction(ctx, rc),
}
if rc.JobContainer != nil {
ee.Runner = rc.JobContainer.GetRunnerContext(ctx)
Expand All @@ -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 {
interpreter exprparser.Interpreter
}
Expand Down

0 comments on commit 7c7d80e

Please sign in to comment.