Add CheckRun.Names scale-up trigger configuration (#390)
This allows you to trigger autoscaling depending on check_run names(i.e. actions job names). If you are willing to differentiate scale amount only for a specific job, or want to scale only on a specific job, try this.
This commit is contained in:
		
							parent
							
								
									a6270b44d5
								
							
						
					
					
						commit
						8d3a83b07a
					
				|  | @ -72,6 +72,12 @@ type GitHubEventScaleUpTriggerSpec struct { | ||||||
| type CheckRunSpec struct { | type CheckRunSpec struct { | ||||||
| 	Types  []string `json:"types,omitempty"` | 	Types  []string `json:"types,omitempty"` | ||||||
| 	Status string   `json:"status,omitempty"` | 	Status string   `json:"status,omitempty"` | ||||||
|  | 
 | ||||||
|  | 	// Names is a list of GitHub Actions glob patterns.
 | ||||||
|  | 	// Any check_run event whose name matches one of patterns in the list can trigger autoscaling.
 | ||||||
|  | 	// Note that check_run name seem to equal to the job name you've defined in your actions workflow yaml file.
 | ||||||
|  | 	// So it is very likely that you can utilize this to trigger depending on the job.
 | ||||||
|  | 	Names []string `json:"names,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request
 | // https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request
 | ||||||
|  |  | ||||||
|  | @ -66,6 +66,11 @@ func (in *CheckRunSpec) DeepCopyInto(out *CheckRunSpec) { | ||||||
| 		*out = make([]string, len(*in)) | 		*out = make([]string, len(*in)) | ||||||
| 		copy(*out, *in) | 		copy(*out, *in) | ||||||
| 	} | 	} | ||||||
|  | 	if in.Names != nil { | ||||||
|  | 		in, out := &in.Names, &out.Names | ||||||
|  | 		*out = make([]string, len(*in)) | ||||||
|  | 		copy(*out, *in) | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheckRunSpec.
 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheckRunSpec.
 | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ type: application | ||||||
| # This is the chart version. This version number should be incremented each time you make changes | # This is the chart version. This version number should be incremented each time you make changes | ||||||
| # to the chart and its templates, including the app version. | # to the chart and its templates, including the app version. | ||||||
| # Versions are expected to follow Semantic Versioning (https://semver.org/) | # Versions are expected to follow Semantic Versioning (https://semver.org/) | ||||||
| version: 0.8.0 | version: 0.9.0 | ||||||
| 
 | 
 | ||||||
| home: https://github.com/summerwind/actions-runner-controller | home: https://github.com/summerwind/actions-runner-controller | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -148,6 +148,17 @@ spec: | ||||||
|                       checkRun: |                       checkRun: | ||||||
|                         description: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#check_run |                         description: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#check_run | ||||||
|                         properties: |                         properties: | ||||||
|  |                           names: | ||||||
|  |                             description: Names is a list of GitHub Actions glob patterns. | ||||||
|  |                               Any check_run event whose name matches one of patterns | ||||||
|  |                               in the list can trigger autoscaling. Note that check_run | ||||||
|  |                               name seem to equal to the job name you've defined in | ||||||
|  |                               your actions workflow yaml file. So it is very likely | ||||||
|  |                               that you can utilize this to trigger depending on the | ||||||
|  |                               job. | ||||||
|  |                             items: | ||||||
|  |                               type: string | ||||||
|  |                             type: array | ||||||
|                           status: |                           status: | ||||||
|                             type: string |                             type: string | ||||||
|                           types: |                           types: | ||||||
|  |  | ||||||
|  | @ -148,6 +148,17 @@ spec: | ||||||
|                       checkRun: |                       checkRun: | ||||||
|                         description: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#check_run |                         description: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#check_run | ||||||
|                         properties: |                         properties: | ||||||
|  |                           names: | ||||||
|  |                             description: Names is a list of GitHub Actions glob patterns. | ||||||
|  |                               Any check_run event whose name matches one of patterns | ||||||
|  |                               in the list can trigger autoscaling. Note that check_run | ||||||
|  |                               name seem to equal to the job name you've defined in | ||||||
|  |                               your actions workflow yaml file. So it is very likely | ||||||
|  |                               that you can utilize this to trigger depending on the | ||||||
|  |                               job. | ||||||
|  |                             items: | ||||||
|  |                               type: string | ||||||
|  |                             type: array | ||||||
|                           status: |                           status: | ||||||
|                             type: string |                             type: string | ||||||
|                           types: |                           types: | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ package controllers | ||||||
| import ( | import ( | ||||||
| 	"github.com/google/go-github/v33/github" | 	"github.com/google/go-github/v33/github" | ||||||
| 	"github.com/summerwind/actions-runner-controller/api/v1alpha1" | 	"github.com/summerwind/actions-runner-controller/api/v1alpha1" | ||||||
|  | 	"github.com/summerwind/actions-runner-controller/pkg/actionsglob" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) MatchCheckRunEvent(event *github.CheckRunEvent) func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool { | func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) MatchCheckRunEvent(event *github.CheckRunEvent) func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool { | ||||||
|  | @ -27,6 +28,16 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) MatchCheckRunEvent(ev | ||||||
| 			return false | 			return false | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if checkRun := event.CheckRun; checkRun != nil && len(cr.Names) > 0 { | ||||||
|  | 			for _, pat := range cr.Names { | ||||||
|  | 				if r := actionsglob.Match(pat, checkRun.GetName()); r { | ||||||
|  | 					return true | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | This package is an implementation of glob that is intended to simulate the behaviour of  | ||||||
|  | https://github.com/actions/toolkit/tree/master/packages/glob in many cases. | ||||||
|  | 
 | ||||||
|  | This isn't a complete reimplementation of the referenced nodejs package. | ||||||
|  | 
 | ||||||
|  | Differences: | ||||||
|  | 
 | ||||||
|  | - This package doesn't implement `**` | ||||||
|  | @ -0,0 +1,78 @@ | ||||||
|  | package actionsglob | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func Match(pat string, s string) bool { | ||||||
|  | 	if len(pat) == 0 { | ||||||
|  | 		panic(fmt.Sprintf("unexpected length of pattern: %d", len(pat))) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var inverse bool | ||||||
|  | 
 | ||||||
|  | 	if pat[0] == '!' { | ||||||
|  | 		pat = pat[1:] | ||||||
|  | 		inverse = true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tokens := strings.SplitAfter(pat, "*") | ||||||
|  | 
 | ||||||
|  | 	var wildcardInHead bool | ||||||
|  | 
 | ||||||
|  | 	for i := 0; i < len(tokens); i++ { | ||||||
|  | 		p := tokens[i] | ||||||
|  | 
 | ||||||
|  | 		if p == "" { | ||||||
|  | 			s = "" | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if p == "*" { | ||||||
|  | 			if i == len(tokens)-1 { | ||||||
|  | 				s = "" | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			wildcardInHead = true | ||||||
|  | 
 | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		wildcardInTail := p[len(p)-1] == '*' | ||||||
|  | 		if wildcardInTail { | ||||||
|  | 			p = p[:len(p)-1] | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		subs := strings.SplitN(s, p, 2) | ||||||
|  | 
 | ||||||
|  | 		if len(subs) == 0 { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if subs[0] != "" { | ||||||
|  | 			if !wildcardInHead { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if subs[1] != "" { | ||||||
|  | 			if !wildcardInTail { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		s = subs[1] | ||||||
|  | 
 | ||||||
|  | 		wildcardInHead = false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r := s == "" | ||||||
|  | 
 | ||||||
|  | 	if inverse { | ||||||
|  | 		r = !r | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return r | ||||||
|  | } | ||||||
|  | @ -0,0 +1,198 @@ | ||||||
|  | package actionsglob | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestMatch(t *testing.T) { | ||||||
|  | 	type testcase struct { | ||||||
|  | 		Pattern, Target string | ||||||
|  | 		Want            bool | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	run := func(t *testing.T, tc testcase) { | ||||||
|  | 		t.Helper() | ||||||
|  | 
 | ||||||
|  | 		got := Match(tc.Pattern, tc.Target) | ||||||
|  | 
 | ||||||
|  | 		if got != tc.Want { | ||||||
|  | 			t.Errorf("%s against %s: want %v, got %v", tc.Pattern, tc.Target, tc.Want, got) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	t.Run("foo == foo", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "foo", | ||||||
|  | 			Target:  "foo", | ||||||
|  | 			Want:    true, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("!foo == foo", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "!foo", | ||||||
|  | 			Target:  "foo", | ||||||
|  | 			Want:    false, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("foo == foo1", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "foo", | ||||||
|  | 			Target:  "foo1", | ||||||
|  | 			Want:    false, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("!foo == foo1", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "!foo", | ||||||
|  | 			Target:  "foo1", | ||||||
|  | 			Want:    true, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("*foo == foo", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "*foo", | ||||||
|  | 			Target:  "foo", | ||||||
|  | 			Want:    true, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("!*foo == foo", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "!*foo", | ||||||
|  | 			Target:  "foo", | ||||||
|  | 			Want:    false, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("*foo == 1foo", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "*foo", | ||||||
|  | 			Target:  "1foo", | ||||||
|  | 			Want:    true, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("!*foo == 1foo", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "!*foo", | ||||||
|  | 			Target:  "1foo", | ||||||
|  | 			Want:    false, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("*foo == foo1", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "*foo", | ||||||
|  | 			Target:  "foo1", | ||||||
|  | 			Want:    false, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("!*foo == foo1", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "!*foo", | ||||||
|  | 			Target:  "foo1", | ||||||
|  | 			Want:    true, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("*foo* == foo1", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "*foo*", | ||||||
|  | 			Target:  "foo1", | ||||||
|  | 			Want:    true, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("!*foo* == foo1", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "!*foo*", | ||||||
|  | 			Target:  "foo1", | ||||||
|  | 			Want:    false, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("*foo == foobar", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "*foo", | ||||||
|  | 			Target:  "foobar", | ||||||
|  | 			Want:    false, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("!*foo == foobar", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "!*foo", | ||||||
|  | 			Target:  "foobar", | ||||||
|  | 			Want:    true, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("*foo* == foobar", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "*foo*", | ||||||
|  | 			Target:  "foobar", | ||||||
|  | 			Want:    true, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("!*foo* == foobar", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "!*foo*", | ||||||
|  | 			Target:  "foobar", | ||||||
|  | 			Want:    false, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("foo* == foo", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "foo*", | ||||||
|  | 			Target:  "foo", | ||||||
|  | 			Want:    true, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("!foo* == foo", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "!foo*", | ||||||
|  | 			Target:  "foo", | ||||||
|  | 			Want:    false, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("foo* == foobar", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "foo*", | ||||||
|  | 			Target:  "foobar", | ||||||
|  | 			Want:    true, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("!foo* == foobar", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "!foo*", | ||||||
|  | 			Target:  "foobar", | ||||||
|  | 			Want:    false, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("foo (* == foo ( 1 / 2 )", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "foo (*", | ||||||
|  | 			Target:  "foo ( 1 / 2 )", | ||||||
|  | 			Want:    true, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("!foo (* == foo ( 1 / 2 )", func(t *testing.T) { | ||||||
|  | 		run(t, testcase{ | ||||||
|  | 			Pattern: "!foo (*", | ||||||
|  | 			Target:  "foo ( 1 / 2 )", | ||||||
|  | 			Want:    false, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue