Skip to content

Commit 3d410c2

Browse files
authored
Add support DeploymentReview Event, ReviewCustomDeploymentProtectionRule API, GetPendingDeployments API (#3254)
Fixes: #3252.
1 parent dea28a3 commit 3d410c2

8 files changed

+1441
-0
lines changed

github/actions_workflow_runs.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,31 @@ type ReferencedWorkflow struct {
112112
Ref *string `json:"ref,omitempty"`
113113
}
114114

115+
// PendingDeployment represents the pending_deployments response.
116+
type PendingDeployment struct {
117+
Environment *PendingDeploymentEnvironment `json:"environment,omitempty"`
118+
WaitTimer *int64 `json:"wait_timer,omitempty"`
119+
WaitTimerStartedAt *Timestamp `json:"wait_timer_started_at,omitempty"`
120+
CurrentUserCanApprove *bool `json:"current_user_can_approve,omitempty"`
121+
Reviewers []*RequiredReviewer `json:"reviewers,omitempty"`
122+
}
123+
124+
// PendingDeploymentEnvironment represents pending deployment environment properties.
125+
type PendingDeploymentEnvironment struct {
126+
ID *int64 `json:"id,omitempty"`
127+
NodeID *string `json:"node_id,omitempty"`
128+
Name *string `json:"name,omitempty"`
129+
URL *string `json:"url,omitempty"`
130+
HTMLURL *string `json:"html_url,omitempty"`
131+
}
132+
133+
// ReviewCustomDeploymentProtectionRuleRequest specifies the parameters to ReviewCustomDeploymentProtectionRule.
134+
type ReviewCustomDeploymentProtectionRuleRequest struct {
135+
EnvironmentName string `json:"environment_name"`
136+
State string `json:"state"`
137+
Comment string `json:"comment"`
138+
}
139+
115140
func (s *ActionsService) listWorkflowRuns(ctx context.Context, endpoint string, opts *ListWorkflowRunsOptions) (*WorkflowRuns, *Response, error) {
116141
u, err := addOptions(endpoint, opts)
117142
if err != nil {
@@ -388,6 +413,28 @@ func (s *ActionsService) GetWorkflowRunUsageByID(ctx context.Context, owner, rep
388413
return workflowRunUsage, resp, nil
389414
}
390415

416+
// GetPendingDeployments get all deployment environments for a workflow run that are waiting for protection rules to pass.
417+
//
418+
// GitHub API docs: https://docs.github.com/rest/actions/workflow-runs#get-pending-deployments-for-a-workflow-run
419+
//
420+
//meta:operation GET /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments
421+
func (s *ActionsService) GetPendingDeployments(ctx context.Context, owner, repo string, runID int64) ([]*PendingDeployment, *Response, error) {
422+
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/pending_deployments", owner, repo, runID)
423+
424+
req, err := s.client.NewRequest("GET", u, nil)
425+
if err != nil {
426+
return nil, nil, err
427+
}
428+
429+
var deployments []*PendingDeployment
430+
resp, err := s.client.Do(ctx, req, &deployments)
431+
if err != nil {
432+
return nil, resp, err
433+
}
434+
435+
return deployments, resp, nil
436+
}
437+
391438
// PendingDeployments approve or reject pending deployments that are waiting on approval by a required reviewer.
392439
//
393440
// GitHub API docs: https://docs.github.com/rest/actions/workflow-runs#review-pending-deployments-for-a-workflow-run
@@ -409,3 +456,20 @@ func (s *ActionsService) PendingDeployments(ctx context.Context, owner, repo str
409456

410457
return deployments, resp, nil
411458
}
459+
460+
// ReviewCustomDeploymentProtectionRule approves or rejects custom deployment protection rules provided by a GitHub App for a workflow run.
461+
//
462+
// GitHub API docs: https://docs.github.com/rest/actions/workflow-runs#review-custom-deployment-protection-rules-for-a-workflow-run
463+
//
464+
//meta:operation POST /repos/{owner}/{repo}/actions/runs/{run_id}/deployment_protection_rule
465+
func (s *ActionsService) ReviewCustomDeploymentProtectionRule(ctx context.Context, owner, repo string, runID int64, request *ReviewCustomDeploymentProtectionRuleRequest) (*Response, error) {
466+
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/deployment_protection_rule", owner, repo, runID)
467+
468+
req, err := s.client.NewRequest("POST", u, request)
469+
if err != nil {
470+
return nil, err
471+
}
472+
473+
resp, err := s.client.Do(ctx, req, nil)
474+
return resp, err
475+
}

github/actions_workflow_runs_test.go

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,112 @@ func TestActionService_DeleteWorkflowRunLogs(t *testing.T) {
571571
})
572572
}
573573

574+
func TestPendingDeployment_Marshal(t *testing.T) {
575+
testJSONMarshal(t, &PendingDeployment{}, "{}")
576+
577+
u := &PendingDeployment{
578+
Environment: &PendingDeploymentEnvironment{
579+
ID: Int64(1),
580+
NodeID: String("nid"),
581+
Name: String("n"),
582+
URL: String("u"),
583+
HTMLURL: String("hu"),
584+
},
585+
WaitTimer: Int64(100),
586+
WaitTimerStartedAt: &Timestamp{referenceTime},
587+
CurrentUserCanApprove: Bool(false),
588+
Reviewers: []*RequiredReviewer{
589+
{
590+
Type: String("User"),
591+
Reviewer: &User{
592+
Login: String("l"),
593+
},
594+
},
595+
{
596+
Type: String("Team"),
597+
Reviewer: &Team{
598+
Name: String("n"),
599+
},
600+
},
601+
},
602+
}
603+
want := `{
604+
"environment": {
605+
"id": 1,
606+
"node_id": "nid",
607+
"name": "n",
608+
"url": "u",
609+
"html_url": "hu"
610+
},
611+
"wait_timer": 100,
612+
"wait_timer_started_at": ` + referenceTimeStr + `,
613+
"current_user_can_approve": false,
614+
"reviewers": [
615+
{
616+
"type": "User",
617+
"reviewer": {
618+
"login": "l"
619+
}
620+
},
621+
{
622+
"type": "Team",
623+
"reviewer": {
624+
"name": "n"
625+
}
626+
}
627+
]
628+
}`
629+
testJSONMarshal(t, u, want)
630+
}
631+
632+
func TestActionsService_ReviewCustomDeploymentProtectionRule(t *testing.T) {
633+
client, mux, _, teardown := setup()
634+
defer teardown()
635+
636+
mux.HandleFunc("/repos/o/r/actions/runs/9444496/deployment_protection_rule", func(w http.ResponseWriter, r *http.Request) {
637+
testMethod(t, r, "POST")
638+
639+
w.WriteHeader(http.StatusNoContent)
640+
})
641+
642+
request := ReviewCustomDeploymentProtectionRuleRequest{
643+
EnvironmentName: "production",
644+
State: "approved",
645+
Comment: "Approve deployment",
646+
}
647+
648+
ctx := context.Background()
649+
if _, err := client.Actions.ReviewCustomDeploymentProtectionRule(ctx, "o", "r", 9444496, &request); err != nil {
650+
t.Errorf("ReviewCustomDeploymentProtectionRule returned error: %v", err)
651+
}
652+
653+
const methodName = "ReviewCustomDeploymentProtectionRule"
654+
testBadOptions(t, methodName, func() (err error) {
655+
_, err = client.Actions.ReviewCustomDeploymentProtectionRule(ctx, "\n", "\n", 9444496, &request)
656+
return err
657+
})
658+
659+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
660+
return client.Actions.ReviewCustomDeploymentProtectionRule(ctx, "o", "r", 9444496, &request)
661+
})
662+
}
663+
664+
func TestReviewCustomDeploymentProtectionRuleRequest_Marshal(t *testing.T) {
665+
testJSONMarshal(t, &ReviewCustomDeploymentProtectionRuleRequest{}, "{}")
666+
667+
r := &ReviewCustomDeploymentProtectionRuleRequest{
668+
EnvironmentName: "e",
669+
State: "rejected",
670+
Comment: "c",
671+
}
672+
want := `{
673+
"environment_name": "e",
674+
"state": "rejected",
675+
"comment": "c"
676+
}`
677+
testJSONMarshal(t, r, want)
678+
}
679+
574680
func TestActionsService_GetWorkflowRunUsageByID(t *testing.T) {
575681
client, mux, _, teardown := setup()
576682
defer teardown()
@@ -1298,3 +1404,121 @@ func TestActionService_PendingDeployments(t *testing.T) {
12981404
return resp, err
12991405
})
13001406
}
1407+
1408+
func TestActionService_GetPendingDeployments(t *testing.T) {
1409+
client, mux, _, teardown := setup()
1410+
defer teardown()
1411+
1412+
mux.HandleFunc("/repos/o/r/actions/runs/399444496/pending_deployments", func(w http.ResponseWriter, r *http.Request) {
1413+
testMethod(t, r, "GET")
1414+
fmt.Fprint(w, `[
1415+
{
1416+
"environment": {
1417+
"id": 1,
1418+
"node_id": "nid",
1419+
"name": "n",
1420+
"url": "u",
1421+
"html_url": "hu"
1422+
},
1423+
"wait_timer": 0,
1424+
"wait_timer_started_at": `+referenceTimeStr+`,
1425+
"current_user_can_approve": false,
1426+
"reviewers": []
1427+
},
1428+
{
1429+
"environment": {
1430+
"id": 2,
1431+
"node_id": "nid",
1432+
"name": "n",
1433+
"url": "u",
1434+
"html_url": "hu"
1435+
},
1436+
"wait_timer": 13,
1437+
"wait_timer_started_at": `+referenceTimeStr+`,
1438+
"current_user_can_approve": true,
1439+
"reviewers": [
1440+
{
1441+
"type": "User",
1442+
"reviewer": {
1443+
"login": "l"
1444+
}
1445+
},
1446+
{
1447+
"type": "Team",
1448+
"reviewer": {
1449+
"name": "t",
1450+
"slug": "s"
1451+
}
1452+
}
1453+
]
1454+
}
1455+
]`)
1456+
})
1457+
1458+
ctx := context.Background()
1459+
deployments, _, err := client.Actions.GetPendingDeployments(ctx, "o", "r", 399444496)
1460+
if err != nil {
1461+
t.Errorf("Actions.GetPendingDeployments returned error: %v", err)
1462+
}
1463+
1464+
want := []*PendingDeployment{
1465+
{
1466+
Environment: &PendingDeploymentEnvironment{
1467+
ID: Int64(1),
1468+
NodeID: String("nid"),
1469+
Name: String("n"),
1470+
URL: String("u"),
1471+
HTMLURL: String("hu"),
1472+
},
1473+
WaitTimer: Int64(0),
1474+
WaitTimerStartedAt: &Timestamp{referenceTime},
1475+
CurrentUserCanApprove: Bool(false),
1476+
Reviewers: []*RequiredReviewer{},
1477+
},
1478+
{
1479+
Environment: &PendingDeploymentEnvironment{
1480+
ID: Int64(2),
1481+
NodeID: String("nid"),
1482+
Name: String("n"),
1483+
URL: String("u"),
1484+
HTMLURL: String("hu"),
1485+
},
1486+
WaitTimer: Int64(13),
1487+
WaitTimerStartedAt: &Timestamp{referenceTime},
1488+
CurrentUserCanApprove: Bool(true),
1489+
Reviewers: []*RequiredReviewer{
1490+
{
1491+
Type: String("User"),
1492+
Reviewer: &User{
1493+
Login: String("l"),
1494+
},
1495+
},
1496+
{
1497+
Type: String("Team"),
1498+
Reviewer: &Team{
1499+
Name: String("t"),
1500+
Slug: String("s"),
1501+
},
1502+
},
1503+
},
1504+
},
1505+
}
1506+
1507+
if !cmp.Equal(deployments, want) {
1508+
t.Errorf("Actions.GetPendingDeployments returned %+v, want %+v", deployments, want)
1509+
}
1510+
1511+
const methodName = "GetPendingDeployments"
1512+
testBadOptions(t, methodName, func() (err error) {
1513+
_, _, err = client.Actions.GetPendingDeployments(ctx, "\n", "\n", 399444496)
1514+
return err
1515+
})
1516+
1517+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
1518+
got, resp, err := client.Actions.GetPendingDeployments(ctx, "o", "r", 399444496)
1519+
if got != nil {
1520+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
1521+
}
1522+
return resp, err
1523+
})
1524+
}

