code.gitea.io/gitea@v1.22.3/modules/actions/workflows.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package actions
     5  
     6  import (
     7  	"bytes"
     8  	"io"
     9  	"strings"
    10  
    11  	"code.gitea.io/gitea/modules/git"
    12  	"code.gitea.io/gitea/modules/log"
    13  	api "code.gitea.io/gitea/modules/structs"
    14  	webhook_module "code.gitea.io/gitea/modules/webhook"
    15  
    16  	"github.com/gobwas/glob"
    17  	"github.com/nektos/act/pkg/jobparser"
    18  	"github.com/nektos/act/pkg/model"
    19  	"github.com/nektos/act/pkg/workflowpattern"
    20  	"gopkg.in/yaml.v3"
    21  )
    22  
    23  type DetectedWorkflow struct {
    24  	EntryName    string
    25  	TriggerEvent *jobparser.Event
    26  	Content      []byte
    27  }
    28  
    29  func init() {
    30  	model.OnDecodeNodeError = func(node yaml.Node, out any, err error) {
    31  		// Log the error instead of panic or fatal.
    32  		// It will be a big job to refactor act/pkg/model to return decode error,
    33  		// so we just log the error and return empty value, and improve it later.
    34  		log.Error("Failed to decode node %v into %T: %v", node, out, err)
    35  	}
    36  }
    37  
    38  func IsWorkflow(path string) bool {
    39  	if (!strings.HasSuffix(path, ".yaml")) && (!strings.HasSuffix(path, ".yml")) {
    40  		return false
    41  	}
    42  
    43  	return strings.HasPrefix(path, ".gitea/workflows") || strings.HasPrefix(path, ".github/workflows")
    44  }
    45  
    46  func ListWorkflows(commit *git.Commit) (git.Entries, error) {
    47  	tree, err := commit.SubTree(".gitea/workflows")
    48  	if _, ok := err.(git.ErrNotExist); ok {
    49  		tree, err = commit.SubTree(".github/workflows")
    50  	}
    51  	if _, ok := err.(git.ErrNotExist); ok {
    52  		return nil, nil
    53  	}
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	entries, err := tree.ListEntriesRecursiveFast()
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	ret := make(git.Entries, 0, len(entries))
    64  	for _, entry := range entries {
    65  		if strings.HasSuffix(entry.Name(), ".yml") || strings.HasSuffix(entry.Name(), ".yaml") {
    66  			ret = append(ret, entry)
    67  		}
    68  	}
    69  	return ret, nil
    70  }
    71  
    72  func GetContentFromEntry(entry *git.TreeEntry) ([]byte, error) {
    73  	f, err := entry.Blob().DataAsync()
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  	content, err := io.ReadAll(f)
    78  	_ = f.Close()
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	return content, nil
    83  }
    84  
    85  func GetEventsFromContent(content []byte) ([]*jobparser.Event, error) {
    86  	workflow, err := model.ReadWorkflow(bytes.NewReader(content))
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	events, err := jobparser.ParseRawOn(&workflow.RawOn)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	return events, nil
    96  }
    97  
    98  func DetectWorkflows(
    99  	gitRepo *git.Repository,
   100  	commit *git.Commit,
   101  	triggedEvent webhook_module.HookEventType,
   102  	payload api.Payloader,
   103  	detectSchedule bool,
   104  ) ([]*DetectedWorkflow, []*DetectedWorkflow, error) {
   105  	entries, err := ListWorkflows(commit)
   106  	if err != nil {
   107  		return nil, nil, err
   108  	}
   109  
   110  	workflows := make([]*DetectedWorkflow, 0, len(entries))
   111  	schedules := make([]*DetectedWorkflow, 0, len(entries))
   112  	for _, entry := range entries {
   113  		content, err := GetContentFromEntry(entry)
   114  		if err != nil {
   115  			return nil, nil, err
   116  		}
   117  
   118  		// one workflow may have multiple events
   119  		events, err := GetEventsFromContent(content)
   120  		if err != nil {
   121  			log.Warn("ignore invalid workflow %q: %v", entry.Name(), err)
   122  			continue
   123  		}
   124  		for _, evt := range events {
   125  			log.Trace("detect workflow %q for event %#v matching %q", entry.Name(), evt, triggedEvent)
   126  			if evt.IsSchedule() {
   127  				if detectSchedule {
   128  					dwf := &DetectedWorkflow{
   129  						EntryName:    entry.Name(),
   130  						TriggerEvent: evt,
   131  						Content:      content,
   132  					}
   133  					schedules = append(schedules, dwf)
   134  				}
   135  			} else if detectMatched(gitRepo, commit, triggedEvent, payload, evt) {
   136  				dwf := &DetectedWorkflow{
   137  					EntryName:    entry.Name(),
   138  					TriggerEvent: evt,
   139  					Content:      content,
   140  				}
   141  				workflows = append(workflows, dwf)
   142  			}
   143  		}
   144  	}
   145  
   146  	return workflows, schedules, nil
   147  }
   148  
   149  func DetectScheduledWorkflows(gitRepo *git.Repository, commit *git.Commit) ([]*DetectedWorkflow, error) {
   150  	entries, err := ListWorkflows(commit)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	wfs := make([]*DetectedWorkflow, 0, len(entries))
   156  	for _, entry := range entries {
   157  		content, err := GetContentFromEntry(entry)
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  
   162  		// one workflow may have multiple events
   163  		events, err := GetEventsFromContent(content)
   164  		if err != nil {
   165  			log.Warn("ignore invalid workflow %q: %v", entry.Name(), err)
   166  			continue
   167  		}
   168  		for _, evt := range events {
   169  			if evt.IsSchedule() {
   170  				log.Trace("detect scheduled workflow: %q", entry.Name())
   171  				dwf := &DetectedWorkflow{
   172  					EntryName:    entry.Name(),
   173  					TriggerEvent: evt,
   174  					Content:      content,
   175  				}
   176  				wfs = append(wfs, dwf)
   177  			}
   178  		}
   179  	}
   180  
   181  	return wfs, nil
   182  }
   183  
   184  func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent webhook_module.HookEventType, payload api.Payloader, evt *jobparser.Event) bool {
   185  	if !canGithubEventMatch(evt.Name, triggedEvent) {
   186  		return false
   187  	}
   188  
   189  	switch triggedEvent {
   190  	case // events with no activity types
   191  		webhook_module.HookEventCreate,
   192  		webhook_module.HookEventDelete,
   193  		webhook_module.HookEventFork,
   194  		webhook_module.HookEventWiki,
   195  		webhook_module.HookEventSchedule:
   196  		if len(evt.Acts()) != 0 {
   197  			log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts())
   198  		}
   199  		// no special filter parameters for these events, just return true if name matched
   200  		return true
   201  
   202  	case // push
   203  		webhook_module.HookEventPush:
   204  		return matchPushEvent(commit, payload.(*api.PushPayload), evt)
   205  
   206  	case // issues
   207  		webhook_module.HookEventIssues,
   208  		webhook_module.HookEventIssueAssign,
   209  		webhook_module.HookEventIssueLabel,
   210  		webhook_module.HookEventIssueMilestone:
   211  		return matchIssuesEvent(commit, payload.(*api.IssuePayload), evt)
   212  
   213  	case // issue_comment
   214  		webhook_module.HookEventIssueComment,
   215  		// `pull_request_comment` is same as `issue_comment`
   216  		// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment
   217  		webhook_module.HookEventPullRequestComment:
   218  		return matchIssueCommentEvent(commit, payload.(*api.IssueCommentPayload), evt)
   219  
   220  	case // pull_request
   221  		webhook_module.HookEventPullRequest,
   222  		webhook_module.HookEventPullRequestSync,
   223  		webhook_module.HookEventPullRequestAssign,
   224  		webhook_module.HookEventPullRequestLabel,
   225  		webhook_module.HookEventPullRequestReviewRequest,
   226  		webhook_module.HookEventPullRequestMilestone:
   227  		return matchPullRequestEvent(gitRepo, commit, payload.(*api.PullRequestPayload), evt)
   228  
   229  	case // pull_request_review
   230  		webhook_module.HookEventPullRequestReviewApproved,
   231  		webhook_module.HookEventPullRequestReviewRejected:
   232  		return matchPullRequestReviewEvent(commit, payload.(*api.PullRequestPayload), evt)
   233  
   234  	case // pull_request_review_comment
   235  		webhook_module.HookEventPullRequestReviewComment:
   236  		return matchPullRequestReviewCommentEvent(commit, payload.(*api.PullRequestPayload), evt)
   237  
   238  	case // release
   239  		webhook_module.HookEventRelease:
   240  		return matchReleaseEvent(commit, payload.(*api.ReleasePayload), evt)
   241  
   242  	case // registry_package
   243  		webhook_module.HookEventPackage:
   244  		return matchPackageEvent(commit, payload.(*api.PackagePayload), evt)
   245  
   246  	default:
   247  		log.Warn("unsupported event %q", triggedEvent)
   248  		return false
   249  	}
   250  }
   251  
   252  func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobparser.Event) bool {
   253  	// with no special filter parameters
   254  	if len(evt.Acts()) == 0 {
   255  		return true
   256  	}
   257  
   258  	matchTimes := 0
   259  	hasBranchFilter := false
   260  	hasTagFilter := false
   261  	refName := git.RefName(pushPayload.Ref)
   262  	// all acts conditions should be satisfied
   263  	for cond, vals := range evt.Acts() {
   264  		switch cond {
   265  		case "branches":
   266  			hasBranchFilter = true
   267  			if !refName.IsBranch() {
   268  				break
   269  			}
   270  			patterns, err := workflowpattern.CompilePatterns(vals...)
   271  			if err != nil {
   272  				break
   273  			}
   274  			if !workflowpattern.Skip(patterns, []string{refName.BranchName()}, &workflowpattern.EmptyTraceWriter{}) {
   275  				matchTimes++
   276  			}
   277  		case "branches-ignore":
   278  			hasBranchFilter = true
   279  			if !refName.IsBranch() {
   280  				break
   281  			}
   282  			patterns, err := workflowpattern.CompilePatterns(vals...)
   283  			if err != nil {
   284  				break
   285  			}
   286  			if !workflowpattern.Filter(patterns, []string{refName.BranchName()}, &workflowpattern.EmptyTraceWriter{}) {
   287  				matchTimes++
   288  			}
   289  		case "tags":
   290  			hasTagFilter = true
   291  			if !refName.IsTag() {
   292  				break
   293  			}
   294  			patterns, err := workflowpattern.CompilePatterns(vals...)
   295  			if err != nil {
   296  				break
   297  			}
   298  			if !workflowpattern.Skip(patterns, []string{refName.TagName()}, &workflowpattern.EmptyTraceWriter{}) {
   299  				matchTimes++
   300  			}
   301  		case "tags-ignore":
   302  			hasTagFilter = true
   303  			if !refName.IsTag() {
   304  				break
   305  			}
   306  			patterns, err := workflowpattern.CompilePatterns(vals...)
   307  			if err != nil {
   308  				break
   309  			}
   310  			if !workflowpattern.Filter(patterns, []string{refName.TagName()}, &workflowpattern.EmptyTraceWriter{}) {
   311  				matchTimes++
   312  			}
   313  		case "paths":
   314  			filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before)
   315  			if err != nil {
   316  				log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err)
   317  			} else {
   318  				patterns, err := workflowpattern.CompilePatterns(vals...)
   319  				if err != nil {
   320  					break
   321  				}
   322  				if !workflowpattern.Skip(patterns, filesChanged, &workflowpattern.EmptyTraceWriter{}) {
   323  					matchTimes++
   324  				}
   325  			}
   326  		case "paths-ignore":
   327  			filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before)
   328  			if err != nil {
   329  				log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err)
   330  			} else {
   331  				patterns, err := workflowpattern.CompilePatterns(vals...)
   332  				if err != nil {
   333  					break
   334  				}
   335  				if !workflowpattern.Filter(patterns, filesChanged, &workflowpattern.EmptyTraceWriter{}) {
   336  					matchTimes++
   337  				}
   338  			}
   339  		default:
   340  			log.Warn("push event unsupported condition %q", cond)
   341  		}
   342  	}
   343  	// if both branch and tag filter are defined in the workflow only one needs to match
   344  	if hasBranchFilter && hasTagFilter {
   345  		matchTimes++
   346  	}
   347  	return matchTimes == len(evt.Acts())
   348  }
   349  
   350  func matchIssuesEvent(commit *git.Commit, issuePayload *api.IssuePayload, evt *jobparser.Event) bool {
   351  	// with no special filter parameters
   352  	if len(evt.Acts()) == 0 {
   353  		return true
   354  	}
   355  
   356  	matchTimes := 0
   357  	// all acts conditions should be satisfied
   358  	for cond, vals := range evt.Acts() {
   359  		switch cond {
   360  		case "types":
   361  			// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issues
   362  			// Actions with the same name:
   363  			// opened, edited, closed, reopened, assigned, unassigned, milestoned, demilestoned
   364  			// Actions need to be converted:
   365  			// label_updated -> labeled
   366  			// label_cleared -> unlabeled
   367  			// Unsupported activity types:
   368  			// deleted, transferred, pinned, unpinned, locked, unlocked
   369  
   370  			action := issuePayload.Action
   371  			switch action {
   372  			case api.HookIssueLabelUpdated:
   373  				action = "labeled"
   374  			case api.HookIssueLabelCleared:
   375  				action = "unlabeled"
   376  			}
   377  			for _, val := range vals {
   378  				if glob.MustCompile(val, '/').Match(string(action)) {
   379  					matchTimes++
   380  					break
   381  				}
   382  			}
   383  		default:
   384  			log.Warn("issue event unsupported condition %q", cond)
   385  		}
   386  	}
   387  	return matchTimes == len(evt.Acts())
   388  }
   389  
   390  func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayload *api.PullRequestPayload, evt *jobparser.Event) bool {
   391  	acts := evt.Acts()
   392  	activityTypeMatched := false
   393  	matchTimes := 0
   394  
   395  	if vals, ok := acts["types"]; !ok {
   396  		// defaultly, only pull request `opened`, `reopened` and `synchronized` will trigger workflow
   397  		// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
   398  		activityTypeMatched = prPayload.Action == api.HookIssueSynchronized || prPayload.Action == api.HookIssueOpened || prPayload.Action == api.HookIssueReOpened
   399  	} else {
   400  		// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
   401  		// Actions with the same name:
   402  		// opened, edited, closed, reopened, assigned, unassigned, review_requested, review_request_removed, milestoned, demilestoned
   403  		// Actions need to be converted:
   404  		// synchronized -> synchronize
   405  		// label_updated -> labeled
   406  		// label_cleared -> unlabeled
   407  		// Unsupported activity types:
   408  		// converted_to_draft, ready_for_review, locked, unlocked, auto_merge_enabled, auto_merge_disabled, enqueued, dequeued
   409  
   410  		action := prPayload.Action
   411  		switch action {
   412  		case api.HookIssueSynchronized:
   413  			action = "synchronize"
   414  		case api.HookIssueLabelUpdated:
   415  			action = "labeled"
   416  		case api.HookIssueLabelCleared:
   417  			action = "unlabeled"
   418  		}
   419  		log.Trace("matching pull_request %s with %v", action, vals)
   420  		for _, val := range vals {
   421  			if glob.MustCompile(val, '/').Match(string(action)) {
   422  				activityTypeMatched = true
   423  				matchTimes++
   424  				break
   425  			}
   426  		}
   427  	}
   428  
   429  	var (
   430  		headCommit = commit
   431  		err        error
   432  	)
   433  	if evt.Name == GithubEventPullRequestTarget && (len(acts["paths"]) > 0 || len(acts["paths-ignore"]) > 0) {
   434  		headCommit, err = gitRepo.GetCommit(prPayload.PullRequest.Head.Sha)
   435  		if err != nil {
   436  			log.Error("GetCommit [ref: %s]: %v", prPayload.PullRequest.Head.Sha, err)
   437  			return false
   438  		}
   439  	}
   440  
   441  	// all acts conditions should be satisfied
   442  	for cond, vals := range acts {
   443  		switch cond {
   444  		case "types":
   445  			// types have been checked
   446  			continue
   447  		case "branches":
   448  			refName := git.RefName(prPayload.PullRequest.Base.Ref)
   449  			patterns, err := workflowpattern.CompilePatterns(vals...)
   450  			if err != nil {
   451  				break
   452  			}
   453  			if !workflowpattern.Skip(patterns, []string{refName.ShortName()}, &workflowpattern.EmptyTraceWriter{}) {
   454  				matchTimes++
   455  			}
   456  		case "branches-ignore":
   457  			refName := git.RefName(prPayload.PullRequest.Base.Ref)
   458  			patterns, err := workflowpattern.CompilePatterns(vals...)
   459  			if err != nil {
   460  				break
   461  			}
   462  			if !workflowpattern.Filter(patterns, []string{refName.ShortName()}, &workflowpattern.EmptyTraceWriter{}) {
   463  				matchTimes++
   464  			}
   465  		case "paths":
   466  			filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref)
   467  			if err != nil {
   468  				log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
   469  			} else {
   470  				patterns, err := workflowpattern.CompilePatterns(vals...)
   471  				if err != nil {
   472  					break
   473  				}
   474  				if !workflowpattern.Skip(patterns, filesChanged, &workflowpattern.EmptyTraceWriter{}) {
   475  					matchTimes++
   476  				}
   477  			}
   478  		case "paths-ignore":
   479  			filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref)
   480  			if err != nil {
   481  				log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
   482  			} else {
   483  				patterns, err := workflowpattern.CompilePatterns(vals...)
   484  				if err != nil {
   485  					break
   486  				}
   487  				if !workflowpattern.Filter(patterns, filesChanged, &workflowpattern.EmptyTraceWriter{}) {
   488  					matchTimes++
   489  				}
   490  			}
   491  		default:
   492  			log.Warn("pull request event unsupported condition %q", cond)
   493  		}
   494  	}
   495  	return activityTypeMatched && matchTimes == len(evt.Acts())
   496  }
   497  
   498  func matchIssueCommentEvent(commit *git.Commit, issueCommentPayload *api.IssueCommentPayload, evt *jobparser.Event) bool {
   499  	// with no special filter parameters
   500  	if len(evt.Acts()) == 0 {
   501  		return true
   502  	}
   503  
   504  	matchTimes := 0
   505  	// all acts conditions should be satisfied
   506  	for cond, vals := range evt.Acts() {
   507  		switch cond {
   508  		case "types":
   509  			// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issue_comment
   510  			// Actions with the same name:
   511  			// created, edited, deleted
   512  			// Actions need to be converted:
   513  			// NONE
   514  			// Unsupported activity types:
   515  			// NONE
   516  
   517  			for _, val := range vals {
   518  				if glob.MustCompile(val, '/').Match(string(issueCommentPayload.Action)) {
   519  					matchTimes++
   520  					break
   521  				}
   522  			}
   523  		default:
   524  			log.Warn("issue comment event unsupported condition %q", cond)
   525  		}
   526  	}
   527  	return matchTimes == len(evt.Acts())
   528  }
   529  
   530  func matchPullRequestReviewEvent(commit *git.Commit, prPayload *api.PullRequestPayload, evt *jobparser.Event) bool {
   531  	// with no special filter parameters
   532  	if len(evt.Acts()) == 0 {
   533  		return true
   534  	}
   535  
   536  	matchTimes := 0
   537  	// all acts conditions should be satisfied
   538  	for cond, vals := range evt.Acts() {
   539  		switch cond {
   540  		case "types":
   541  			// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_review
   542  			// Activity types with the same name:
   543  			// NONE
   544  			// Activity types need to be converted:
   545  			// reviewed -> submitted
   546  			// reviewed -> edited
   547  			// Unsupported activity types:
   548  			// dismissed
   549  
   550  			actions := make([]string, 0)
   551  			if prPayload.Action == api.HookIssueReviewed {
   552  				// the `reviewed` HookIssueAction can match the two activity types: `submitted` and `edited`
   553  				// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_review
   554  				actions = append(actions, "submitted", "edited")
   555  			}
   556  
   557  			matched := false
   558  			for _, val := range vals {
   559  				for _, action := range actions {
   560  					if glob.MustCompile(val, '/').Match(action) {
   561  						matched = true
   562  						break
   563  					}
   564  				}
   565  				if matched {
   566  					break
   567  				}
   568  			}
   569  			if matched {
   570  				matchTimes++
   571  			}
   572  		default:
   573  			log.Warn("pull request review event unsupported condition %q", cond)
   574  		}
   575  	}
   576  	return matchTimes == len(evt.Acts())
   577  }
   578  
   579  func matchPullRequestReviewCommentEvent(commit *git.Commit, prPayload *api.PullRequestPayload, evt *jobparser.Event) bool {
   580  	// with no special filter parameters
   581  	if len(evt.Acts()) == 0 {
   582  		return true
   583  	}
   584  
   585  	matchTimes := 0
   586  	// all acts conditions should be satisfied
   587  	for cond, vals := range evt.Acts() {
   588  		switch cond {
   589  		case "types":
   590  			// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_review_comment
   591  			// Activity types with the same name:
   592  			// NONE
   593  			// Activity types need to be converted:
   594  			// reviewed -> created
   595  			// reviewed -> edited
   596  			// Unsupported activity types:
   597  			// deleted
   598  
   599  			actions := make([]string, 0)
   600  			if prPayload.Action == api.HookIssueReviewed {
   601  				// the `reviewed` HookIssueAction can match the two activity types: `created` and `edited`
   602  				// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_review_comment
   603  				actions = append(actions, "created", "edited")
   604  			}
   605  
   606  			matched := false
   607  			for _, val := range vals {
   608  				for _, action := range actions {
   609  					if glob.MustCompile(val, '/').Match(action) {
   610  						matched = true
   611  						break
   612  					}
   613  				}
   614  				if matched {
   615  					break
   616  				}
   617  			}
   618  			if matched {
   619  				matchTimes++
   620  			}
   621  		default:
   622  			log.Warn("pull request review comment event unsupported condition %q", cond)
   623  		}
   624  	}
   625  	return matchTimes == len(evt.Acts())
   626  }
   627  
   628  func matchReleaseEvent(commit *git.Commit, payload *api.ReleasePayload, evt *jobparser.Event) bool {
   629  	// with no special filter parameters
   630  	if len(evt.Acts()) == 0 {
   631  		return true
   632  	}
   633  
   634  	matchTimes := 0
   635  	// all acts conditions should be satisfied
   636  	for cond, vals := range evt.Acts() {
   637  		switch cond {
   638  		case "types":
   639  			// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#release
   640  			// Activity types with the same name:
   641  			// published
   642  			// Activity types need to be converted:
   643  			// updated -> edited
   644  			// Unsupported activity types:
   645  			// unpublished, created, deleted, prereleased, released
   646  
   647  			action := payload.Action
   648  			switch action {
   649  			case api.HookReleaseUpdated:
   650  				action = "edited"
   651  			}
   652  			for _, val := range vals {
   653  				if glob.MustCompile(val, '/').Match(string(action)) {
   654  					matchTimes++
   655  					break
   656  				}
   657  			}
   658  		default:
   659  			log.Warn("release event unsupported condition %q", cond)
   660  		}
   661  	}
   662  	return matchTimes == len(evt.Acts())
   663  }
   664  
   665  func matchPackageEvent(commit *git.Commit, payload *api.PackagePayload, evt *jobparser.Event) bool {
   666  	// with no special filter parameters
   667  	if len(evt.Acts()) == 0 {
   668  		return true
   669  	}
   670  
   671  	matchTimes := 0
   672  	// all acts conditions should be satisfied
   673  	for cond, vals := range evt.Acts() {
   674  		switch cond {
   675  		case "types":
   676  			// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#registry_package
   677  			// Activity types with the same name:
   678  			// NONE
   679  			// Activity types need to be converted:
   680  			// created -> published
   681  			// Unsupported activity types:
   682  			// updated
   683  
   684  			action := payload.Action
   685  			switch action {
   686  			case api.HookPackageCreated:
   687  				action = "published"
   688  			}
   689  			for _, val := range vals {
   690  				if glob.MustCompile(val, '/').Match(string(action)) {
   691  					matchTimes++
   692  					break
   693  				}
   694  			}
   695  		default:
   696  			log.Warn("package event unsupported condition %q", cond)
   697  		}
   698  	}
   699  	return matchTimes == len(evt.Acts())
   700  }