diff --git a/controllers/horizontal_runner_autoscaler_webhook.go b/controllers/horizontal_runner_autoscaler_webhook.go index 2819bdea16..967c13d29d 100644 --- a/controllers/horizontal_runner_autoscaler_webhook.go +++ b/controllers/horizontal_runner_autoscaler_webhook.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/types" "net/http" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "strings" "time" "github.com/go-logr/logr" @@ -133,22 +134,25 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) Handle(w http.Respons case *gogithub.PushEvent: target, err = autoscaler.getScaleUpTarget( context.TODO(), - *e.Repo.Name, - *e.Repo.Organization, + e.Repo.GetName(), + e.Repo.Owner.GetLogin(), + e.Repo.Owner.GetType(), autoscaler.MatchPushEvent(e), ) case *gogithub.PullRequestEvent: target, err = autoscaler.getScaleUpTarget( context.TODO(), - *e.Repo.Name, - *e.Repo.Organization.Name, + e.Repo.GetName(), + e.Repo.Owner.GetLogin(), + e.Repo.Owner.GetType(), autoscaler.MatchPullRequestEvent(e), ) case *gogithub.CheckRunEvent: target, err = autoscaler.getScaleUpTarget( context.TODO(), - e.GetRepo().GetName(), - e.GetOrg().GetLogin(), // empty string if the repo is not in an organization + e.Repo.GetName(), + e.Repo.Owner.GetLogin(), + e.Repo.Owner.GetType(), autoscaler.MatchCheckRunEvent(e), ) case *gogithub.PingEvent: @@ -227,6 +231,10 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) findHRAsByKey(ctx con opts := append([]client.ListOption{}, defaultListOpts...) opts = append(opts, client.MatchingFields{scaleTargetKey: value}) + if autoscaler.WatchNamespace != "" { + opts = append(opts, client.InNamespace(autoscaler.WatchNamespace)) + } + var hraList v1alpha1.HorizontalRunnerAutoscalerList if err := autoscaler.List(ctx, &hraList, opts...); err != nil { @@ -296,27 +304,45 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getScaleTarget(ctx co targets := autoscaler.searchScaleTargets(hras, f) if len(targets) != 1 { + var scaleTargetIDs []string + + for _, t := range targets { + scaleTargetIDs = append(scaleTargetIDs, t.HorizontalRunnerAutoscaler.Name) + } + + autoscaler.Log.Info( + "Found too many scale targets: "+ + "It must be exactly one to avoid ambiguity. "+ + "Either set WatchNamespace for the webhook-based autoscaler to let it only find HRAs in the namespace, "+ + "or update Repository or Organization fields in your RunnerDeployment resources to fix the ambiguity.", + "scaleTargets", strings.Join(scaleTargetIDs, ",")) + return nil, nil } return &targets[0], nil } -func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getScaleUpTarget(ctx context.Context, repoNameFromWebhook, orgNameFromWebhook string, f func(v1alpha1.ScaleUpTrigger) bool) (*ScaleTarget, error) { - repositoryRunnerKey := orgNameFromWebhook + "/" + repoNameFromWebhook +func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getScaleUpTarget(ctx context.Context, repo, owner, ownerType string, f func(v1alpha1.ScaleUpTrigger) bool) (*ScaleTarget, error) { + repositoryRunnerKey := owner + "/" + repo + autoscaler.Log.Info("finding repository-wide runner", "repository", repositoryRunnerKey) if target, err := autoscaler.getScaleTarget(ctx, repositoryRunnerKey, f); err != nil { return nil, err } else if target != nil { - autoscaler.Log.Info("scale up target is repository-wide runners", "repository", repoNameFromWebhook) + autoscaler.Log.Info("scale up target is repository-wide runners", "repository", repo) return target, nil } - autoscaler.Log.Info("finding organizational runner", "organization", orgNameFromWebhook) - if target, err := autoscaler.getScaleTarget(ctx, orgNameFromWebhook, f); err != nil { + if ownerType == "User" { + return nil, nil + } + + autoscaler.Log.Info("finding organizational runner", "organization", owner) + if target, err := autoscaler.getScaleTarget(ctx, owner, f); err != nil { return nil, err } else if target != nil { - autoscaler.Log.Info("scale up target is organizational runners", "organization", orgNameFromWebhook) + autoscaler.Log.Info("scale up target is organizational runners", "organization", owner) return target, nil } diff --git a/controllers/horizontal_runner_autoscaler_webhook_test.go b/controllers/horizontal_runner_autoscaler_webhook_test.go index 0e41235956..d57ff50c14 100644 --- a/controllers/horizontal_runner_autoscaler_webhook_test.go +++ b/controllers/horizontal_runner_autoscaler_webhook_test.go @@ -28,8 +28,26 @@ func init() { _ = actionsv1alpha1.AddToScheme(sc) } -func TestWebhookCheckRun(t *testing.T) { - f, err := os.Open("testdata/webhook_check_run_payload.json") +func TestOrgWebhookCheckRun(t *testing.T) { + f, err := os.Open("testdata/org_webhook_check_run_payload.json") + if err != nil { + t.Fatalf("could not open the fixture: %s", err) + } + defer f.Close() + var e github.CheckRunEvent + if err := json.NewDecoder(f).Decode(&e); err != nil { + t.Fatalf("invalid json: %s", err) + } + testServer(t, + "check_run", + &e, + 200, + "no horizontalrunnerautoscaler to scale for this github event", + ) +} + +func TestRepoWebhookCheckRun(t *testing.T) { + f, err := os.Open("testdata/repo_webhook_check_run_payload.json") if err != nil { t.Fatalf("could not open the fixture: %s", err) } diff --git a/controllers/integration_test.go b/controllers/integration_test.go index 8ce2a643f4..b79729a4ed 100644 --- a/controllers/integration_test.go +++ b/controllers/integration_test.go @@ -131,11 +131,12 @@ func SetupIntegrationTest(ctx context.Context) *testEnvironment { Expect(err).NotTo(HaveOccurred(), "failed to setup controller") autoscalerWebhook := &HorizontalRunnerAutoscalerGitHubWebhook{ - Client: mgr.GetClient(), - Scheme: scheme.Scheme, - Log: logf.Log, - Recorder: mgr.GetEventRecorderFor("horizontalrunnerautoscaler-controller"), - Name: controllerName("horizontalrunnerautoscalergithubwebhook"), + Client: mgr.GetClient(), + Scheme: scheme.Scheme, + Log: logf.Log, + Recorder: mgr.GetEventRecorderFor("horizontalrunnerautoscaler-controller"), + Name: controllerName("horizontalrunnerautoscalergithubwebhook"), + WatchNamespace: ns.Name, } err = autoscalerWebhook.SetupWithManager(mgr) Expect(err).NotTo(HaveOccurred(), "failed to setup autoscaler webhook") @@ -173,7 +174,7 @@ var _ = Context("INTEGRATION: Inside of a new namespace", func() { Describe("when no existing resources exist", func() { - It("should create and scale runners on pull_request event", func() { + It("should create and scale organization's repository runners on pull_request event", func() { name := "example-runnerdeploy" { @@ -296,19 +297,19 @@ var _ = Context("INTEGRATION: Inside of a new namespace", func() { // Scale-up to 2 replicas on first pull_request create webhook event { - env.SendPullRequestEvent("test", "valid", "main", "created") + env.SendOrgPullRequestEvent("test", "valid", "main", "created") ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook") ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 2, "runners after first webhook event") } // Scale-up to 3 replicas on second pull_request create webhook event { - env.SendPullRequestEvent("test", "valid", "main", "created") + env.SendOrgPullRequestEvent("test", "valid", "main", "created") ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3, "runners after second webhook event") } }) - It("should create and scale runners on check_run event", func() { + It("should create and scale organization's repository runners on check_run event", func() { name := "example-runnerdeploy" { @@ -385,7 +386,7 @@ var _ = Context("INTEGRATION: Inside of a new namespace", func() { // Scale-up to 4 replicas on first check_run create webhook event { - env.SendCheckRunEvent("test", "valid", "pending", "created") + env.SendOrgCheckRunEvent("test", "valid", "pending", "created") ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook") ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 4, "runners after first webhook event") } @@ -396,12 +397,243 @@ var _ = Context("INTEGRATION: Inside of a new namespace", func() { // Scale-up to 5 replicas on second check_run create webhook event { - env.SendCheckRunEvent("test", "valid", "pending", "created") + env.SendOrgCheckRunEvent("test", "valid", "pending", "created") ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 5, "runners after second webhook event") } env.ExpectRegisteredNumberCountEventuallyEquals(5, "count of fake list runners") }) + + It("should create and scale user's repository runners on pull_request event", func() { + name := "example-runnerdeploy" + + { + rd := &actionsv1alpha1.RunnerDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns.Name, + }, + Spec: actionsv1alpha1.RunnerDeploymentSpec{ + Replicas: intPtr(1), + Template: actionsv1alpha1.RunnerTemplate{ + Spec: actionsv1alpha1.RunnerSpec{ + Repository: "test/valid", + Image: "bar", + Group: "baz", + Env: []corev1.EnvVar{ + {Name: "FOO", Value: "FOOVALUE"}, + }, + }, + }, + }, + } + + ExpectCreate(ctx, rd, "test RunnerDeployment") + ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) + ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) + } + + { + ExpectRunnerDeploymentEventuallyUpdates(ctx, ns.Name, name, func(rd *actionsv1alpha1.RunnerDeployment) { + rd.Spec.Replicas = intPtr(2) + }) + ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) + ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 2) + } + + // Scale-up to 3 replicas + { + hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns.Name, + }, + Spec: actionsv1alpha1.HorizontalRunnerAutoscalerSpec{ + ScaleTargetRef: actionsv1alpha1.ScaleTargetRef{ + Name: name, + }, + MinReplicas: intPtr(1), + MaxReplicas: intPtr(3), + ScaleDownDelaySecondsAfterScaleUp: intPtr(1), + Metrics: nil, + ScaleUpTriggers: []actionsv1alpha1.ScaleUpTrigger{ + { + GitHubEvent: &actionsv1alpha1.GitHubEventScaleUpTriggerSpec{ + PullRequest: &actionsv1alpha1.PullRequestSpec{ + Types: []string{"created"}, + Branches: []string{"main"}, + }, + }, + Amount: 1, + Duration: metav1.Duration{Duration: time.Minute}, + }, + }, + }, + } + + ExpectCreate(ctx, hra, "test HorizontalRunnerAutoscaler") + + ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) + ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3) + } + + { + var runnerList actionsv1alpha1.RunnerList + + err := k8sClient.List(ctx, &runnerList, client.InNamespace(ns.Name)) + if err != nil { + logf.Log.Error(err, "list runners") + } + + for i, r := range runnerList.Items { + env.fakeRunnerList.Add(&github3.Runner{ + ID: github.Int64(int64(i)), + Name: github.String(r.Name), + OS: github.String("linux"), + Status: github.String("online"), + Busy: github.Bool(false), + }) + } + + rs, err := env.ghClient.ListRunners(context.Background(), "", "", "test/valid") + Expect(err).NotTo(HaveOccurred(), "verifying list fake runners response") + Expect(len(rs)).To(Equal(3), "count of fake list runners") + } + + // Scale-down to 1 replica + { + time.Sleep(time.Second) + + env.Responses.ListRepositoryWorkflowRuns.Body = workflowRunsFor1Replicas + env.Responses.ListRepositoryWorkflowRuns.Statuses["queued"] = workflowRunsFor1Replicas_queued + env.Responses.ListRepositoryWorkflowRuns.Statuses["in_progress"] = workflowRunsFor1Replicas_in_progress + + var hra actionsv1alpha1.HorizontalRunnerAutoscaler + + err := k8sClient.Get(ctx, types.NamespacedName{Namespace: ns.Name, Name: name}, &hra) + + Expect(err).NotTo(HaveOccurred(), "failed to get test HorizontalRunnerAutoscaler resource") + + hra.Annotations = map[string]string{ + "force-update": "1", + } + + err = k8sClient.Update(ctx, &hra) + + Expect(err).NotTo(HaveOccurred(), "failed to get test HorizontalRunnerAutoscaler resource") + + ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1, "runners after HRA force update for scale-down") + } + + // Scale-up to 2 replicas on first pull_request create webhook event + { + env.SendUserPullRequestEvent("test", "valid", "main", "created") + ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook") + ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 2, "runners after first webhook event") + } + + // Scale-up to 3 replicas on second pull_request create webhook event + { + env.SendUserPullRequestEvent("test", "valid", "main", "created") + ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3, "runners after second webhook event") + } + }) + + It("should create and scale user's repository runners on check_run event", func() { + name := "example-runnerdeploy" + + { + rd := &actionsv1alpha1.RunnerDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns.Name, + }, + Spec: actionsv1alpha1.RunnerDeploymentSpec{ + Replicas: intPtr(1), + Template: actionsv1alpha1.RunnerTemplate{ + Spec: actionsv1alpha1.RunnerSpec{ + Repository: "test/valid", + Image: "bar", + Group: "baz", + Env: []corev1.EnvVar{ + {Name: "FOO", Value: "FOOVALUE"}, + }, + }, + }, + }, + } + + ExpectCreate(ctx, rd, "test RunnerDeployment") + ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) + ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) + } + + { + env.ExpectRegisteredNumberCountEventuallyEquals(1, "count of fake list runners") + } + + // Scale-up to 3 replicas by the default TotalNumberOfQueuedAndInProgressWorkflowRuns-based scaling + // See workflowRunsFor3Replicas_queued and workflowRunsFor3Replicas_in_progress for GitHub List-Runners API responses + // used while testing. + { + hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns.Name, + }, + Spec: actionsv1alpha1.HorizontalRunnerAutoscalerSpec{ + ScaleTargetRef: actionsv1alpha1.ScaleTargetRef{ + Name: name, + }, + MinReplicas: intPtr(1), + MaxReplicas: intPtr(5), + ScaleDownDelaySecondsAfterScaleUp: intPtr(1), + Metrics: nil, + ScaleUpTriggers: []actionsv1alpha1.ScaleUpTrigger{ + { + GitHubEvent: &actionsv1alpha1.GitHubEventScaleUpTriggerSpec{ + CheckRun: &actionsv1alpha1.CheckRunSpec{ + Types: []string{"created"}, + Status: "pending", + }, + }, + Amount: 1, + Duration: metav1.Duration{Duration: time.Minute}, + }, + }, + }, + } + + ExpectCreate(ctx, hra, "test HorizontalRunnerAutoscaler") + + ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) + ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3) + } + + { + env.ExpectRegisteredNumberCountEventuallyEquals(3, "count of fake list runners") + } + + // Scale-up to 4 replicas on first check_run create webhook event + { + env.SendUserCheckRunEvent("test", "valid", "pending", "created") + ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook") + ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 4, "runners after first webhook event") + } + + { + env.ExpectRegisteredNumberCountEventuallyEquals(4, "count of fake list runners") + } + + // Scale-up to 5 replicas on second check_run create webhook event + { + env.SendUserCheckRunEvent("test", "valid", "pending", "created") + ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 5, "runners after second webhook event") + } + + env.ExpectRegisteredNumberCountEventuallyEquals(5, "count of fake list runners") + }) + }) }) @@ -419,7 +651,7 @@ func (env *testEnvironment) ExpectRegisteredNumberCountEventuallyEquals(want int time.Second*1, time.Millisecond*500).Should(Equal(want), optionalDescriptions...) } -func (env *testEnvironment) SendPullRequestEvent(org, repo, branch, action string) { +func (env *testEnvironment) SendOrgPullRequestEvent(org, repo, branch, action string) { resp, err := sendWebhook(env.webhookServer, "pull_request", &github.PullRequestEvent{ PullRequest: &github.PullRequest{ Base: &github.PullRequestBranch{ @@ -428,8 +660,9 @@ func (env *testEnvironment) SendPullRequestEvent(org, repo, branch, action strin }, Repo: &github.Repository{ Name: github.String(repo), - Organization: &github.Organization{ - Name: github.String(org), + Owner: &github.User{ + Login: github.String(org), + Type: github.String("Organization"), }, }, Action: github.String(action), @@ -440,7 +673,7 @@ func (env *testEnvironment) SendPullRequestEvent(org, repo, branch, action strin ExpectWithOffset(1, resp.StatusCode).To(Equal(200)) } -func (env *testEnvironment) SendCheckRunEvent(org, repo, status, action string) { +func (env *testEnvironment) SendOrgCheckRunEvent(org, repo, status, action string) { resp, err := sendWebhook(env.webhookServer, "check_run", &github.CheckRunEvent{ CheckRun: &github.CheckRun{ Status: github.String(status), @@ -450,6 +683,52 @@ func (env *testEnvironment) SendCheckRunEvent(org, repo, status, action string) }, Repo: &github.Repository{ Name: github.String(repo), + Owner: &github.User{ + Login: github.String(org), + Type: github.String("Organization"), + }, + }, + Action: github.String(action), + }) + + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "failed to send check_run event") + + ExpectWithOffset(1, resp.StatusCode).To(Equal(200)) +} + +func (env *testEnvironment) SendUserPullRequestEvent(owner, repo, branch, action string) { + resp, err := sendWebhook(env.webhookServer, "pull_request", &github.PullRequestEvent{ + PullRequest: &github.PullRequest{ + Base: &github.PullRequestBranch{ + Ref: github.String(branch), + }, + }, + Repo: &github.Repository{ + Name: github.String(repo), + Owner: &github.User{ + Login: github.String(owner), + Type: github.String("User"), + }, + }, + Action: github.String(action), + }) + + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "failed to send pull_request event") + + ExpectWithOffset(1, resp.StatusCode).To(Equal(200)) +} + +func (env *testEnvironment) SendUserCheckRunEvent(owner, repo, status, action string) { + resp, err := sendWebhook(env.webhookServer, "check_run", &github.CheckRunEvent{ + CheckRun: &github.CheckRun{ + Status: github.String(status), + }, + Repo: &github.Repository{ + Name: github.String(repo), + Owner: &github.User{ + Login: github.String(owner), + Type: github.String("User"), + }, }, Action: github.String(action), }) diff --git a/controllers/testdata/webhook_check_run_payload.json b/controllers/testdata/org_webhook_check_run_payload.json similarity index 78% rename from controllers/testdata/webhook_check_run_payload.json rename to controllers/testdata/org_webhook_check_run_payload.json index cb0f682729..2ddbb57c96 100644 --- a/controllers/testdata/webhook_check_run_payload.json +++ b/controllers/testdata/org_webhook_check_run_payload.json @@ -138,111 +138,111 @@ "updated_at": "2021-02-18T06:16:31Z" }, "app": { + "id": 1234567890, + "slug": "github-actions", + "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "owner": { + "login": "github", "id": 1234567890, - "slug": "github-actions", "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", - "owner": { - "login": "github", - "id": 1234567890, - "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", - "avatar_url": "https://avatars.githubusercontent.com/u/1234567890?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github", - "html_url": "https://github.com/github", - "followers_url": "https://api.github.com/users/github/followers", - "following_url": "https://api.github.com/users/github/following{/other_user}", - "gists_url": "https://api.github.com/users/github/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github/subscriptions", - "organizations_url": "https://api.github.com/users/github/orgs", - "repos_url": "https://api.github.com/users/github/repos", - "events_url": "https://api.github.com/users/github/events{/privacy}", - "received_events_url": "https://api.github.com/users/github/received_events", - "type": "Organization", - "site_admin": false - }, - "name": "GitHub Actions", - "description": "Automate your workflow from idea to production", - "external_url": "https://help.github.com/en/actions", - "html_url": "https://github.com/apps/github-actions", - "created_at": "2018-07-30T09:30:17Z", - "updated_at": "2019-12-10T19:04:12Z", - "permissions": { - "actions": "write", - "checks": "write", - "contents": "write", - "deployments": "write", - "issues": "write", - "metadata": "read", - "organization_packages": "write", - "packages": "write", - "pages": "write", - "pull_requests": "write", - "repository_hooks": "write", - "repository_projects": "write", - "security_events": "write", - "statuses": "write", - "vulnerability_alerts": "read" - }, - "events": [ - "check_run", - "check_suite", - "create", - "delete", - "deployment", - "deployment_status", - "fork", - "gollum", - "issues", - "issue_comment", - "label", - "milestone", - "page_build", - "project", - "project_card", - "project_column", - "public", - "pull_request", - "pull_request_review", - "pull_request_review_comment", - "push", - "registry_package", - "release", - "repository", - "repository_dispatch", - "status", - "watch", - "workflow_dispatch", - "workflow_run" - ] + "avatar_url": "https://avatars.githubusercontent.com/u/1234567890?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github", + "html_url": "https://github.com/github", + "followers_url": "https://api.github.com/users/github/followers", + "following_url": "https://api.github.com/users/github/following{/other_user}", + "gists_url": "https://api.github.com/users/github/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github/subscriptions", + "organizations_url": "https://api.github.com/users/github/orgs", + "repos_url": "https://api.github.com/users/github/repos", + "events_url": "https://api.github.com/users/github/events{/privacy}", + "received_events_url": "https://api.github.com/users/github/received_events", + "type": "Organization", + "site_admin": false }, - "pull_requests": [ - { - "url": "https://api.github.com/repos/MYORG/MYREPO/pulls/1234567890", - "id": 1234567890, - "number": 1234567890, - "head": { - "ref": "feature", - "sha": "1234567890123456789012345678901234567890", - "repo": { - "id": 1234567890, - "url": "https://api.github.com/repos/MYORG/MYREPO", - "name": "MYREPO" - } - }, - "base": { - "ref": "master", - "sha": "1234567890123456789012345678901234567890", - "repo": { - "id": 1234567890, - "url": "https://api.github.com/repos/MYORG/MYREPO", - "name": "MYREPO" - } - } - } + "name": "GitHub Actions", + "description": "Automate your workflow from idea to production", + "external_url": "https://help.github.com/en/actions", + "html_url": "https://github.com/apps/github-actions", + "created_at": "2018-07-30T09:30:17Z", + "updated_at": "2019-12-10T19:04:12Z", + "permissions": { + "actions": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "issues": "write", + "metadata": "read", + "organization_packages": "write", + "packages": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "security_events": "write", + "statuses": "write", + "vulnerability_alerts": "read" + }, + "events": [ + "check_run", + "check_suite", + "create", + "delete", + "deployment", + "deployment_status", + "fork", + "gollum", + "issues", + "issue_comment", + "label", + "milestone", + "page_build", + "project", + "project_card", + "project_column", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "push", + "registry_package", + "release", + "repository", + "repository_dispatch", + "status", + "watch", + "workflow_dispatch", + "workflow_run" ] }, - "repository": { + "pull_requests": [ + { + "url": "https://api.github.com/repos/MYORG/MYREPO/pulls/1234567890", + "id": 1234567890, + "number": 1234567890, + "head": { + "ref": "feature", + "sha": "1234567890123456789012345678901234567890", + "repo": { + "id": 1234567890, + "url": "https://api.github.com/repos/MYORG/MYREPO", + "name": "MYREPO" + } + }, + "base": { + "ref": "master", + "sha": "1234567890123456789012345678901234567890", + "repo": { + "id": 1234567890, + "url": "https://api.github.com/repos/MYORG/MYREPO", + "name": "MYREPO" + } + } + } + ] +}, +"repository": { "id": 1234567890, "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "name": "MYREPO", diff --git a/controllers/testdata/repo_webhook_check_run_payload.json b/controllers/testdata/repo_webhook_check_run_payload.json new file mode 100644 index 0000000000..bc31a129cb --- /dev/null +++ b/controllers/testdata/repo_webhook_check_run_payload.json @@ -0,0 +1,360 @@ +{ + "action": "completed", + "check_run": { + "id": 1949438388, + "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "head_sha": "1234567890123456789012345678901234567890", + "external_id": "ca395085-040a-526b-2ce8-bdc85f692774", + "url": "https://api.github.com/repos/MYORG/MYREPO/check-runs/123467890", + "html_url": "https://github.com/MYORG/MYREPO/runs/123467890", + "details_url": "https://github.com/MYORG/MYREPO/runs/123467890", + "status": "queued", + "conclusion": null, + "started_at": "2021-02-18T06:16:31Z", + "completed_at": null, + "output": { + "title": null, + "summary": null, + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/MYORG/MYREPO/check-runs/123467890/annotations" + }, + "name": "build", + "name": "validate", + "check_suite": { + "id": 1234567890, + "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "head_branch": "MYNAME/actions-runner-controller-webhook", + "head_sha": "1234567890123456789012345678901234567890", + "status": "queued", + "conclusion": null, + "url": "https://api.github.com/repos/MYORG/MYREPO/check-suites/1234567890", + "before": "1234567890123456789012345678901234567890", + "after": "1234567890123456789012345678901234567890", + "pull_requests": [ + { + "url": "https://api.github.com/repos/MYORG/MYREPO/pulls/2033", + "id": 1234567890, + "number": 1234567890, + "head": { + "ref": "feature", + "sha": "1234567890123456789012345678901234567890", + "repo": { + "id": 1234567890, + "url": "https://api.github.com/repos/MYORG/MYREPO", + "name": "MYREPO" + } + }, + "base": { + "ref": "master", + "sha": "1234567890123456789012345678901234567890", + "repo": { + "id": 1234567890, + "url": "https://api.github.com/repos/MYORG/MYREPO", + "name": "MYREPO" + } + } + } + ], + "app": { + "id": 1234567890, + "slug": "github-actions", + "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "owner": { + "login": "github", + "id": 1234567890, + "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "avatar_url": "https://avatars.githubusercontent.com/u/123467890?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github", + "html_url": "https://github.com/github", + "followers_url": "https://api.github.com/users/github/followers", + "following_url": "https://api.github.com/users/github/following{/other_user}", + "gists_url": "https://api.github.com/users/github/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github/subscriptions", + "organizations_url": "https://api.github.com/users/github/orgs", + "repos_url": "https://api.github.com/users/github/repos", + "events_url": "https://api.github.com/users/github/events{/privacy}", + "received_events_url": "https://api.github.com/users/github/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "GitHub Actions", + "description": "Automate your workflow from idea to production", + "external_url": "https://help.github.com/en/actions", + "html_url": "https://github.com/apps/github-actions", + "created_at": "2018-07-30T09:30:17Z", + "updated_at": "2019-12-10T19:04:12Z", + "permissions": { + "actions": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "issues": "write", + "metadata": "read", + "organization_packages": "write", + "packages": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "security_events": "write", + "statuses": "write", + "vulnerability_alerts": "read" + }, + "events": [ + "check_run", + "check_suite", + "create", + "delete", + "deployment", + "deployment_status", + "fork", + "gollum", + "issues", + "issue_comment", + "label", + "milestone", + "page_build", + "project", + "project_card", + "project_column", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "push", + "registry_package", + "release", + "repository", + "repository_dispatch", + "status", + "watch", + "workflow_dispatch", + "workflow_run" + ] + }, + "created_at": "2021-02-18T06:15:32Z", + "updated_at": "2021-02-18T06:16:31Z" + }, + "app": { + "id": 1234567890, + "slug": "github-actions", + "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "owner": { + "login": "github", + "id": 1234567890, + "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "avatar_url": "https://avatars.githubusercontent.com/u/1234567890?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github", + "html_url": "https://github.com/github", + "followers_url": "https://api.github.com/users/github/followers", + "following_url": "https://api.github.com/users/github/following{/other_user}", + "gists_url": "https://api.github.com/users/github/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github/subscriptions", + "organizations_url": "https://api.github.com/users/github/orgs", + "repos_url": "https://api.github.com/users/github/repos", + "events_url": "https://api.github.com/users/github/events{/privacy}", + "received_events_url": "https://api.github.com/users/github/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "GitHub Actions", + "description": "Automate your workflow from idea to production", + "external_url": "https://help.github.com/en/actions", + "html_url": "https://github.com/apps/github-actions", + "created_at": "2018-07-30T09:30:17Z", + "updated_at": "2019-12-10T19:04:12Z", + "permissions": { + "actions": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "issues": "write", + "metadata": "read", + "organization_packages": "write", + "packages": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "security_events": "write", + "statuses": "write", + "vulnerability_alerts": "read" + }, + "events": [ + "check_run", + "check_suite", + "create", + "delete", + "deployment", + "deployment_status", + "fork", + "gollum", + "issues", + "issue_comment", + "label", + "milestone", + "page_build", + "project", + "project_card", + "project_column", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "push", + "registry_package", + "release", + "repository", + "repository_dispatch", + "status", + "watch", + "workflow_dispatch", + "workflow_run" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/MYORG/MYREPO/pulls/1234567890", + "id": 1234567890, + "number": 1234567890, + "head": { + "ref": "feature", + "sha": "1234567890123456789012345678901234567890", + "repo": { + "id": 1234567890, + "url": "https://api.github.com/repos/MYORG/MYREPO", + "name": "MYREPO" + } + }, + "base": { + "ref": "master", + "sha": "1234567890123456789012345678901234567890", + "repo": { + "id": 1234567890, + "url": "https://api.github.com/repos/MYORG/MYREPO", + "name": "MYREPO" + } + } + } + ] + }, + "repository": { + "id": 1234567890, + "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "name": "MYREPO", + "full_name": "MYORG/MYREPO", + "private": true, + "owner": { + "login": "MYUSER", + "id": 1234567890, + "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "avatar_url": "https://avatars.githubusercontent.com/u/1234567890?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/MYUSER", + "html_url": "https://github.com/MYUSER", + "followers_url": "https://api.github.com/users/MYUSER/followers", + "following_url": "https://api.github.com/users/MYUSER/following{/other_user}", + "gists_url": "https://api.github.com/users/MYUSER/gists{/gist_id}", + "starred_url": "https://api.github.com/users/MYUSER/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/MYUSER/subscriptions", + "organizations_url": "https://api.github.com/users/MYUSER/orgs", + "repos_url": "https://api.github.com/users/MYUSER/repos", + "events_url": "https://api.github.com/users/MYUSER/events{/privacy}", + "received_events_url": "https://api.github.com/users/MYUSER/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/MYUSER/MYREPO", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/MYUSER/MYREPO", + "forks_url": "https://api.github.com/repos/MYUSER/MYREPO/forks", + "keys_url": "https://api.github.com/repos/MYUSER/MYREPO/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/MYUSER/MYREPO/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/MYUSER/MYREPO/teams", + "hooks_url": "https://api.github.com/repos/MYUSER/MYREPO/hooks", + "issue_events_url": "https://api.github.com/repos/MYUSER/MYREPO/issues/events{/number}", + "events_url": "https://api.github.com/repos/MYUSER/MYREPO/events", + "assignees_url": "https://api.github.com/repos/MYUSER/MYREPO/assignees{/user}", + "branches_url": "https://api.github.com/repos/MYUSER/MYREPO/branches{/branch}", + "tags_url": "https://api.github.com/repos/MYUSER/MYREPO/tags", + "blobs_url": "https://api.github.com/repos/MYUSER/MYREPO/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/MYUSER/MYREPO/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/MYUSER/MYREPO/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/MYUSER/MYREPO/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/MYUSER/MYREPO/statuses/{sha}", + "languages_url": "https://api.github.com/repos/MYUSER/MYREPO/languages", + "stargazers_url": "https://api.github.com/repos/MYUSER/MYREPO/stargazers", + "contributors_url": "https://api.github.com/repos/MYUSER/MYREPO/contributors", + "subscribers_url": "https://api.github.com/repos/MYUSER/MYREPO/subscribers", + "subscription_url": "https://api.github.com/repos/MYUSER/MYREPO/subscription", + "commits_url": "https://api.github.com/repos/MYUSER/MYREPO/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/MYUSER/MYREPO/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/MYUSER/MYREPO/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/MYUSER/MYREPO/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/MYUSER/MYREPO/contents/{+path}", + "compare_url": "https://api.github.com/repos/MYUSER/MYREPO/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/MYUSER/MYREPO/merges", + "archive_url": "https://api.github.com/repos/MYUSER/MYREPO/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/MYUSER/MYREPO/downloads", + "issues_url": "https://api.github.com/repos/MYUSER/MYREPO/issues{/number}", + "pulls_url": "https://api.github.com/repos/MYUSER/MYREPO/pulls{/number}", + "milestones_url": "https://api.github.com/repos/MYUSER/MYREPO/milestones{/number}", + "notifications_url": "https://api.github.com/repos/MYUSER/MYREPO/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/MYUSER/MYREPO/labels{/name}", + "releases_url": "https://api.github.com/repos/MYUSER/MYREPO/releases{/id}", + "deployments_url": "https://api.github.com/repos/MYUSER/MYREPO/deployments", + "created_at": "2021-02-18T06:16:31Z", + "updated_at": "2021-02-18T06:16:31Z", + "pushed_at": "2021-02-18T06:16:31Z", + "git_url": "git://github.com/MYUSER/MYREPO.git", + "ssh_url": "git@github.com:MYUSER/MYREPO.git", + "clone_url": "https://github.com/MYUSER/MYREPO.git", + "svn_url": "https://github.com/MYUSER/MYREPO", + "homepage": null, + "size": 4, + "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": 0, + "license": null, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main" + }, + "sender": { + "login": "MYUSER", + "id": 1234567890, + "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "avatar_url": "https://avatars.githubusercontent.com/u/1234567890?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/MYUSER", + "html_url": "https://github.com/MYUSER", + "followers_url": "https://api.github.com/users/MYUSER/followers", + "following_url": "https://api.github.com/users/MYUSER/following{/other_user}", + "gists_url": "https://api.github.com/users/MYUSER/gists{/gist_id}", + "starred_url": "https://api.github.com/users/MYUSER/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/MYUSER/subscriptions", + "organizations_url": "https://api.github.com/users/MYUSER/orgs", + "repos_url": "https://api.github.com/users/MYUSER/repos", + "events_url": "https://api.github.com/users/MYUSER/events{/privacy}", + "received_events_url": "https://api.github.com/users/MYUSER/received_events", + "type": "User", + "site_admin": false + } +}