diff --git a/controllers/horizontal_runner_autoscaler_webhook.go b/controllers/horizontal_runner_autoscaler_webhook.go index d58d82f5..b522906e 100644 --- a/controllers/horizontal_runner_autoscaler_webhook.go +++ b/controllers/horizontal_runner_autoscaler_webhook.go @@ -542,7 +542,7 @@ HRA: // Ensure that the RunnerDeployment-managed runners have all the labels requested by the workflow_job. for _, l := range labels { var matched bool - for _, l2 := range rd.Spec.Template.Spec.Labels { + for _, l2 := range rd.Spec.Template.Spec.Labels { if l == l2 { matched = true break diff --git a/controllers/horizontal_runner_autoscaler_webhook_test.go b/controllers/horizontal_runner_autoscaler_webhook_test.go index d97850c6..20d51580 100644 --- a/controllers/horizontal_runner_autoscaler_webhook_test.go +++ b/controllers/horizontal_runner_autoscaler_webhook_test.go @@ -114,6 +114,145 @@ func TestWebhookPing(t *testing.T) { ) } +func TestWebhookWorkflowJob(t *testing.T) { + setupTest := func() github.WorkflowJobEvent { + f, err := os.Open("testdata/org_webhook_workflow_job_payload.json") + if err != nil { + t.Fatalf("could not open the fixture: %s", err) + } + defer f.Close() + var e github.WorkflowJobEvent + if err := json.NewDecoder(f).Decode(&e); err != nil { + t.Fatalf("invalid json: %s", err) + } + + return e + } + t.Run("Successful", func(t *testing.T) { + e := setupTest() + hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: actionsv1alpha1.HorizontalRunnerAutoscalerSpec{ + ScaleTargetRef: actionsv1alpha1.ScaleTargetRef{ + Name: "test-name", + }, + }, + } + + rd := &actionsv1alpha1.RunnerDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: actionsv1alpha1.RunnerDeploymentSpec{ + Template: actionsv1alpha1.RunnerTemplate{ + Spec: actionsv1alpha1.RunnerSpec{ + RunnerConfig: actionsv1alpha1.RunnerConfig{ + Organization: "MYORG", + Labels: []string{"label1"}, + }, + }, + }, + }, + } + + initObjs := []runtime.Object{hra, rd} + + testServerWithInitObjs(t, + "workflow_job", + &e, + 200, + "scaled test-name by 1", + initObjs, + ) + }) + t.Run("WrongLabels", func(t *testing.T) { + e := setupTest() + hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: actionsv1alpha1.HorizontalRunnerAutoscalerSpec{ + ScaleTargetRef: actionsv1alpha1.ScaleTargetRef{ + Name: "test-name", + }, + }, + } + + rd := &actionsv1alpha1.RunnerDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: actionsv1alpha1.RunnerDeploymentSpec{ + Template: actionsv1alpha1.RunnerTemplate{ + Spec: actionsv1alpha1.RunnerSpec{ + RunnerConfig: actionsv1alpha1.RunnerConfig{ + Organization: "MYORG", + Labels: []string{"bad-label"}, + }, + }, + }, + }, + } + + initObjs := []runtime.Object{hra, rd} + + testServerWithInitObjs(t, + "workflow_job", + &e, + 200, + "no horizontalrunnerautoscaler to scale for this github event", + initObjs, + ) + }) + // This test verifies that the old way of matching labels doesn't work anymore + t.Run("OldLabels", func(t *testing.T) { + e := setupTest() + hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: actionsv1alpha1.HorizontalRunnerAutoscalerSpec{ + ScaleTargetRef: actionsv1alpha1.ScaleTargetRef{ + Name: "test-name", + }, + }, + } + + rd := &actionsv1alpha1.RunnerDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + }, + Spec: actionsv1alpha1.RunnerDeploymentSpec{ + Template: actionsv1alpha1.RunnerTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "label1": "label1", + }, + }, + Spec: actionsv1alpha1.RunnerSpec{ + RunnerConfig: actionsv1alpha1.RunnerConfig{ + Organization: "MYORG", + Labels: []string{"bad-label"}, + }, + }, + }, + }, + } + + initObjs := []runtime.Object{hra, rd} + + testServerWithInitObjs(t, + "workflow_job", + &e, + 200, + "no horizontalrunnerautoscaler to scale for this github event", + initObjs, + ) + }) +} + func TestGetRequest(t *testing.T) { hra := HorizontalRunnerAutoscalerGitHubWebhook{} request, _ := http.NewRequest(http.MethodGet, "/", nil) @@ -177,13 +316,11 @@ func installTestLogger(webhook *HorizontalRunnerAutoscalerGitHubWebhook) *bytes. return logs } -func testServer(t *testing.T, eventType string, event interface{}, wantCode int, wantBody string) { +func testServerWithInitObjs(t *testing.T, eventType string, event interface{}, wantCode int, wantBody string, initObjs []runtime.Object) { t.Helper() hraWebhook := &HorizontalRunnerAutoscalerGitHubWebhook{} - var initObjs []runtime.Object - client := fake.NewFakeClientWithScheme(sc, initObjs...) logs := installTestLogger(hraWebhook) @@ -227,6 +364,11 @@ func testServer(t *testing.T, eventType string, event interface{}, wantCode int, } } +func testServer(t *testing.T, eventType string, event interface{}, wantCode int, wantBody string) { + var initObjs []runtime.Object + testServerWithInitObjs(t, eventType, event, wantCode, wantBody, initObjs) +} + func sendWebhook(server *httptest.Server, eventType string, event interface{}) (*http.Response, error) { jsonBuf := &bytes.Buffer{} enc := json.NewEncoder(jsonBuf) diff --git a/controllers/testdata/org_webhook_workflow_job_payload.json b/controllers/testdata/org_webhook_workflow_job_payload.json new file mode 100644 index 00000000..3a0940b0 --- /dev/null +++ b/controllers/testdata/org_webhook_workflow_job_payload.json @@ -0,0 +1,151 @@ +{ + "action": "queued", + "workflow_job": { + "id": 1234567890, + "run_id": 1234567890, + "run_url": "https://api.github.com/repos/MYORG/MYREPO/actions/runs/1234567890", + "node_id": "CR_kwDOGCados7e1x2g", + "head_sha": "1234567890123456789012345678901234567890", + "url": "https://api.github.com/repos/MYORG/MYREPO/actions/jobs/1234567890", + "html_url": "https://github.com/MYORG/MYREPO/runs/1234567890", + "status": "queued", + "conclusion": null, + "started_at": "2021-09-28T23:45:29Z", + "completed_at": null, + "name": "build", + "steps": [], + "check_run_url": "https://api.github.com/repos/MYORG/MYREPO/check-runs/1234567890", + "labels": [ + "label1" + ] + }, + "repository": { + "id": 1234567890, + "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ=", + "name": "MYREPO", + "full_name": "MYORG/MYREPO", + "private": true, + "owner": { + "login": "MYORG", + "id": 1234567890, + "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "avatar_url": "https://avatars.githubusercontent.com/u/1234567890?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/MYORG", + "html_url": "https://github.com/MYORG", + "followers_url": "https://api.github.com/users/MYORG/followers", + "following_url": "https://api.github.com/users/MYORG/following{/other_user}", + "gists_url": "https://api.github.com/users/MYORG/gists{/gist_id}", + "starred_url": "https://api.github.com/users/MYORG/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/MYORG/subscriptions", + "organizations_url": "https://api.github.com/users/MYORG/orgs", + "repos_url": "https://api.github.com/users/MYORG/repos", + "events_url": "https://api.github.com/users/MYORG/events{/privacy}", + "received_events_url": "https://api.github.com/users/MYORG/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/MYORG/MYREPO", + "description": "MYREPO", + "fork": false, + "url": "https://api.github.com/repos/MYORG/MYREPO", + "forks_url": "https://api.github.com/repos/MYORG/MYREPO/forks", + "keys_url": "https://api.github.com/repos/MYORG/MYREPO/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/MYORG/MYREPO/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/MYORG/MYREPO/teams", + "hooks_url": "https://api.github.com/repos/MYORG/MYREPO/hooks", + "issue_events_url": "https://api.github.com/repos/MYORG/MYREPO/issues/events{/number}", + "events_url": "https://api.github.com/repos/MYORG/MYREPO/events", + "assignees_url": "https://api.github.com/repos/MYORG/MYREPO/assignees{/user}", + "branches_url": "https://api.github.com/repos/MYORG/MYREPO/branches{/branch}", + "tags_url": "https://api.github.com/repos/MYORG/MYREPO/tags", + "blobs_url": "https://api.github.com/repos/MYORG/MYREPO/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/MYORG/MYREPO/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/MYORG/MYREPO/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/MYORG/MYREPO/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/MYORG/MYREPO/statuses/{sha}", + "languages_url": "https://api.github.com/repos/MYORG/MYREPO/languages", + "stargazers_url": "https://api.github.com/repos/MYORG/MYREPO/stargazers", + "contributors_url": "https://api.github.com/repos/MYORG/MYREPO/contributors", + "subscribers_url": "https://api.github.com/repos/MYORG/MYREPO/subscribers", + "subscription_url": "https://api.github.com/repos/MYORG/MYREPO/subscription", + "commits_url": "https://api.github.com/repos/MYORG/MYREPO/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/MYORG/MYREPO/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/MYORG/MYREPO/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/MYORG/MYREPO/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/MYORG/MYREPO/contents/{+path}", + "compare_url": "https://api.github.com/repos/MYORG/MYREPO/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/MYORG/MYREPO/merges", + "archive_url": "https://api.github.com/repos/MYORG/MYREPO/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/MYORG/MYREPO/downloads", + "issues_url": "https://api.github.com/repos/MYORG/MYREPO/issues{/number}", + "pulls_url": "https://api.github.com/repos/MYORG/MYREPO/pulls{/number}", + "milestones_url": "https://api.github.com/repos/MYORG/MYREPO/milestones{/number}", + "notifications_url": "https://api.github.com/repos/MYORG/MYREPO/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/MYORG/MYREPO/labels{/name}", + "releases_url": "https://api.github.com/repos/MYORG/MYREPO/releases{/id}", + "deployments_url": "https://api.github.com/repos/MYORG/MYREPO/deployments", + "created_at": "2021-09-10T18:55:38Z", + "updated_at": "2021-09-10T18:55:41Z", + "pushed_at": "2021-09-28T23:25:26Z", + "git_url": "git://github.com/MYORG/MYREPO.git", + "ssh_url": "git@github.com:MYORG/MYREPO.git", + "clone_url": "https://github.com/MYORG/MYREPO.git", + "svn_url": "https://github.com/MYORG/MYREPO", + "homepage": null, + "size": 121, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 1, + "license": null, + "allow_forking": false, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "master" + }, + "organization": { + "login": "MYORG", + "id": 1234567890, + "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "url": "https://api.github.com/orgs/MYORG", + "repos_url": "https://api.github.com/orgs/MYORG/repos", + "events_url": "https://api.github.com/orgs/MYORG/events", + "hooks_url": "https://api.github.com/orgs/MYORG/hooks", + "issues_url": "https://api.github.com/orgs/MYORG/issues", + "members_url": "https://api.github.com/orgs/MYORG/members{/member}", + "public_members_url": "https://api.github.com/orgs/MYORG/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/1234567890?v=4", + "description": "" + }, + "sender": { + "login": "MYNAME", + "id": 1234567890, + "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "avatar_url": "https://avatars.githubusercontent.com/u/1234567890?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/MYNAME", + "html_url": "https://github.com/MYNAME", + "followers_url": "https://api.github.com/users/MYNAME/followers", + "following_url": "https://api.github.com/users/MYNAME/following{/other_user}", + "gists_url": "https://api.github.com/users/MYNAME/gists{/gist_id}", + "starred_url": "https://api.github.com/users/MYNAME/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/MYNAME/subscriptions", + "organizations_url": "https://api.github.com/users/MYNAME/orgs", + "repos_url": "https://api.github.com/users/MYNAME/repos", + "events_url": "https://api.github.com/users/MYNAME/events{/privacy}", + "received_events_url": "https://api.github.com/users/MYNAME/received_events", + "type": "User", + "site_admin": false + } +}