Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions github/actions_workflow_runs.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,31 @@ type ReferencedWorkflow struct {
Ref *string `json:"ref,omitempty"`
}

// PendingDeployment represents the pending_deployments response.
type PendingDeployment struct {
Environment *PendingDeploymentEnvironment `json:"environment,omitempty"`
WaitTimer *int64 `json:"wait_timer,omitempty"`
WaitTimerStartedAt *Timestamp `json:"wait_timer_started_at,omitempty"`
CurrentUserCanApprove *bool `json:"current_user_can_approve,omitempty"`
Reviewers []*RequiredReviewer `json:"reviewers,omitempty"`
}

// PendingDeploymentEnvironment represents pending deployment environment properties.
type PendingDeploymentEnvironment struct {
ID *int64 `json:"id,omitempty"`
NodeID *string `json:"node_id,omitempty"`
Name *string `json:"name,omitempty"`
URL *string `json:"url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
}

// ReviewCustomDeploymentProtectionRuleRequest specifies the parameters to ReviewCustomDeploymentProtectionRule.
type ReviewCustomDeploymentProtectionRuleRequest struct {
EnvironmentName string `json:"environment_name"`
State string `json:"state"`
Comment string `json:"comment"`
}

func (s *ActionsService) listWorkflowRuns(ctx context.Context, endpoint string, opts *ListWorkflowRunsOptions) (*WorkflowRuns, *Response, error) {
u, err := addOptions(endpoint, opts)
if err != nil {
Expand Down Expand Up @@ -388,6 +413,28 @@ func (s *ActionsService) GetWorkflowRunUsageByID(ctx context.Context, owner, rep
return workflowRunUsage, resp, nil
}

// GetPendingDeployments get all deployment environments for a workflow run that are waiting for protection rules to pass.
//
// GitHub API docs: https://docs.github.com/rest/actions/workflow-runs#get-pending-deployments-for-a-workflow-run
//
//meta:operation GET /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments
func (s *ActionsService) GetPendingDeployments(ctx context.Context, owner, repo string, runID int64) ([]*PendingDeployment, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/pending_deployments", owner, repo, runID)

req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

var deployments []*PendingDeployment
resp, err := s.client.Do(ctx, req, &deployments)
if err != nil {
return nil, resp, err
}

return deployments, resp, nil
}

// PendingDeployments approve or reject pending deployments that are waiting on approval by a required reviewer.
//
// GitHub API docs: https://docs.github.com/rest/actions/workflow-runs#review-pending-deployments-for-a-workflow-run
Expand All @@ -409,3 +456,20 @@ func (s *ActionsService) PendingDeployments(ctx context.Context, owner, repo str

return deployments, resp, nil
}

// ReviewCustomDeploymentProtectionRule approves or rejects custom deployment protection rules provided by a GitHub App for a workflow run.
//
// GitHub API docs: https://docs.github.com/rest/actions/workflow-runs#review-custom-deployment-protection-rules-for-a-workflow-run
//
//meta:operation POST /repos/{owner}/{repo}/actions/runs/{run_id}/deployment_protection_rule
func (s *ActionsService) ReviewCustomDeploymentProtectionRule(ctx context.Context, owner, repo string, runID int64, request *ReviewCustomDeploymentProtectionRuleRequest) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/deployment_protection_rule", owner, repo, runID)

req, err := s.client.NewRequest("POST", u, request)
if err != nil {
return nil, err
}

resp, err := s.client.Do(ctx, req, nil)
return resp, err
}
224 changes: 224 additions & 0 deletions github/actions_workflow_runs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,112 @@ func TestActionService_DeleteWorkflowRunLogs(t *testing.T) {
})
}

func TestPendingDeployment_Marshal(t *testing.T) {
testJSONMarshal(t, &PendingDeployment{}, "{}")

u := &PendingDeployment{
Environment: &PendingDeploymentEnvironment{
ID: Int64(1),
NodeID: String("nid"),
Name: String("n"),
URL: String("u"),
HTMLURL: String("hu"),
},
WaitTimer: Int64(100),
WaitTimerStartedAt: &Timestamp{referenceTime},
CurrentUserCanApprove: Bool(false),
Reviewers: []*RequiredReviewer{
{
Type: String("User"),
Reviewer: &User{
Login: String("l"),
},
},
{
Type: String("Team"),
Reviewer: &Team{
Name: String("n"),
},
},
},
}
want := `{
"environment": {
"id": 1,
"node_id": "nid",
"name": "n",
"url": "u",
"html_url": "hu"
},
"wait_timer": 100,
"wait_timer_started_at": ` + referenceTimeStr + `,
"current_user_can_approve": false,
"reviewers": [
{
"type": "User",
"reviewer": {
"login": "l"
}
},
{
"type": "Team",
"reviewer": {
"name": "n"
}
}
]
}`
testJSONMarshal(t, u, want)
}

func TestActionsService_ReviewCustomDeploymentProtectionRule(t *testing.T) {
client, mux, _, teardown := setup()
defer teardown()

mux.HandleFunc("/repos/o/r/actions/runs/9444496/deployment_protection_rule", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "POST")

w.WriteHeader(http.StatusNoContent)
})

request := ReviewCustomDeploymentProtectionRuleRequest{
EnvironmentName: "production",
State: "approved",
Comment: "Approve deployment",
}

ctx := context.Background()
if _, err := client.Actions.ReviewCustomDeploymentProtectionRule(ctx, "o", "r", 9444496, &request); err != nil {
t.Errorf("ReviewCustomDeploymentProtectionRule returned error: %v", err)
}

const methodName = "ReviewCustomDeploymentProtectionRule"
testBadOptions(t, methodName, func() (err error) {
_, err = client.Actions.ReviewCustomDeploymentProtectionRule(ctx, "\n", "\n", 9444496, &request)
return err
})

testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
return client.Actions.ReviewCustomDeploymentProtectionRule(ctx, "o", "r", 9444496, &request)
})
}

func TestReviewCustomDeploymentProtectionRuleRequest_Marshal(t *testing.T) {
testJSONMarshal(t, &ReviewCustomDeploymentProtectionRuleRequest{}, "{}")

r := &ReviewCustomDeploymentProtectionRuleRequest{
EnvironmentName: "e",
State: "rejected",
Comment: "c",
}
want := `{
"environment_name": "e",
"state": "rejected",
"comment": "c"
}`
testJSONMarshal(t, r, want)
}

func TestActionsService_GetWorkflowRunUsageByID(t *testing.T) {
client, mux, _, teardown := setup()
defer teardown()
Expand Down Expand Up @@ -1298,3 +1404,121 @@ func TestActionService_PendingDeployments(t *testing.T) {
return resp, err
})
}

func TestActionService_GetPendingDeployments(t *testing.T) {
client, mux, _, teardown := setup()
defer teardown()

mux.HandleFunc("/repos/o/r/actions/runs/399444496/pending_deployments", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[
{
"environment": {
"id": 1,
"node_id": "nid",
"name": "n",
"url": "u",
"html_url": "hu"
},
"wait_timer": 0,
"wait_timer_started_at": `+referenceTimeStr+`,
"current_user_can_approve": false,
"reviewers": []
},
{
"environment": {
"id": 2,
"node_id": "nid",
"name": "n",
"url": "u",
"html_url": "hu"
},
"wait_timer": 13,
"wait_timer_started_at": `+referenceTimeStr+`,
"current_user_can_approve": true,
"reviewers": [
{
"type": "User",
"reviewer": {
"login": "l"
}
},
{
"type": "Team",
"reviewer": {
"name": "t",
"slug": "s"
}
}
]
}
]`)
})

