Repository: google/go-github
Pull request #2716 (2023-03-23 08:56:20)

Add download capability for workflow run attempt logs
Fixes: #2715 


codecov[bot]: ## [Codecov](https://codecov.io/gh/google/go-github/pull/2716?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#2716](https://codecov.io/gh/google/go-github/pull/2716?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (5bc7592) into [master](https://codecov.io/gh/google/go-github/commit/31350112a17a67bdc672f2a73865a2985b15fd60?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (3135011) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

```diff
@@           Coverage Diff           @@
##           master    #2716   +/-   ##
=======================================
  Coverage   98.05%   98.05%           
=======================================
  Files         131      131           
  Lines       11459    11473   +14     
=======================================
+ Hits        11236    11250   +14     
  Misses        152      152           
  Partials       71       71           
```


| [Impacted Files](https://codecov.io/gh/google/go-github/pull/2716?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) | Coverage Δ | |
|---|---|---|
| [github/actions\_workflow\_runs.go](https://codecov.io/gh/google/go-github/pull/2716?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL2FjdGlvbnNfd29ya2Zsb3dfcnVucy5nbw==) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)


Patch:
diff --git a/github/actions_workflow_runs.go b/github/actions_workflow_runs.go
index 0cda90d4..b37a9f31 100644
--- a/github/actions_workflow_runs.go
+++ b/github/actions_workflow_runs.go
@@ -204,6 +204,26 @@ func (s *ActionsService) GetWorkflowRunAttempt(ctx context.Context, owner, repo
 	return run, resp, nil
 }
 
+// GetWorkflowRunAttemptLogs gets a redirect URL to download a plain text file of logs for a workflow run for attempt number.
+//
+// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#download-workflow-run-attempt-logs
+func (s *ActionsService) GetWorkflowRunAttemptLogs(ctx context.Context, owner, repo string, runID int64, attemptNumber int, followRedirects bool) (*url.URL, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/attempts/%v/logs", owner, repo, runID, attemptNumber)
+
+	resp, err := s.client.roundTripWithOptionalFollowRedirect(ctx, u, followRedirects)
+	if err != nil {
+		return nil, nil, err
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusFound {
+		return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status)
+	}
+
+	parsedURL, err := url.Parse(resp.Header.Get("Location"))
+	return parsedURL, newResponse(resp), err
+}
+
 // RerunWorkflowByID re-runs a workflow by ID.
 //
 // GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#re-run-a-workflow
diff --git a/github/billing.go b/github/billing.go
index 2900a016..7a76bf86 100644
--- a/github/billing.go
+++ b/github/billing.go
@@ -120,9 +120,14 @@ func (s *BillingService) GetStorageBillingOrg(ctx context.Context, org string) (
 
 // GetAdvancedSecurityActiveCommittersOrg returns the GitHub Advanced Security active committers for an organization per repository.
 //
-// GitHub API docs: https://docs.github.com/en/rest/billing#get-github-advanced-security-active-committers-for-an-organization
-func (s *BillingService) GetAdvancedSecurityActiveCommittersOrg(ctx context.Context, org string) (*ActiveCommitters, *Response, error) {
+// GitHub API docs: https://docs.github.com/en/enterprise-cloud@latest/rest/billing?apiVersion=2022-11-28#get-github-advanced-security-active-committers-for-an-organization
+func (s *BillingService) GetAdvancedSecurityActiveCommittersOrg(ctx context.Context, org string, opts *ListOptions) (*ActiveCommitters, *Response, error) {
 	u := fmt.Sprintf("orgs/%v/settings/billing/advanced-security", org)
+	u, err := addOptions(u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
 	req, err := s.client.NewRequest("GET", u, nil)
 	if err != nil {
 		return nil, nil, err


Test patch:
diff --git a/github/actions_workflow_runs_test.go b/github/actions_workflow_runs_test.go
index 641c4d73..74b5bdd0 100644
--- a/github/actions_workflow_runs_test.go
+++ b/github/actions_workflow_runs_test.go
@@ -188,6 +188,89 @@ func TestActionsService_GetWorkflowRunAttempt(t *testing.T) {
 	})
 }
 
+func TestActionsService_GetWorkflowRunAttemptLogs(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/actions/runs/399444496/attempts/2/logs", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		http.Redirect(w, r, "http://github.com/a", http.StatusFound)
+	})
+
+	ctx := context.Background()
+	url, resp, err := client.Actions.GetWorkflowRunAttemptLogs(ctx, "o", "r", 399444496, 2, true)
+	if err != nil {
+		t.Errorf("Actions.GetWorkflowRunAttemptLogs returned error: %v", err)
+	}
+	if resp.StatusCode != http.StatusFound {
+		t.Errorf("Actions.GetWorkflowRunAttemptLogs returned status: %d, want %d", resp.StatusCode, http.StatusFound)
+	}
+	want := "http://github.com/a"
+	if url.String() != want {
+		t.Errorf("Actions.GetWorkflowRunAttemptLogs returned %+v, want %+v", url.String(), want)
+	}
+
+	const methodName = "GetWorkflowRunAttemptLogs"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Actions.GetWorkflowRunAttemptLogs(ctx, "\n", "\n", 399444496, 2, true)
+		return err
+	})
+}
+
+func TestActionsService_GetWorkflowRunAttemptLogs_StatusMovedPermanently_dontFollowRedirects(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/actions/runs/399444496/attempts/2/logs", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		http.Redirect(w, r, "http://github.com/a", http.StatusMovedPermanently)
+	})
+
+	ctx := context.Background()
+	_, resp, _ := client.Actions.GetWorkflowRunAttemptLogs(ctx, "o", "r", 399444496, 2, false)
+	if resp.StatusCode != http.StatusMovedPermanently {
+		t.Errorf("Actions.GetWorkflowRunAttemptLogs returned status: %d, want %d", resp.StatusCode, http.StatusMovedPermanently)
+	}
+}
+
+func TestActionsService_GetWorkflowRunAttemptLogs_StatusMovedPermanently_followRedirects(t *testing.T) {
+	client, mux, serverURL, teardown := setup()
+	defer teardown()
+
+	// Mock a redirect link, which leads to an archive link
+	mux.HandleFunc("/repos/o/r/actions/runs/399444496/attempts/2/logs", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		redirectURL, _ := url.Parse(serverURL + baseURLPath + "/redirect")
+		http.Redirect(w, r, redirectURL.String(), http.StatusMovedPermanently)
+	})
+
+	mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		http.Redirect(w, r, "http://github.com/a", http.StatusFound)
+	})
+
+	ctx := context.Background()
+	url, resp, err := client.Actions.GetWorkflowRunAttemptLogs(ctx, "o", "r", 399444496, 2, true)
+	if err != nil {
+		t.Errorf("Actions.GetWorkflowRunAttemptLogs returned error: %v", err)
+	}
+
+	if resp.StatusCode != http.StatusFound {
+		t.Errorf("Actions.GetWorkflowRunAttemptLogs returned status: %d, want %d", resp.StatusCode, http.StatusFound)
+	}
+
+	want := "http://github.com/a"
+	if url.String() != want {
+		t.Errorf("Actions.GetWorkflowRunAttemptLogs returned %+v, want %+v", url.String(), want)
+	}
+
+	const methodName = "GetWorkflowRunAttemptLogs"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Actions.GetWorkflowRunAttemptLogs(ctx, "\n", "\n", 399444496, 2, true)
+		return err
+	})
+}
+
 func TestActionsService_RerunWorkflowRunByID(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
diff --git a/github/billing_test.go b/github/billing_test.go
index 52b338b8..d7adcb09 100644
--- a/github/billing_test.go
+++ b/github/billing_test.go
@@ -452,7 +452,8 @@ func TestBillingService_GetAdvancedSecurityActiveCommittersOrg(t *testing.T) {
 	})
 
 	ctx := context.Background()
