Skip to content

Commit

Permalink
Add CheckRun.Names scale-up trigger configuration (#390)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mumoshu committed Mar 14, 2021
1 parent a6270b4 commit 8d3a83b
Show file tree
Hide file tree
Showing 9 changed files with 329 additions and 1 deletion.
6 changes: 6 additions & 0 deletions api/v1alpha1/horizontalrunnerautoscaler_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion charts/actions-runner-controller/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
11 changes: 11 additions & 0 deletions controllers/horizontal_runner_autoscaler_webhook_on_check_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
}
8 changes: 8 additions & 0 deletions pkg/actionsglob/README.md
Original file line number Diff line number Diff line change
@@ -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 `**`
78 changes: 78 additions & 0 deletions pkg/actionsglob/actionsglob.go
Original file line number Diff line number Diff line change
@@ -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
}
198 changes: 198 additions & 0 deletions pkg/actionsglob/match_test.go
Original file line number Diff line number Diff line change
@@ -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,
})
})
}

0 comments on commit 8d3a83b

Please sign in to comment.