Fix panic on webhook for user-owned repository (#344)
* Fix panic on webhook for user-owned repository Follow-up for #282 and #334
This commit is contained in:
		
							parent
							
								
									2d7fbbfb68
								
							
						
					
					
						commit
						991535e567
					
				|  | @ -24,6 +24,7 @@ import ( | ||||||
| 	"k8s.io/apimachinery/pkg/types" | 	"k8s.io/apimachinery/pkg/types" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/reconcile" | 	"sigs.k8s.io/controller-runtime/pkg/reconcile" | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/go-logr/logr" | 	"github.com/go-logr/logr" | ||||||
|  | @ -133,22 +134,25 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) Handle(w http.Respons | ||||||
| 	case *gogithub.PushEvent: | 	case *gogithub.PushEvent: | ||||||
| 		target, err = autoscaler.getScaleUpTarget( | 		target, err = autoscaler.getScaleUpTarget( | ||||||
| 			context.TODO(), | 			context.TODO(), | ||||||
| 			*e.Repo.Name, | 			e.Repo.GetName(), | ||||||
| 			*e.Repo.Organization, | 			e.Repo.Owner.GetLogin(), | ||||||
|  | 			e.Repo.Owner.GetType(), | ||||||
| 			autoscaler.MatchPushEvent(e), | 			autoscaler.MatchPushEvent(e), | ||||||
| 		) | 		) | ||||||
| 	case *gogithub.PullRequestEvent: | 	case *gogithub.PullRequestEvent: | ||||||
| 		target, err = autoscaler.getScaleUpTarget( | 		target, err = autoscaler.getScaleUpTarget( | ||||||
| 			context.TODO(), | 			context.TODO(), | ||||||
| 			*e.Repo.Name, | 			e.Repo.GetName(), | ||||||
| 			*e.Repo.Organization.Name, | 			e.Repo.Owner.GetLogin(), | ||||||
|  | 			e.Repo.Owner.GetType(), | ||||||
| 			autoscaler.MatchPullRequestEvent(e), | 			autoscaler.MatchPullRequestEvent(e), | ||||||
| 		) | 		) | ||||||
| 	case *gogithub.CheckRunEvent: | 	case *gogithub.CheckRunEvent: | ||||||
| 		target, err = autoscaler.getScaleUpTarget( | 		target, err = autoscaler.getScaleUpTarget( | ||||||
| 			context.TODO(), | 			context.TODO(), | ||||||
| 			e.GetRepo().GetName(), | 			e.Repo.GetName(), | ||||||
| 			e.GetOrg().GetLogin(), // empty string if the repo is not in an organization
 | 			e.Repo.Owner.GetLogin(), | ||||||
|  | 			e.Repo.Owner.GetType(), | ||||||
| 			autoscaler.MatchCheckRunEvent(e), | 			autoscaler.MatchCheckRunEvent(e), | ||||||
| 		) | 		) | ||||||
| 	case *gogithub.PingEvent: | 	case *gogithub.PingEvent: | ||||||
|  | @ -227,6 +231,10 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) findHRAsByKey(ctx con | ||||||
| 		opts := append([]client.ListOption{}, defaultListOpts...) | 		opts := append([]client.ListOption{}, defaultListOpts...) | ||||||
| 		opts = append(opts, client.MatchingFields{scaleTargetKey: value}) | 		opts = append(opts, client.MatchingFields{scaleTargetKey: value}) | ||||||
| 
 | 
 | ||||||
|  | 		if autoscaler.WatchNamespace != "" { | ||||||
|  | 			opts = append(opts, client.InNamespace(autoscaler.WatchNamespace)) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		var hraList v1alpha1.HorizontalRunnerAutoscalerList | 		var hraList v1alpha1.HorizontalRunnerAutoscalerList | ||||||
| 
 | 
 | ||||||
| 		if err := autoscaler.List(ctx, &hraList, opts...); err != nil { | 		if err := autoscaler.List(ctx, &hraList, opts...); err != nil { | ||||||
|  | @ -296,27 +304,45 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getScaleTarget(ctx co | ||||||
| 	targets := autoscaler.searchScaleTargets(hras, f) | 	targets := autoscaler.searchScaleTargets(hras, f) | ||||||
| 
 | 
 | ||||||
| 	if len(targets) != 1 { | 	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 nil, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return &targets[0], nil | 	return &targets[0], nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getScaleUpTarget(ctx context.Context, repoNameFromWebhook, orgNameFromWebhook string, f func(v1alpha1.ScaleUpTrigger) bool) (*ScaleTarget, error) { | func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getScaleUpTarget(ctx context.Context, repo, owner, ownerType string, f func(v1alpha1.ScaleUpTrigger) bool) (*ScaleTarget, error) { | ||||||
| 	repositoryRunnerKey := orgNameFromWebhook + "/" + repoNameFromWebhook | 	repositoryRunnerKey := owner + "/" + repo | ||||||
|  | 
 | ||||||
| 	autoscaler.Log.Info("finding repository-wide runner", "repository", repositoryRunnerKey) | 	autoscaler.Log.Info("finding repository-wide runner", "repository", repositoryRunnerKey) | ||||||
| 	if target, err := autoscaler.getScaleTarget(ctx, repositoryRunnerKey, f); err != nil { | 	if target, err := autoscaler.getScaleTarget(ctx, repositoryRunnerKey, f); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} else if target != nil { | 	} 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 | 		return target, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	autoscaler.Log.Info("finding organizational runner", "organization", orgNameFromWebhook) | 	if ownerType == "User" { | ||||||
| 	if target, err := autoscaler.getScaleTarget(ctx, orgNameFromWebhook, f); err != nil { | 		return nil, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	autoscaler.Log.Info("finding organizational runner", "organization", owner) | ||||||
|  | 	if target, err := autoscaler.getScaleTarget(ctx, owner, f); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} else if target != nil { | 	} 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 | 		return target, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -28,8 +28,26 @@ func init() { | ||||||
| 	_ = actionsv1alpha1.AddToScheme(sc) | 	_ = actionsv1alpha1.AddToScheme(sc) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestWebhookCheckRun(t *testing.T) { | func TestOrgWebhookCheckRun(t *testing.T) { | ||||||
| 	f, err := os.Open("testdata/webhook_check_run_payload.json") | 	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 { | 	if err != nil { | ||||||
| 		t.Fatalf("could not open the fixture: %s", err) | 		t.Fatalf("could not open the fixture: %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -131,11 +131,12 @@ func SetupIntegrationTest(ctx context.Context) *testEnvironment { | ||||||
| 		Expect(err).NotTo(HaveOccurred(), "failed to setup controller") | 		Expect(err).NotTo(HaveOccurred(), "failed to setup controller") | ||||||
| 
 | 
 | ||||||
| 		autoscalerWebhook := &HorizontalRunnerAutoscalerGitHubWebhook{ | 		autoscalerWebhook := &HorizontalRunnerAutoscalerGitHubWebhook{ | ||||||
| 			Client:   mgr.GetClient(), | 			Client:         mgr.GetClient(), | ||||||
| 			Scheme:   scheme.Scheme, | 			Scheme:         scheme.Scheme, | ||||||
| 			Log:      logf.Log, | 			Log:            logf.Log, | ||||||
| 			Recorder: mgr.GetEventRecorderFor("horizontalrunnerautoscaler-controller"), | 			Recorder:       mgr.GetEventRecorderFor("horizontalrunnerautoscaler-controller"), | ||||||
| 			Name:     controllerName("horizontalrunnerautoscalergithubwebhook"), | 			Name:           controllerName("horizontalrunnerautoscalergithubwebhook"), | ||||||
|  | 			WatchNamespace: ns.Name, | ||||||
| 		} | 		} | ||||||
| 		err = autoscalerWebhook.SetupWithManager(mgr) | 		err = autoscalerWebhook.SetupWithManager(mgr) | ||||||
| 		Expect(err).NotTo(HaveOccurred(), "failed to setup autoscaler webhook") | 		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() { | 	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" | 			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
 | 			// 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") | 				ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook") | ||||||
| 				ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 2, "runners after first webhook event") | 				ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 2, "runners after first webhook event") | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			// Scale-up to 3 replicas on second pull_request create 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") | 				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" | 			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
 | 			// 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") | 				ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook") | ||||||
| 				ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 4, "runners after first webhook event") | 				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
 | 			// 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") | 				ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 5, "runners after second webhook event") | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			env.ExpectRegisteredNumberCountEventuallyEquals(5, "count of fake list runners") | 			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") | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
| 	}) | 	}) | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  | @ -416,10 +648,10 @@ func (env *testEnvironment) ExpectRegisteredNumberCountEventuallyEquals(want int | ||||||
| 
 | 
 | ||||||
| 			return len(rs) | 			return len(rs) | ||||||
| 		}, | 		}, | ||||||
| 		time.Second*1, time.Millisecond*500).Should(Equal(want), optionalDescriptions...) | 		time.Second*5, 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{ | 	resp, err := sendWebhook(env.webhookServer, "pull_request", &github.PullRequestEvent{ | ||||||
| 		PullRequest: &github.PullRequest{ | 		PullRequest: &github.PullRequest{ | ||||||
| 			Base: &github.PullRequestBranch{ | 			Base: &github.PullRequestBranch{ | ||||||
|  | @ -428,8 +660,9 @@ func (env *testEnvironment) SendPullRequestEvent(org, repo, branch, action strin | ||||||
| 		}, | 		}, | ||||||
| 		Repo: &github.Repository{ | 		Repo: &github.Repository{ | ||||||
| 			Name: github.String(repo), | 			Name: github.String(repo), | ||||||
| 			Organization: &github.Organization{ | 			Owner: &github.User{ | ||||||
| 				Name: github.String(org), | 				Login: github.String(org), | ||||||
|  | 				Type:  github.String("Organization"), | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		Action: github.String(action), | 		Action: github.String(action), | ||||||
|  | @ -440,7 +673,7 @@ func (env *testEnvironment) SendPullRequestEvent(org, repo, branch, action strin | ||||||
| 	ExpectWithOffset(1, resp.StatusCode).To(Equal(200)) | 	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{ | 	resp, err := sendWebhook(env.webhookServer, "check_run", &github.CheckRunEvent{ | ||||||
| 		CheckRun: &github.CheckRun{ | 		CheckRun: &github.CheckRun{ | ||||||
| 			Status: github.String(status), | 			Status: github.String(status), | ||||||
|  | @ -450,6 +683,52 @@ func (env *testEnvironment) SendCheckRunEvent(org, repo, status, action string) | ||||||
| 		}, | 		}, | ||||||
| 		Repo: &github.Repository{ | 		Repo: &github.Repository{ | ||||||
| 			Name: github.String(repo), | 			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), | 		Action: github.String(action), | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
|  | @ -82,6 +82,7 @@ func SetupDeploymentTest(ctx context.Context) *corev1.Namespace { | ||||||
| 			Scheme:   scheme.Scheme, | 			Scheme:   scheme.Scheme, | ||||||
| 			Log:      logf.Log, | 			Log:      logf.Log, | ||||||
| 			Recorder: mgr.GetEventRecorderFor("runnerreplicaset-controller"), | 			Recorder: mgr.GetEventRecorderFor("runnerreplicaset-controller"), | ||||||
|  | 			Name:     "runnerdeployment-" + ns.Name, | ||||||
| 		} | 		} | ||||||
| 		err = controller.SetupWithManager(mgr) | 		err = controller.SetupWithManager(mgr) | ||||||
| 		Expect(err).NotTo(HaveOccurred(), "failed to setup controller") | 		Expect(err).NotTo(HaveOccurred(), "failed to setup controller") | ||||||
|  |  | ||||||
|  | @ -60,6 +60,7 @@ func SetupTest(ctx context.Context) *corev1.Namespace { | ||||||
| 			Log:          logf.Log, | 			Log:          logf.Log, | ||||||
| 			Recorder:     mgr.GetEventRecorderFor("runnerreplicaset-controller"), | 			Recorder:     mgr.GetEventRecorderFor("runnerreplicaset-controller"), | ||||||
| 			GitHubClient: ghClient, | 			GitHubClient: ghClient, | ||||||
|  | 			Name:         "runnerreplicaset-" + ns.Name, | ||||||
| 		} | 		} | ||||||
| 		err = controller.SetupWithManager(mgr) | 		err = controller.SetupWithManager(mgr) | ||||||
| 		Expect(err).NotTo(HaveOccurred(), "failed to setup controller") | 		Expect(err).NotTo(HaveOccurred(), "failed to setup controller") | ||||||
|  |  | ||||||
|  | @ -138,111 +138,111 @@ | ||||||
|       "updated_at": "2021-02-18T06:16:31Z" |       "updated_at": "2021-02-18T06:16:31Z" | ||||||
|     }, |     }, | ||||||
|     "app": { |     "app": { | ||||||
|  |     "id": 1234567890, | ||||||
|  |     "slug": "github-actions", | ||||||
|  |     "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", | ||||||
|  |     "owner": { | ||||||
|  |       "login": "github", | ||||||
|       "id": 1234567890, |       "id": 1234567890, | ||||||
|       "slug": "github-actions", |  | ||||||
|       "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", |       "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", | ||||||
|       "owner": { |       "avatar_url": "https://avatars.githubusercontent.com/u/1234567890?v=4", | ||||||
|         "login": "github", |       "gravatar_id": "", | ||||||
|         "id": 1234567890, |       "url": "https://api.github.com/users/github", | ||||||
|         "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", |       "html_url": "https://github.com/github", | ||||||
|         "avatar_url": "https://avatars.githubusercontent.com/u/1234567890?v=4", |       "followers_url": "https://api.github.com/users/github/followers", | ||||||
|         "gravatar_id": "", |       "following_url": "https://api.github.com/users/github/following{/other_user}", | ||||||
|         "url": "https://api.github.com/users/github", |       "gists_url": "https://api.github.com/users/github/gists{/gist_id}", | ||||||
|         "html_url": "https://github.com/github", |       "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", | ||||||
|         "followers_url": "https://api.github.com/users/github/followers", |       "subscriptions_url": "https://api.github.com/users/github/subscriptions", | ||||||
|         "following_url": "https://api.github.com/users/github/following{/other_user}", |       "organizations_url": "https://api.github.com/users/github/orgs", | ||||||
|         "gists_url": "https://api.github.com/users/github/gists{/gist_id}", |       "repos_url": "https://api.github.com/users/github/repos", | ||||||
|         "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", |       "events_url": "https://api.github.com/users/github/events{/privacy}", | ||||||
|         "subscriptions_url": "https://api.github.com/users/github/subscriptions", |       "received_events_url": "https://api.github.com/users/github/received_events", | ||||||
|         "organizations_url": "https://api.github.com/users/github/orgs", |       "type": "Organization", | ||||||
|         "repos_url": "https://api.github.com/users/github/repos", |       "site_admin": false | ||||||
|         "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": [ |     "name": "GitHub Actions", | ||||||
|       { |     "description": "Automate your workflow from idea to production", | ||||||
|         "url": "https://api.github.com/repos/MYORG/MYREPO/pulls/1234567890", |     "external_url": "https://help.github.com/en/actions", | ||||||
|         "id": 1234567890, |     "html_url": "https://github.com/apps/github-actions", | ||||||
|         "number": 1234567890, |     "created_at": "2018-07-30T09:30:17Z", | ||||||
|         "head": { |     "updated_at": "2019-12-10T19:04:12Z", | ||||||
|           "ref": "feature", |     "permissions": { | ||||||
|           "sha": "1234567890123456789012345678901234567890", |       "actions": "write", | ||||||
|           "repo": { |       "checks": "write", | ||||||
|             "id": 1234567890, |       "contents": "write", | ||||||
|             "url": "https://api.github.com/repos/MYORG/MYREPO", |       "deployments": "write", | ||||||
|             "name": "MYREPO" |       "issues": "write", | ||||||
|           } |       "metadata": "read", | ||||||
|         }, |       "organization_packages": "write", | ||||||
|         "base": { |       "packages": "write", | ||||||
|           "ref": "master", |       "pages": "write", | ||||||
|           "sha": "1234567890123456789012345678901234567890", |       "pull_requests": "write", | ||||||
|           "repo": { |       "repository_hooks": "write", | ||||||
|             "id": 1234567890, |       "repository_projects": "write", | ||||||
|             "url": "https://api.github.com/repos/MYORG/MYREPO", |       "security_events": "write", | ||||||
|             "name": "MYREPO" |       "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, |     "id": 1234567890, | ||||||
|     "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", |     "node_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", | ||||||
|     "name": "MYREPO", |     "name": "MYREPO", | ||||||
|  | @ -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 | ||||||
|  |   } | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue