Skip to content

Commit 02e9443

Browse files
authored
Merge pull request #4 from john-tipper/nektos#2489-filter-workflows-by-events-john
nektos#2489 filter workflows by events john
2 parents 3a65a03 + 9609181 commit 02e9443

17 files changed

+2905
-84
lines changed

cmd/input.go

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ type Input struct {
6262
useNewActionCache bool
6363
localRepository []string
6464
listOptions bool
65+
applyEventFilters bool
6566
passEnvVarsToDockerBuild string
6667
}
6768

cmd/root.go

+28-51
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ func createRootCommand(ctx context.Context, input *Input, version string) *cobra
134134
rootCmd.PersistentFlags().StringVar(&input.passEnvVarsToDockerBuild, customEnvVarFlag, "", fmt.Sprintf("A comma separated list of keys of env vars to pass as build args to build of Docker actions, e.g. %s. Use flag with no value to get these default proxy values.", customEnvVarDefault))
135135
rootCmd.PersistentFlags().Lookup(customEnvVarFlag).NoOptDefVal = customEnvVarDefault
136136

137+
rootCmd.PersistentFlags().BoolVar(&input.applyEventFilters, "apply-event-filters", false, "Only run workflows for an event that matches the event filters defined for the workflow.")
137138
rootCmd.SetArgs(args())
138139
return rootCmd
139140
}
@@ -451,7 +452,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
451452
matrixes := parseMatrix(input.matrix)
452453
log.Debugf("Evaluated matrix inclusions: %v", matrixes)
453454

454-
planner, err := model.NewWorkflowPlanner(input.WorkflowsPath(), input.noWorkflowRecurse)
455+
planner, err := model.NewWorkflowPlanner(input.WorkflowsPath(), input.noWorkflowRecurse, input.applyEventFilters, input.eventPath)
455456
if err != nil {
456457
return err
457458
}
@@ -476,53 +477,6 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
476477
// collect all events from loaded workflows
477478
events := planner.GetEvents()
478479

479-
// plan with filtered jobs - to be used for filtering only
480-
var filterPlan *model.Plan
481-
482-
// Determine the event name to be filtered
483-
var filterEventName string
484-
485-
if len(args) > 0 {
486-
log.Debugf("Using first passed in arguments event for filtering: %s", args[0])
487-
filterEventName = args[0]
488-
} else if input.autodetectEvent && len(events) > 0 && len(events[0]) > 0 {
489-
// set default event type to first event from many available
490-
// this way user dont have to specify the event.
491-
log.Debugf("Using first detected workflow event for filtering: %s", events[0])
492-
filterEventName = events[0]
493-
}
494-
495-
var plannerErr error
496-
if jobID != "" {
497-
log.Debugf("Preparing plan with a job: %s", jobID)
498-
filterPlan, plannerErr = planner.PlanJob(jobID)
499-
} else if filterEventName != "" {
500-
log.Debugf("Preparing plan for a event: %s", filterEventName)
501-
filterPlan, plannerErr = planner.PlanEvent(filterEventName)
502-
} else {
503-
log.Debugf("Preparing plan with all jobs")
504-
filterPlan, plannerErr = planner.PlanAll()
505-
}
506-
if filterPlan == nil && plannerErr != nil {
507-
return plannerErr
508-
}
509-
510-
if list {
511-
err = printList(filterPlan)
512-
if err != nil {
513-
return err
514-
}
515-
return plannerErr
516-
}
517-
518-
if graph {
519-
err = drawGraph(filterPlan)
520-
if err != nil {
521-
return err
522-
}
523-
return plannerErr
524-
}
525-
526480
// plan with triggered jobs
527481
var plan *model.Plan
528482

@@ -546,22 +500,44 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
546500
}
547501

