Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
dca7801682 | ||
|
4b99ed8916 | ||
|
e46ede1b17 | ||
|
1ba076d321 | ||
|
0efa2d5e63 |
@@ -16,7 +16,6 @@ func NewInterpeter(
|
||||
gitCtx *model.GithubContext,
|
||||
results map[string]*JobResult,
|
||||
) exprparser.Interpreter {
|
||||
|
||||
strategy := make(map[string]interface{})
|
||||
if job.Strategy != nil {
|
||||
strategy["fail-fast"] = job.Strategy.FailFast
|
||||
|
@@ -50,6 +50,7 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
|
||||
runsOn[i] = evaluator.Interpolate(v)
|
||||
}
|
||||
job.RawRunsOn = encodeRunsOn(runsOn)
|
||||
job.EraseNeeds() // there will be only one job in SingleWorkflow, it cannot have needs
|
||||
ret = append(ret, &SingleWorkflow{
|
||||
Name: workflow.Name,
|
||||
RawOn: workflow.RawOn,
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package jobparser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nektos/act/pkg/model"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -121,3 +123,85 @@ type RunDefaults struct {
|
||||
Shell string `yaml:"shell,omitempty"`
|
||||
WorkingDirectory string `yaml:"working-directory,omitempty"`
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
Name string
|
||||
Acts map[string][]string
|
||||
}
|
||||
|
||||
func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) {
|
||||
switch rawOn.Kind {
|
||||
case yaml.ScalarNode:
|
||||
var val string
|
||||
err := rawOn.Decode(&val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*Event{
|
||||
{Name: val},
|
||||
}, nil
|
||||
case yaml.SequenceNode:
|
||||
var val []interface{}
|
||||
err := rawOn.Decode(&val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([]*Event, 0, len(val))
|
||||
for _, v := range val {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
res = append(res, &Event{Name: t})
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid type %T", t)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
case yaml.MappingNode:
|
||||
var val map[string]interface{}
|
||||
err := rawOn.Decode(&val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([]*Event, 0, len(val))
|
||||
for k, v := range val {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
res = append(res, &Event{
|
||||
Name: k,
|
||||
Acts: map[string][]string{},
|
||||
})
|
||||
case []string:
|
||||
res = append(res, &Event{
|
||||
Name: k,
|
||||
Acts: map[string][]string{},
|
||||
})
|
||||
case map[string]interface{}:
|
||||
acts := make(map[string][]string, len(t))
|
||||
for act, branches := range t {
|
||||
switch b := branches.(type) {
|
||||
case string:
|
||||
acts[act] = []string{b}
|
||||
case []string:
|
||||
acts[act] = b
|
||||
case []interface{}:
|
||||
acts[act] = make([]string, len(b))
|
||||
for i, v := range b {
|
||||
acts[act][i] = v.(string)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown on type: %#v", branches)
|
||||
}
|
||||
}
|
||||
res = append(res, &Event{
|
||||
Name: k,
|
||||
Acts: acts,
|
||||
})
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown on type: %#v", v)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown on type: %v", rawOn.Kind)
|
||||
}
|
||||
}
|
||||
|
184
pkg/jobparser/model_test.go
Normal file
184
pkg/jobparser/model_test.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package jobparser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/nektos/act/pkg/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseRawOn(t *testing.T) {
|
||||
kases := []struct {
|
||||
input string
|
||||
result []*Event
|
||||
}{
|
||||
{
|
||||
input: "on: issue_comment",
|
||||
result: []*Event{
|
||||
{
|
||||
Name: "issue_comment",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "on:\n push",
|
||||
result: []*Event{
|
||||
{
|
||||
Name: "push",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
input: "on:\n - push\n - pull_request",
|
||||
result: []*Event{
|
||||
{
|
||||
Name: "push",
|
||||
},
|
||||
{
|
||||
Name: "pull_request",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "on:\n push:\n branches:\n - master",
|
||||
result: []*Event{
|
||||
{
|
||||
Name: "push",
|
||||
Acts: map[string][]string{
|
||||
"branches": {
|
||||
"master",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "on:\n branch_protection_rule:\n types: [created, deleted]",
|
||||
result: []*Event{
|
||||
{
|
||||
Name: "branch_protection_rule",
|
||||
Acts: map[string][]string{
|
||||
"types": {
|
||||
"created",
|
||||
"deleted",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "on:\n project:\n types: [created, deleted]\n milestone:\n types: [opened, deleted]",
|
||||
result: []*Event{
|
||||
{
|
||||
Name: "project",
|
||||
Acts: map[string][]string{
|
||||
"types": {
|
||||
"created",
|
||||
"deleted",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "milestone",
|
||||
Acts: map[string][]string{
|
||||
"types": {
|
||||
"opened",
|
||||
"deleted",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "on:\n pull_request:\n types:\n - opened\n branches:\n - 'releases/**'",
|
||||
result: []*Event{
|
||||
{
|
||||
Name: "pull_request",
|
||||
Acts: map[string][]string{
|
||||
"types": {
|
||||
"opened",
|
||||
},
|
||||
"branches": {
|
||||
"releases/**",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "on:\n push:\n branches:\n - main\n pull_request:\n types:\n - opened\n branches:\n - '**'",
|
||||
result: []*Event{
|
||||
{
|
||||
Name: "push",
|
||||
Acts: map[string][]string{
|
||||
"branches": {
|
||||
"main",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "pull_request",
|
||||
Acts: map[string][]string{
|
||||
"types": {
|
||||
"opened",
|
||||
},
|
||||
"branches": {
|
||||
"**",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "on:\n push:\n branches:\n - 'main'\n - 'releases/**'",
|
||||
result: []*Event{
|
||||
{
|
||||
Name: "push",
|
||||
Acts: map[string][]string{
|
||||
"branches": {
|
||||
"main",
|
||||
"releases/**",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "on:\n push:\n tags:\n - v1.**",
|
||||
result: []*Event{
|
||||
{
|
||||
Name: "push",
|
||||
Acts: map[string][]string{
|
||||
"tags": {
|
||||
"v1.**",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "on: [pull_request, workflow_dispatch]",
|
||||
result: []*Event{
|
||||
{
|
||||
Name: "pull_request",
|
||||
},
|
||||
{
|
||||
Name: "workflow_dispatch",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, kase := range kases {
|
||||
t.Run(kase.input, func(t *testing.T) {
|
||||
origin, err := model.ReadWorkflow(strings.NewReader(kase.input))
|
||||
assert.NoError(t, err)
|
||||
|
||||
events, err := ParseRawOn(&origin.RawOn)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, kase.result, events, fmt.Sprintf("%#v", events))
|
||||
})
|
||||
}
|
||||
}
|
@@ -42,6 +42,8 @@ const (
|
||||
ActionRunsUsingDocker = "docker"
|
||||
// ActionRunsUsingComposite for running composite
|
||||
ActionRunsUsingComposite = "composite"
|
||||
// ActionRunsUsingGo for running with go
|
||||
ActionRunsUsingGo = "go"
|
||||
)
|
||||
|
||||
// ActionRuns are a field in Action
|
||||
|
@@ -29,8 +29,10 @@ type actionStep interface {
|
||||
|
||||
type readAction func(ctx context.Context, step *model.Step, actionDir string, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error)
|
||||
|
||||
type actionYamlReader func(filename string) (io.Reader, io.Closer, error)
|
||||
type fileWriter func(filename string, data []byte, perm fs.FileMode) error
|
||||
type (
|
||||
actionYamlReader func(filename string) (io.Reader, io.Closer, error)
|
||||
fileWriter func(filename string, data []byte, perm fs.FileMode) error
|
||||
)
|
||||
|
||||
type runAction func(step actionStep, actionDir string, remoteAction *remoteAction) common.Executor
|
||||
|
||||
@@ -61,7 +63,7 @@ func readActionImpl(ctx context.Context, step *model.Step, actionDir string, act
|
||||
if b, err = trampoline.ReadFile("res/trampoline.js"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err2 := writeFile(filepath.Join(actionDir, actionPath, "trampoline.js"), b, 0400)
|
||||
err2 := writeFile(filepath.Join(actionDir, actionPath, "trampoline.js"), b, 0o400)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
@@ -167,6 +169,13 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
|
||||
}
|
||||
|
||||
return execAsComposite(step)(ctx)
|
||||
case model.ActionRunsUsingGo:
|
||||
if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil {
|
||||
return err
|
||||
}
|
||||
containerArgs := []string{"go", "run", path.Join(containerActionDir, action.Runs.Main)}
|
||||
logger.Debugf("executing remote job container: %s", containerArgs)
|
||||
return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)
|
||||
default:
|
||||
return fmt.Errorf(fmt.Sprintf("The runs.using key must be one of: %v, got %s", []string{
|
||||
model.ActionRunsUsingDocker,
|
||||
|
@@ -44,8 +44,6 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
|
||||
return fmt.Errorf("Expected format {org}/{repo}[/path]@ref. Actual '%s' Input string was not in a correct format", sar.Step.Uses)
|
||||
}
|
||||
|
||||
sar.remoteAction.URL = sar.RunContext.Config.DefaultActionInstance
|
||||
|
||||
github := sar.getGithubContext(ctx)
|
||||
if sar.remoteAction.IsCheckout() && isLocalCheckout(github, sar.Step) && !sar.RunContext.Config.NoSkipCheckout {
|
||||
common.Logger(ctx).Debugf("Skipping local actions/checkout because workdir was already copied")
|
||||
@@ -61,7 +59,7 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
|
||||
|
||||
actionDir := fmt.Sprintf("%s/%s", sar.RunContext.ActionCacheDir(), strings.ReplaceAll(sar.Step.Uses, "/", "-"))
|
||||
gitClone := stepActionRemoteNewCloneExecutor(git.NewGitCloneExecutorInput{
|
||||
URL: sar.remoteAction.CloneURL(),
|
||||
URL: sar.remoteAction.CloneURL(sar.RunContext.Config.DefaultActionInstance),
|
||||
Ref: sar.remoteAction.Ref,
|
||||
Dir: actionDir,
|
||||
Token: "", /*
|
||||
@@ -215,8 +213,11 @@ type remoteAction struct {
|
||||
Ref string
|
||||
}
|
||||
|
||||
func (ra *remoteAction) CloneURL() string {
|
||||
func (ra *remoteAction) CloneURL(defaultURL string) string {
|
||||
u := ra.URL
|
||||
if u == "" {
|
||||
u = defaultURL
|
||||
}
|
||||
if !strings.HasPrefix(u, "http://") && !strings.HasPrefix(u, "https://") {
|
||||
u = "https://" + u
|
||||
}
|
||||
@@ -231,6 +232,26 @@ func (ra *remoteAction) IsCheckout() bool {
|
||||
}
|
||||
|
||||
func newRemoteAction(action string) *remoteAction {
|
||||
// support http(s)://host/owner/repo@v3
|
||||
for _, schema := range []string{"https://", "http://"} {
|
||||
if strings.HasPrefix(action, schema) {
|
||||
splits := strings.SplitN(strings.TrimPrefix(action, schema), "/", 2)
|
||||
if len(splits) != 2 {
|
||||
return nil
|
||||
}
|
||||
ret := parseAction(splits[1])
|
||||
if ret == nil {
|
||||
return nil
|
||||
}
|
||||
ret.URL = schema + splits[0]
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
return parseAction(action)
|
||||
}
|
||||
|
||||
func parseAction(action string) *remoteAction {
|
||||
// GitHub's document[^] describes:
|
||||
// > We strongly recommend that you include the version of
|
||||
// > the action you are using by specifying a Git ref, SHA, or Docker tag number.
|
||||
@@ -246,6 +267,6 @@ func newRemoteAction(action string) *remoteAction {
|
||||
Repo: matches[2],
|
||||
Path: matches[4],
|
||||
Ref: matches[6],
|
||||
URL: "github.com",
|
||||
URL: "",
|
||||
}
|
||||
}
|
||||
|
@@ -623,3 +623,97 @@ func TestStepActionRemotePost(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_newRemoteAction(t *testing.T) {
|
||||
tests := []struct {
|
||||
action string
|
||||
want *remoteAction
|
||||
wantCloneURL string
|
||||
}{
|
||||
{
|
||||
action: "actions/heroku@main",
|
||||
want: &remoteAction{
|
||||
URL: "",
|
||||
Org: "actions",
|
||||
Repo: "heroku",
|
||||
Path: "",
|
||||
Ref: "main",
|
||||
},
|
||||
wantCloneURL: "https://github.com/actions/heroku",
|
||||
},
|
||||
{
|
||||
action: "actions/aws/ec2@main",
|
||||
want: &remoteAction{
|
||||
URL: "",
|
||||
Org: "actions",
|
||||
Repo: "aws",
|
||||
Path: "ec2",
|
||||
Ref: "main",
|
||||
},
|
||||
wantCloneURL: "https://github.com/actions/aws",
|
||||
},
|
||||
{
|
||||
action: "./.github/actions/my-action", // it's valid for GitHub, but act don't support it
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
action: "docker://alpine:3.8", // it's valid for GitHub, but act don't support it
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
action: "https://gitea.com/actions/heroku@main", // it's invalid for GitHub, but gitea supports it
|
||||
want: &remoteAction{
|
||||
URL: "https://gitea.com",
|
||||
Org: "actions",
|
||||
Repo: "heroku",
|
||||
Path: "",
|
||||
Ref: "main",
|
||||
},
|
||||
wantCloneURL: "https://gitea.com/actions/heroku",
|
||||
},
|
||||
{
|
||||
action: "https://gitea.com/actions/aws/ec2@main", // it's invalid for GitHub, but gitea supports it
|
||||
want: &remoteAction{
|
||||
URL: "https://gitea.com",
|
||||
Org: "actions",
|
||||
Repo: "aws",
|
||||
Path: "ec2",
|
||||
Ref: "main",
|
||||
},
|
||||
wantCloneURL: "https://gitea.com/actions/aws",
|
||||
},
|
||||
{
|
||||
action: "http://gitea.com/actions/heroku@main", // it's invalid for GitHub, but gitea supports it
|
||||
want: &remoteAction{
|
||||
URL: "http://gitea.com",
|
||||
Org: "actions",
|
||||
Repo: "heroku",
|
||||
Path: "",
|
||||
Ref: "main",
|
||||
},
|
||||
wantCloneURL: "http://gitea.com/actions/heroku",
|
||||
},
|
||||
{
|
||||
action: "http://gitea.com/actions/aws/ec2@main", // it's invalid for GitHub, but gitea supports it
|
||||
want: &remoteAction{
|
||||
URL: "http://gitea.com",
|
||||
Org: "actions",
|
||||
Repo: "aws",
|
||||
Path: "ec2",
|
||||
Ref: "main",
|
||||
},
|
||||
wantCloneURL: "http://gitea.com/actions/aws",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.action, func(t *testing.T) {
|
||||
got := newRemoteAction(tt.action)
|
||||
assert.Equalf(t, tt.want, got, "newRemoteAction(%v)", tt.action)
|
||||
cloneURL := ""
|
||||
if got != nil {
|
||||
cloneURL = got.CloneURL("github.com")
|
||||
}
|
||||
assert.Equalf(t, tt.wantCloneURL, cloneURL, "newRemoteAction(%v).CloneURL()", tt.action)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user