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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@ func SetupIntegrationTest(ctx context.Context) *testEnvironment {
|
||||||
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")
|
||||||
|
|
|
||||||
|
|
@ -241,8 +241,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"repository": {
|
"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