548502
// build the plan for this run
503+
var plannerErr error
549504
if jobID != "" {
550505
log.Debugf("Planning job: %s", jobID)
551-
plan, plannerErr = planner.PlanJob(jobID)
506+
plan, plannerErr = planner.PlanJob(jobID, eventName)
552507
} else {
553508
log.Debugf("Planning jobs for event: %s", eventName)
554509
plan, plannerErr = planner.PlanEvent(eventName)
555510
}
556511
if plan != nil {
557-
if len(plan.Stages) == 0 {
558-
plannerErr = fmt.Errorf("Could not find any stages to run. View the valid jobs with `act --list`. Use `act --help` to find how to filter by Job ID/Workflow/Event Name")
512+
if len(plan.Stages) == 0 && !input.applyEventFilters {
513+
plannerErr = fmt.Errorf("could not find any stages to run. View the valid jobs with `act --list`. Use `act --help` to find how to filter by Job ID/Workflow/Event Name")
559514
}
560515
}
561516
if plan == nil && plannerErr != nil {
562517
return plannerErr
563518
}
564519

520+
if list {
521+
err = printList(plan)
522+
if err != nil {
523+
return err
524+
}
525+
return plannerErr
526+
}
527+
528+
if graph {
529+
err = drawGraph(plan)
530+
if err != nil {
531+
return err
532+
}
533+
return plannerErr
534+
}
535+
536+
if len(plan.Stages) == 0 && input.applyEventFilters {
537+
log.Info("The configured event filters caused all jobs to be skipped - if you are expecting a job to execute then check your supplied event structure against the workflow 'on' filters, or run Act without applying event filters.")
538+
return plannerErr
539+
}
540+
565541
// check to see if the main branch was defined
566542
defaultbranch, err := cmd.Flags().GetString("defaultbranch")
567543
if err != nil {
@@ -642,6 +618,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
642618
ReplaceGheActionTokenWithGithubCom: input.replaceGheActionTokenWithGithubCom,
643619
Matrix: matrixes,
644620
ContainerNetworkMode: docker_container.NetworkMode(input.networkName),
621+
ApplyEventFilters: input.applyEventFilters,
645622
PassEnvVarsToDockerBuild: input.passEnvVarsToDockerBuild,
646623
}
647624
if input.useNewActionCache || len(input.localRepository) > 0 {

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ require (
3838
dario.cat/mergo v1.0.1
3939
github.com/distribution/reference v0.6.0
4040
github.com/golang-jwt/jwt/v5 v5.2.2
41+
github.com/google/go-github/v71 v71.0.0
4142
google.golang.org/protobuf v1.36.6
4243
)
4344

@@ -63,6 +64,7 @@ require (
6364
github.com/gogo/protobuf v1.3.2 // indirect
6465
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
6566
github.com/google/go-cmp v0.7.0 // indirect
67+
github.com/google/go-querystring v1.1.0 // indirect
6668
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
6769
github.com/inconshreveable/mousetrap v1.1.0 // indirect
6870
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect

go.sum

+5
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,13 @@ github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeD
8484
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
8585
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
8686
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
87+
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
8788
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
8889
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
90+
github.com/google/go-github/v71 v71.0.0 h1:Zi16OymGKZZMm8ZliffVVJ/Q9YZreDKONCr+WUd0Z30=
91+
github.com/google/go-github/v71 v71.0.0/go.mod h1:URZXObp2BLlMjwu0O8g4y6VBneUj2bCHgnI8FfgZ51M=
92+
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
93+
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
8994
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
9095
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
9196
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=

pkg/artifacts/server_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ func runTestJobFile(ctx context.Context, t *testing.T, tjfi TestJobFileInfo) {
298298
runner, err := runner.New(runnerConfig)
299299
assert.Nil(t, err, tjfi.workflowPath)
300300

301-
planner, err := model.NewWorkflowPlanner(fullWorkflowPath, true)
301+
planner, err := model.NewWorkflowPlanner(fullWorkflowPath, true, false, "")
302302
assert.Nil(t, err, fullWorkflowPath)
303303

304304
plan, err := planner.PlanEvent(tjfi.eventName)

pkg/model/planner.go

+57-20
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ import (
1616
// WorkflowPlanner contains methods for creating plans
1717
type WorkflowPlanner interface {
1818
PlanEvent(eventName string) (*Plan, error)
19-
PlanJob(jobName string) (*Plan, error)
19+
PlanJob(jobName string, eventName string) (*Plan, error)
2020
PlanAll() (*Plan, error)
2121
GetEvents() []string
22+
ShouldApplyEventFilters() bool
23+
EventPath() string
2224
}
2325

2426
// Plan contains a list of stages to run in series
@@ -56,7 +58,7 @@ type WorkflowFiles struct {
5658
}
5759

5860
// NewWorkflowPlanner will load a specific workflow, all workflows from a directory or all workflows from a directory and its subdirectories
59-
func NewWorkflowPlanner(path string, noWorkflowRecurse bool) (WorkflowPlanner, error) {
61+
func NewWorkflowPlanner(path string, noWorkflowRecurse bool, applyEventFilters bool, eventPath string) (WorkflowPlanner, error) {
6062
path, err := filepath.Abs(path)
6163
if err != nil {
6264
return nil, err
@@ -153,11 +155,12 @@ func NewWorkflowPlanner(path string, noWorkflowRecurse bool) (WorkflowPlanner, e
153155
_ = f.Close()
154156
}
155157
}
156-
158+
wp.shouldApplyEventFilters = applyEventFilters
159+
wp.eventPath = eventPath
157160
return wp, nil
158161
}
159162

160-
func NewSingleWorkflowPlanner(name string, f io.Reader) (WorkflowPlanner, error) {
163+
func NewSingleWorkflowPlanner(name string, f io.Reader, applyEventFilters bool, eventPath string) (WorkflowPlanner, error) {
161164
wp := new(workflowPlanner)
162165

163166
log.Debugf("Reading workflow %s", name)
@@ -179,7 +182,8 @@ func NewSingleWorkflowPlanner(name string, f io.Reader) (WorkflowPlanner, error)
179182
}
180183

181184
wp.workflows = append(wp.workflows, workflow)
182-
185+
wp.shouldApplyEventFilters = applyEventFilters
186+
wp.eventPath = eventPath
183187
return wp, nil
184188
}
185189

@@ -194,7 +198,9 @@ func validateJobName(workflow *Workflow) error {
194198
}
195199

196200
type workflowPlanner struct {
197-
workflows []*Workflow
201+
workflows []*Workflow
202+
shouldApplyEventFilters bool
203+
eventPath string
198204
}
199205

200206
// PlanEvent builds a new list of runs to execute in parallel for an event name
@@ -207,20 +213,39 @@ func (wp *workflowPlanner) PlanEvent(eventName string) (*Plan, error) {
207213
var lastErr error
208214

209215
for _, w := range wp.workflows {
210-
events := w.On()
211-
if len(events) == 0 {
212-
log.Debugf("no events found for workflow: %s", w.File)
213-
continue
214-
}
216+
// the user might want the legacy behaviour so we do this in two sections
217+
if wp.ShouldApplyEventFilters() {
218+
// user has explicitly opted in to maybe not having a workflow run because of the event filters
219+
if w.ShouldFilterForEvent(eventName, wp.EventPath()) {
220+
log.Debugf("Skipping workflow %s because workflow does not have an event filter that matches this event", w.Name)
221+
continue
222+
}
215223

216-
for _, e := range events {
217-
if e == eventName {
218-
stages, err := createStages(w, w.GetJobIDs()...)
219-
if err != nil {
220-
log.Warn(err)
221-
lastErr = err
222-
} else {
223-
plan.mergeStages(stages)
224+
stages, err := createStages(w, w.GetJobIDs()...)
225+
if err != nil {
226+
log.Warn(err)
227+
lastErr = err
228+
} else {
229+
plan.mergeStages(stages)
230+
}
231+
} else {
232+
// the user wants the legacy behaviour which is that if there is a matching on event then we execute it
233+
// even if the filters for branch/tag/path don't match
234+
events := w.On()
235+
if len(events) == 0 {
236+
log.Debugf("no events found for workflow: %s", w.File)
237+
continue
238+
}
239+
240+
for _, e := range events {
241+
if e == eventName {
242+
stages, err := createStages(w, w.GetJobIDs()...)
243+
if err != nil {
244+
log.Warn(err)
245+
lastErr = err
246+
} else {
247+
plan.mergeStages(stages)
248+
}
224249
}
225250
}
226251
}
@@ -229,14 +254,18 @@ func (wp *workflowPlanner) PlanEvent(eventName string) (*Plan, error) {
229254
}
230255

231256
// PlanJob builds a new run to execute in parallel for a job name
232-
func (wp *workflowPlanner) PlanJob(jobName string) (*Plan, error) {
257+
func (wp *workflowPlanner) PlanJob(jobName string, eventName string) (*Plan, error) {
233258
plan := new(Plan)
234259
if len(wp.workflows) == 0 {
235260
log.Debugf("no jobs found for workflow: %s", jobName)
236261
}
237262
var lastErr error
238263

239264
for _, w := range wp.workflows {
265+
if wp.ShouldApplyEventFilters() && w.ShouldFilterForEvent(eventName, wp.EventPath()) {
266+
log.Debugf("Skipping workflow %s because workflow does not have an event filter that matches this event", w.Name)
267+
continue
268+
}
240269
stages, err := createStages(w, jobName)
241270
if err != nil {
242271
log.Warn(err)
@@ -300,6 +329,14 @@ func (wp *workflowPlanner) GetEvents() []string {
300329
return events
301330
}
302331

332+
func (wp *workflowPlanner) ShouldApplyEventFilters() bool {
333+
return wp.shouldApplyEventFilters
334+
}
335+
336+
func (wp *workflowPlanner) EventPath() string {
337+
return wp.eventPath
338+
}
339+
303340
// MaxRunNameLen determines the max name length of all jobs
304341
func (p *Plan) MaxRunNameLen() int {
305342
maxRunNameLen := 0

0 commit comments

Comments
 (0)