4 Commits

Author SHA1 Message Date
badhezi
9924aea786 Evaluate run-name field for workflows (#137)
All checks were successful
checks / check and test (push) Successful in 18s
To support https://github.com/go-gitea/gitea/pull/34301

Reviewed-on: https://gitea.com/gitea/act/pulls/137
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: badhezi <zlilaharon@gmail.com>
Co-committed-by: badhezi <zlilaharon@gmail.com>
2025-05-12 17:17:50 +00:00
Jack Jackson
65c232c4a5 Parse permissions (#133)
Resurrecting [this PR](https://gitea.com/gitea/act/pulls/73) as the original author has [lost motivation](https://github.com/go-gitea/gitea/pull/25664#issuecomment-2737099259) (though, to be clear - all credit belongs to them, all mistakes are mine and mine alone!)

Co-authored-by: Søren L. Hansen <sorenisanerd@gmail.com>
Reviewed-on: https://gitea.com/gitea/act/pulls/133
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Jack Jackson <scubbojj@gmail.com>
Co-committed-by: Jack Jackson <scubbojj@gmail.com>
2025-03-24 18:17:06 +00:00
Guillaume S.
5da4954b65 fix handle missing yaml.ScalarNode (#129)
This bug was reported on https://github.com/go-gitea/gitea/issues/33657
Rewrite of (see below) was missing in this commit 6cdf1e5788
```go
case string:
    acts[act] = []string{b}
```

Reviewed-on: https://gitea.com/gitea/act/pulls/129
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Guillaume S. <me@gsvd.dev>
Co-committed-by: Guillaume S. <me@gsvd.dev>
2025-02-26 06:19:02 +00:00
Zettat123
ec091ad269 Support concurrency (#124)
To support `concurrency` syntax for Gitea Actions

Gitea PR: https://github.com/go-gitea/gitea/pull/32751

Reviewed-on: https://gitea.com/gitea/act/pulls/124
Reviewed-by: Lunny Xiao <lunny@noreply.gitea.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>
2025-02-11 02:51:48 +00:00
9 changed files with 147 additions and 34 deletions

View File

@@ -46,7 +46,6 @@ type Input struct {
artifactServerPort string
noCacheServer bool
cacheServerPath string
cacheServerAdvertiseURL string
cacheServerAddr string
cacheServerPort uint16
jsonLogger bool

View File

@@ -94,7 +94,6 @@ func Execute(ctx context.Context, version string) {
rootCmd.PersistentFlags().BoolVarP(&input.noSkipCheckout, "no-skip-checkout", "", false, "Do not skip actions/checkout")
rootCmd.PersistentFlags().BoolVarP(&input.noCacheServer, "no-cache-server", "", false, "Disable cache server")
rootCmd.PersistentFlags().StringVarP(&input.cacheServerPath, "cache-server-path", "", filepath.Join(CacheHomeDir, "actcache"), "Defines the path where the cache server stores caches.")
rootCmd.PersistentFlags().StringVarP(&input.cacheServerAdvertiseURL, "cache-server-advertise-url", "", "", "Defines the URL for advertising the cache server behind a proxy. e.g.: https://act-cache-server.example.com")
rootCmd.PersistentFlags().StringVarP(&input.cacheServerAddr, "cache-server-addr", "", common.GetOutboundIP().String(), "Defines the address to which the cache server binds.")
rootCmd.PersistentFlags().Uint16VarP(&input.cacheServerPort, "cache-server-port", "", 0, "Defines the port where the artifact server listens. 0 means a randomly available port.")
rootCmd.PersistentFlags().StringVarP(&input.actionCachePath, "action-cache-path", "", filepath.Join(CacheHomeDir, "act"), "Defines the path where the actions get cached and host workspaces created.")
@@ -599,7 +598,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
var cacheHandler *artifactcache.Handler
if !input.noCacheServer && envs[cacheURLKey] == "" {
var err error
cacheHandler, err = artifactcache.StartHandler(input.cacheServerPath, input.cacheServerAdvertiseURL, input.cacheServerAddr, input.cacheServerPort, common.Logger(ctx))
cacheHandler, err = artifactcache.StartHandler(input.cacheServerPath, input.cacheServerAddr, input.cacheServerPort, common.Logger(ctx))
if err != nil {
return err
}

View File

@@ -39,10 +39,9 @@ type Handler struct {
gcAt time.Time
outboundIP string
advertiseURL string
}
func StartHandler(dir, advertiseURL, outboundIP string, port uint16, logger logrus.FieldLogger) (*Handler, error) {
func StartHandler(dir, outboundIP string, port uint16, logger logrus.FieldLogger) (*Handler, error) {
h := &Handler{}
if logger == nil {
@@ -72,8 +71,6 @@ func StartHandler(dir, advertiseURL, outboundIP string, port uint16, logger logr
}
h.storage = storage
h.advertiseURL = advertiseURL
if outboundIP != "" {
h.outboundIP = outboundIP
} else if ip := common.GetOutboundIP(); ip == nil {
@@ -114,14 +111,11 @@ func StartHandler(dir, advertiseURL, outboundIP string, port uint16, logger logr
}
func (h *Handler) ExternalURL() string {
if h.advertiseURL != "" {
return h.advertiseURL
} else {
// TODO: make the external url configurable if necessary
return fmt.Sprintf("http://%s:%d",
h.outboundIP,
h.listener.Addr().(*net.TCPAddr).Port)
}
}
func (h *Handler) Close() error {
if h == nil {

View File

@@ -20,7 +20,7 @@ import (
func TestHandler(t *testing.T) {
dir := filepath.Join(t.TempDir(), "artifactcache")
handler, err := StartHandler(dir, "", "", 0, nil)
handler, err := StartHandler(dir, "", 0, nil)
require.NoError(t, err)
base := fmt.Sprintf("%s%s", handler.ExternalURL(), urlBase)
@@ -589,7 +589,7 @@ func uploadCacheNormally(t *testing.T, base, key, version string, content []byte
func TestHandler_gcCache(t *testing.T) {
dir := filepath.Join(t.TempDir(), "artifactcache")
handler, err := StartHandler(dir, "", "", 0, nil)
handler, err := StartHandler(dir, "", 0, nil)
require.NoError(t, err)
defer func() {

View File

@@ -16,6 +16,7 @@ func NewInterpeter(
gitCtx *model.GithubContext,
results map[string]*JobResult,
vars map[string]string,
inputs map[string]interface{},
) exprparser.Interpreter {
strategy := make(map[string]interface{})
if job.Strategy != nil {
@@ -62,7 +63,7 @@ func NewInterpeter(
Strategy: strategy,
Matrix: matrix,
Needs: using,
Inputs: nil, // not supported yet
Inputs: inputs,
Vars: vars,
}

View File

@@ -8,6 +8,7 @@ import (
"gopkg.in/yaml.v3"
"github.com/nektos/act/pkg/exprparser"
"github.com/nektos/act/pkg/model"
)
@@ -40,6 +41,10 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
if err != nil {
return nil, fmt.Errorf("invalid jobs: %w", err)
}
evaluator := NewExpressionEvaluator(exprparser.NewInterpeter(&exprparser.EvaluationEnvironment{Github: pc.gitContext, Vars: pc.vars}, exprparser.Config{}))
workflow.RunName = evaluator.Interpolate(workflow.RunName)
for i, id := range ids {
job := jobs[i]
matricxes, err := getMatrixes(origin.GetJob(id))
@@ -52,7 +57,7 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
job.Name = id
}
job.Strategy.RawMatrix = encodeMatrix(matrix)
evaluator := NewExpressionEvaluator(NewInterpeter(id, origin.GetJob(id), matrix, pc.gitContext, results, pc.vars))
evaluator := NewExpressionEvaluator(NewInterpeter(id, origin.GetJob(id), matrix, pc.gitContext, results, pc.vars, nil))
job.Name = nameWithMatrix(job.Name, matrix, evaluator)
runsOn := origin.GetJob(id).RunsOn()
for i, v := range runsOn {
@@ -64,6 +69,8 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
RawOn: workflow.RawOn,
Env: workflow.Env,
Defaults: workflow.Defaults,
RawPermissions: workflow.RawPermissions,
RunName: workflow.RunName,
}
if err := swf.SetJob(id, job); err != nil {
return nil, fmt.Errorf("SetJob: %w", err)

View File

@@ -1,6 +1,7 @@
package jobparser
import (
"bytes"
"fmt"
"github.com/nektos/act/pkg/model"
@@ -14,6 +15,8 @@ type SingleWorkflow struct {
Env map[string]string `yaml:"env,omitempty"`
RawJobs yaml.Node `yaml:"jobs,omitempty"`
Defaults Defaults `yaml:"defaults,omitempty"`
RawPermissions yaml.Node `yaml:"permissions,omitempty"`
RunName string `yaml:"run-name,omitempty"`
}
func (w *SingleWorkflow) Job() (string, *Job) {
@@ -82,6 +85,8 @@ type Job struct {
Uses string `yaml:"uses,omitempty"`
With map[string]interface{} `yaml:"with,omitempty"`
RawSecrets yaml.Node `yaml:"secrets,omitempty"`
RawConcurrency *model.RawConcurrency `yaml:"concurrency,omitempty"`
RawPermissions yaml.Node `yaml:"permissions,omitempty"`
}
func (j *Job) Clone() *Job {
@@ -104,6 +109,8 @@ func (j *Job) Clone() *Job {
Uses: j.Uses,
With: j.With,
RawSecrets: j.RawSecrets,
RawConcurrency: j.RawConcurrency,
RawPermissions: j.RawPermissions,
}
}
@@ -241,6 +248,73 @@ func parseWorkflowDispatchInputs(inputs map[string]interface{}) ([]WorkflowDispa
return results, nil
}
func ReadWorkflowRawConcurrency(content []byte) (*model.RawConcurrency, error) {
w := new(model.Workflow)
err := yaml.NewDecoder(bytes.NewReader(content)).Decode(w)
return w.RawConcurrency, err
}
func EvaluateConcurrency(rc *model.RawConcurrency, jobID string, job *Job, gitCtx map[string]any, results map[string]*JobResult, vars map[string]string, inputs map[string]any) (string, bool, error) {
actJob := &model.Job{}
if job != nil {
actJob.Strategy = &model.Strategy{
FailFastString: job.Strategy.FailFastString,
MaxParallelString: job.Strategy.MaxParallelString,
RawMatrix: job.Strategy.RawMatrix,
}
actJob.Strategy.FailFast = actJob.Strategy.GetFailFast()
actJob.Strategy.MaxParallel = actJob.Strategy.GetMaxParallel()
}
matrix := make(map[string]any)
matrixes, err := actJob.GetMatrixes()
if err != nil {
return "", false, err
}
if len(matrixes) > 0 {
matrix = matrixes[0]
}
evaluator := NewExpressionEvaluator(NewInterpeter(jobID, actJob, matrix, toGitContext(gitCtx), results, vars, inputs))
group := evaluator.Interpolate(rc.Group)
cancelInProgress := evaluator.Interpolate(rc.CancelInProgress)
return group, cancelInProgress == "true", nil
}
func toGitContext(input map[string]any) *model.GithubContext {
gitContext := &model.GithubContext{
EventPath: asString(input["event_path"]),
Workflow: asString(input["workflow"]),
RunID: asString(input["run_id"]),
RunNumber: asString(input["run_number"]),
Actor: asString(input["actor"]),
Repository: asString(input["repository"]),
EventName: asString(input["event_name"]),
Sha: asString(input["sha"]),
Ref: asString(input["ref"]),
RefName: asString(input["ref_name"]),
RefType: asString(input["ref_type"]),
HeadRef: asString(input["head_ref"]),
BaseRef: asString(input["base_ref"]),
Token: asString(input["token"]),
Workspace: asString(input["workspace"]),
Action: asString(input["action"]),
ActionPath: asString(input["action_path"]),
ActionRef: asString(input["action_ref"]),
ActionRepository: asString(input["action_repository"]),
Job: asString(input["job"]),
RepositoryOwner: asString(input["repository_owner"]),
RetentionDays: asString(input["retention_days"]),
}
event, ok := input["event"].(map[string]any)
if ok {
gitContext.Event = event
}
return gitContext
}
func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) {
switch rawOn.Kind {
case yaml.ScalarNode:
@@ -335,6 +409,13 @@ func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) {
return nil, err
}
acts[act] = t
case yaml.ScalarNode:
var t string
err := content.Decode(&t)
if err != nil {
return nil, err
}
acts[act] = []string{t}
case yaml.MappingNode:
if k != "workflow_dispatch" || act != "inputs" {
return nil, fmt.Errorf("map should only for workflow_dispatch but %s: %#v", act, content)
@@ -422,3 +503,12 @@ func parseMappingNode[T any](node *yaml.Node) ([]string, []T, error) {
return scalars, datas, nil
}
func asString(v interface{}) string {
if v == nil {
return ""
} else if s, ok := v.(string); ok {
return s
}
return ""
}

View File

@@ -58,6 +58,19 @@ func TestParseRawOn(t *testing.T) {
},
},
},
{
input: "on:\n push:\n branches: main",
result: []*Event{
{
Name: "push",
acts: map[string][]string{
"branches": {
"main",
},
},
},
},
},
{
input: "on:\n branch_protection_rule:\n types: [created, deleted]",
result: []*Event{

View File

@@ -23,6 +23,8 @@ type Workflow struct {
Env map[string]string `yaml:"env"`
Jobs map[string]*Job `yaml:"jobs"`
Defaults Defaults `yaml:"defaults"`
RawConcurrency *RawConcurrency `yaml:"concurrency"`
RawPermissions yaml.Node `yaml:"permissions"`
}
// On events for the workflow
@@ -199,6 +201,7 @@ type Job struct {
Uses string `yaml:"uses"`
With map[string]interface{} `yaml:"with"`
RawSecrets yaml.Node `yaml:"secrets"`
RawPermissions yaml.Node `yaml:"permissions"`
Result string
}
@@ -769,3 +772,10 @@ func decodeNode(node yaml.Node, out interface{}) bool {
}
return true
}
// For Gitea
// RawConcurrency represents a workflow concurrency or a job concurrency with uninterpolated options
type RawConcurrency struct {
Group string `yaml:"group,omitempty"`
CancelInProgress string `yaml:"cancel-in-progress,omitempty"`
}