ctx := context.Background()
deployments, _, err := client.Actions.GetPendingDeployments(ctx, "o", "r", 399444496)
if err != nil {
t.Errorf("Actions.GetPendingDeployments returned error: %v", err)
}

want := []*PendingDeployment{
{
Environment: &PendingDeploymentEnvironment{
ID: Int64(1),
NodeID: String("nid"),
Name: String("n"),
URL: String("u"),
HTMLURL: String("hu"),
},
WaitTimer: Int64(0),
WaitTimerStartedAt: &Timestamp{referenceTime},
CurrentUserCanApprove: Bool(false),
Reviewers: []*RequiredReviewer{},
},
{
Environment: &PendingDeploymentEnvironment{
ID: Int64(2),
NodeID: String("nid"),
Name: String("n"),
URL: String("u"),
HTMLURL: String("hu"),
},
WaitTimer: Int64(13),
WaitTimerStartedAt: &Timestamp{referenceTime},
CurrentUserCanApprove: Bool(true),
Reviewers: []*RequiredReviewer{
{
Type: String("User"),
Reviewer: &User{
Login: String("l"),
},
},
{
Type: String("Team"),
Reviewer: &Team{
Name: String("t"),
Slug: String("s"),
},
},
},
},
}

if !cmp.Equal(deployments, want) {
t.Errorf("Actions.GetPendingDeployments returned %+v, want %+v", deployments, want)
}

const methodName = "GetPendingDeployments"
testBadOptions(t, methodName, func() (err error) {
_, _, err = client.Actions.GetPendingDeployments(ctx, "\n", "\n", 399444496)
return err
})

testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
got, resp, err := client.Actions.GetPendingDeployments(ctx, "o", "r", 399444496)
if got != nil {
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
}
return resp, err
})
}
40 changes: 40 additions & 0 deletions github/event_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,46 @@ type DeploymentProtectionRuleEvent struct {
Installation *Installation `json:"installation,omitempty"`
}

// DeploymentReviewEvent represents a deployment review event.
// The Webhook event name is "deployment_review".
//
// GitHub API docs: https://docs.github.com/webhooks-and-events/webhooks/webhook-events-and-payloads?#deployment_review
type DeploymentReviewEvent struct {
// The action performed. Possible values are: "requested", "approved", or "rejected".
Action *string `json:"action,omitempty"`

// The following will be populated only if requested.
Requester *User `json:"requester,omitempty"`
Environment *string `json:"environment,omitempty"`

// The following will be populated only if approved or rejected.
Approver *User `json:"approver,omitempty"`
Comment *string `json:"comment,omitempty"`
WorkflowJobRuns []*WorkflowJobRun `json:"workflow_job_runs,omitempty"`

Enterprise *Enterprise `json:"enterprise,omitempty"`
Installation *Installation `json:"installation,omitempty"`
Organization *Organization `json:"organization,omitempty"`
Repo *Repository `json:"repository,omitempty"`
Reviewers []*RequiredReviewer `json:"reviewers,omitempty"`
Sender *User `json:"sender,omitempty"`
Since *string `json:"since,omitempty"`
WorkflowJobRun *WorkflowJobRun `json:"workflow_job_run,omitempty"`
WorkflowRun *WorkflowRun `json:"workflow_run,omitempty"`
}

// WorkflowJobRun represents a workflow_job_run in a GitHub DeploymentReviewEvent.
type WorkflowJobRun struct {
Conclusion *string `json:"conclusion,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
Environment *string `json:"environment,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
ID *int64 `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Status *string `json:"status,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
}

// DeploymentStatusEvent represents a deployment status.
// The Webhook event name is "deployment_status".
//
Expand Down
Loading