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:
Yusuke Kuoka 2021-03-14 10:21:42 +09:00 committed by GitHub
parent a6270b44d5
commit 8d3a83b07a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 329 additions and 1 deletions

View File

@ -72,6 +72,12 @@ type GitHubEventScaleUpTriggerSpec struct {
type CheckRunSpec struct {
Types []string `json:"types,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

View File

@ -66,6 +66,11 @@ func (in *CheckRunSpec) DeepCopyInto(out *CheckRunSpec) {
*out = make([]string, len(*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.

View File

@ -15,7 +15,7 @@ type: application
# 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.
# 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

View File

@ -148,6 +148,17 @@ spec:
checkRun:
description: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#check_run
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:
type: string
types:

View File

@ -148,6 +148,17 @@ spec:
checkRun:
description: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#check_run
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:
type: string
types:

View File

@ -3,6 +3,7 @@ package controllers
import (
"github.com/google/go-github/v33/github"
"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 {
@ -27,6 +28,16 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) MatchCheckRunEvent(ev
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
}
}

View File

@ -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 `**`

View File

@ -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
}

View File

@ -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,
})
})
}