github/event_types.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,46 @@ type DeploymentProtectionRuleEvent struct {
225225
Installation *Installation `json:"installation,omitempty"`
226226
}
227227

228+
// DeploymentReviewEvent represents a deployment review event.
229+
// The Webhook event name is "deployment_review".
230+
//
231+
// GitHub API docs: https://docs.github.com/webhooks-and-events/webhooks/webhook-events-and-payloads?#deployment_review
232+
type DeploymentReviewEvent struct {
233+
// The action performed. Possible values are: "requested", "approved", or "rejected".
234+
Action *string `json:"action,omitempty"`
235+
236+
// The following will be populated only if requested.
237+
Requester *User `json:"requester,omitempty"`
238+
Environment *string `json:"environment,omitempty"`
239+
240+
// The following will be populated only if approved or rejected.
241+
Approver *User `json:"approver,omitempty"`
242+
Comment *string `json:"comment,omitempty"`
243+
WorkflowJobRuns []*WorkflowJobRun `json:"workflow_job_runs,omitempty"`
244+
245+
Enterprise *Enterprise `json:"enterprise,omitempty"`
246+
Installation *Installation `json:"installation,omitempty"`
247+
Organization *Organization `json:"organization,omitempty"`
248+
Repo *Repository `json:"repository,omitempty"`
249+
Reviewers []*RequiredReviewer `json:"reviewers,omitempty"`
250+
Sender *User `json:"sender,omitempty"`
251+
Since *string `json:"since,omitempty"`
252+
WorkflowJobRun *WorkflowJobRun `json:"workflow_job_run,omitempty"`
253+
WorkflowRun *WorkflowRun `json:"workflow_run,omitempty"`
254+
}
255+
256+
// WorkflowJobRun represents a workflow_job_run in a GitHub DeploymentReviewEvent.
257+
type WorkflowJobRun struct {
258+
Conclusion *string `json:"conclusion,omitempty"`
259+
CreatedAt *Timestamp `json:"created_at,omitempty"`
260+
Environment *string `json:"environment,omitempty"`
261+
HTMLURL *string `json:"html_url,omitempty"`
262+
ID *int64 `json:"id,omitempty"`
263+
Name *string `json:"name,omitempty"`
264+
Status *string `json:"status,omitempty"`
265+
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
266+
}
267+
228268
// DeploymentStatusEvent represents a deployment status.
229269
// The Webhook event name is "deployment_status".
230270
//

0 commit comments

Comments
 (0)