-	hook, _, err := client.Billing.GetAdvancedSecurityActiveCommittersOrg(ctx, "o")
+	opts := &ListOptions{Page: 2, PerPage: 50}
+	hook, _, err := client.Billing.GetAdvancedSecurityActiveCommittersOrg(ctx, "o", opts)
 	if err != nil {
 		t.Errorf("Billing.GetAdvancedSecurityActiveCommittersOrg	 returned error: %v", err)
 	}
@@ -478,12 +479,12 @@ func TestBillingService_GetAdvancedSecurityActiveCommittersOrg(t *testing.T) {
 
 	const methodName = "GetAdvancedSecurityActiveCommittersOrg"
 	testBadOptions(t, methodName, func() (err error) {
-		_, _, err = client.Billing.GetAdvancedSecurityActiveCommittersOrg(ctx, "\n")
+		_, _, err = client.Billing.GetAdvancedSecurityActiveCommittersOrg(ctx, "\n", nil)
 		return err
 	})
 
 	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
-		got, resp, err := client.Billing.GetAdvancedSecurityActiveCommittersOrg(ctx, "o")
+		got, resp, err := client.Billing.GetAdvancedSecurityActiveCommittersOrg(ctx, "o", nil)
 		if got != nil {
 			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
 		}
@@ -496,6 +497,6 @@ func TestBillingService_GetAdvancedSecurityActiveCommittersOrg_invalidOrg(t *tes
 	defer teardown()
 
 	ctx := context.Background()
-	_, _, err := client.Billing.GetAdvancedSecurityActiveCommittersOrg(ctx, "%")
+	_, _, err := client.Billing.GetAdvancedSecurityActiveCommittersOrg(ctx, "%", nil)
 	testURLParseError(t, err)
 }