Repository: google/go-github
Pull request #1811 (2021-02-22 16:36:54)

Add ActionsAllowed and ActionsPermissions to Organization
### What

Implements methods to interact with `/orgs/MY-ORG/actions` API:
- https://docs.github.com/en/rest/reference/actions

### Why

This is important to allow setting up github-actions policies at organization level using this library (e.g. enable/disable github-actions, set/edit filters for allowed actions).

### Additional notes 

I'm fairly new to using this library and I hope I covered all that's needed here, but please let me know if there is something missing or incomplete.

codecov[bot]: # [Codecov](https://codecov.io/gh/google/go-github/pull/1811?src=pr&el=h1) Report
> Merging [#1811](https://codecov.io/gh/google/go-github/pull/1811?src=pr&el=desc) (889803a) into [master](https://codecov.io/gh/google/go-github/commit/ed10c13634b9a8c313deccb6d0103a8a8de0159b?el=desc) (ed10c13) will **increase** coverage by `0.04%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/google/go-github/pull/1811/graphs/tree.svg?width=650&height=150&src=pr&token=EgYjGjZlI0)](https://codecov.io/gh/google/go-github/pull/1811?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1811      +/-   ##
==========================================
+ Coverage   97.55%   97.60%   +0.04%     
==========================================
  Files          99      102       +3     
  Lines        6426     6558     +132     
==========================================
+ Hits         6269     6401     +132     
  Misses         85       85              
  Partials       72       72              
```


| [Impacted Files](https://codecov.io/gh/google/go-github/pull/1811?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [github/event\_types.go](https://codecov.io/gh/google/go-github/pull/1811/diff?src=pr&el=tree#diff-Z2l0aHViL2V2ZW50X3R5cGVzLmdv) | `100.00% <ø> (ø)` | |
| [github/actions\_runner\_groups.go](https://codecov.io/gh/google/go-github/pull/1811/diff?src=pr&el=tree#diff-Z2l0aHViL2FjdGlvbnNfcnVubmVyX2dyb3Vwcy5nbw==) | `100.00% <100.00%> (ø)` | |
| [github/github.go](https://codecov.io/gh/google/go-github/pull/1811/diff?src=pr&el=tree#diff-Z2l0aHViL2dpdGh1Yi5nbw==) | `96.65% <100.00%> (+0.03%)` | :arrow_up: |
| [github/orgs\_actions\_allowed.go](https://codecov.io/gh/google/go-github/pull/1811/diff?src=pr&el=tree#diff-Z2l0aHViL29yZ3NfYWN0aW9uc19hbGxvd2VkLmdv) | `100.00% <100.00%> (ø)` | |
| [github/orgs\_actions\_permissions.go](https://codecov.io/gh/google/go-github/pull/1811/diff?src=pr&el=tree#diff-Z2l0aHViL29yZ3NfYWN0aW9uc19wZXJtaXNzaW9ucy5nbw==) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/google/go-github/pull/1811?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/google/go-github/pull/1811?src=pr&el=footer). Last update [ed10c13...eb30345](https://codecov.io/gh/google/go-github/pull/1811?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

marcelocarlos: Thank you for the review @gmlewis! Just added a couple of quick follow-up questions and addressed most of the comments.
marcelocarlos: Thanks @gmlewis, all comments addressed now 👍 

Patch:
diff --git a/github/actions_workflow_runs.go b/github/actions_workflow_runs.go
index 4ccb7a6d..64274a3d 100644
--- a/github/actions_workflow_runs.go
+++ b/github/actions_workflow_runs.go
@@ -15,6 +15,7 @@ import (
 // WorkflowRun represents a repository action workflow run.
 type WorkflowRun struct {
 	ID             *int64         `json:"id,omitempty"`
+	Name           *string        `json:"name,omitempty"`
 	NodeID         *string        `json:"node_id,omitempty"`
 	HeadBranch     *string        `json:"head_branch,omitempty"`
 	HeadSHA        *string        `json:"head_sha,omitempty"`
diff --git a/github/apps.go b/github/apps.go
index 12df54dd..cb0069de 100644
--- a/github/apps.go
+++ b/github/apps.go
@@ -90,21 +90,26 @@ type InstallationPermissions struct {
 
 // Installation represents a GitHub Apps installation.
 type Installation struct {
-	ID                  *int64                   `json:"id,omitempty"`
-	NodeID              *string                  `json:"node_id,omitempty"`
-	AppID               *int64                   `json:"app_id,omitempty"`
-	TargetID            *int64                   `json:"target_id,omitempty"`
-	Account             *User                    `json:"account,omitempty"`
-	AccessTokensURL     *string                  `json:"access_tokens_url,omitempty"`
-	RepositoriesURL     *string                  `json:"repositories_url,omitempty"`
-	HTMLURL             *string                  `json:"html_url,omitempty"`
-	TargetType          *string                  `json:"target_type,omitempty"`
-	SingleFileName      *string                  `json:"single_file_name,omitempty"`
-	RepositorySelection *string                  `json:"repository_selection,omitempty"`
-	Events              []string                 `json:"events,omitempty"`
-	Permissions         *InstallationPermissions `json:"permissions,omitempty"`
-	CreatedAt           *Timestamp               `json:"created_at,omitempty"`
-	UpdatedAt           *Timestamp               `json:"updated_at,omitempty"`
+	ID                     *int64                   `json:"id,omitempty"`
+	NodeID                 *string                  `json:"node_id,omitempty"`
+	AppID                  *int64                   `json:"app_id,omitempty"`
+	AppSlug                *string                  `json:"app_slug,omitempty"`
+	TargetID               *int64                   `json:"target_id,omitempty"`
+	Account                *User                    `json:"account,omitempty"`
+	AccessTokensURL        *string                  `json:"access_tokens_url,omitempty"`
+	RepositoriesURL        *string                  `json:"repositories_url,omitempty"`
+	HTMLURL                *string                  `json:"html_url,omitempty"`
+	TargetType             *string                  `json:"target_type,omitempty"`
+	SingleFileName         *string                  `json:"single_file_name,omitempty"`
+	RepositorySelection    *string                  `json:"repository_selection,omitempty"`
+	Events                 []string                 `json:"events,omitempty"`
+	SingleFilePaths        []string                 `json:"single_file_paths,omitempty"`
+	Permissions            *InstallationPermissions `json:"permissions,omitempty"`
+	CreatedAt              *Timestamp               `json:"created_at,omitempty"`
+	UpdatedAt              *Timestamp               `json:"updated_at,omitempty"`
+	HasMultipleSingleFiles *bool                    `json:"has_multiple_single_files,omitempty"`
+	SuspendedBy            *User                    `json:"suspended_by,omitempty"`
+	SuspendedAt            *Timestamp               `json:"suspended_at,omitempty"`
 }
 
 // Attachment represents a GitHub Apps attachment.
diff --git a/github/apps_installation.go b/github/apps_installation.go
index b4ac5eef..501d8006 100644
--- a/github/apps_installation.go
+++ b/github/apps_installation.go
@@ -8,6 +8,7 @@ package github
 import (
 	"context"
 	"fmt"
+	"strings"
 )
 
 // ListRepositories represents the response from the list repos endpoints.
@@ -30,6 +31,10 @@ func (s *AppsService) ListRepos(ctx context.Context, opts *ListOptions) (*ListRe
 		return nil, nil, err
 	}
 
+	// TODO: remove custom Accept headers when APIs fully launch.
+	acceptHeaders := []string{mediaTypeTopicsPreview, mediaTypeRepositoryVisibilityPreview}
+	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
+
 	var r *ListRepositories
 
 	resp, err := s.client.Do(ctx, req, &r)
@@ -56,6 +61,10 @@ func (s *AppsService) ListUserRepos(ctx context.Context, id int64, opts *ListOpt
 		return nil, nil, err
 	}
 
+	// TODO: remove custom Accept headers when APIs fully launch.
+	acceptHeaders := []string{mediaTypeTopicsPreview, mediaTypeRepositoryVisibilityPreview}
+	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
+
 	var r *ListRepositories
 	resp, err := s.client.Do(ctx, req, &r)
 	if err != nil {
diff --git a/github/github-accessors.go b/github/github-accessors.go
index e6ea3431..eba8d4cb 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -20,6 +20,46 @@ func (a *AbuseRateLimitError) GetRetryAfter() time.Duration {
 	return *a.RetryAfter
 }
 
+// GetGithubOwnedAllowed returns the GithubOwnedAllowed field if it's non-nil, zero value otherwise.
+func (a *ActionsAllowed) GetGithubOwnedAllowed() bool {
+	if a == nil || a.GithubOwnedAllowed == nil {
+		return false
+	}
+	return *a.GithubOwnedAllowed
+}
+
+// GetVerifiedAllowed returns the VerifiedAllowed field if it's non-nil, zero value otherwise.
+func (a *ActionsAllowed) GetVerifiedAllowed() bool {
+	if a == nil || a.VerifiedAllowed == nil {
+		return false
+	}
+	return *a.VerifiedAllowed
+}
+
+// GetAllowedActions returns the AllowedActions field if it's non-nil, zero value otherwise.
+func (a *ActionsPermissions) GetAllowedActions() string {
+	if a == nil || a.AllowedActions == nil {
+		return ""
+	}
+	return *a.AllowedActions
+}
+
+// GetEnabledRepositories returns the EnabledRepositories field if it's non-nil, zero value otherwise.
+func (a *ActionsPermissions) GetEnabledRepositories() string {
+	if a == nil || a.EnabledRepositories == nil {
+		return ""
+	}
+	return *a.EnabledRepositories
+}
+
+// GetSelectedActionsURL returns the SelectedActionsURL field if it's non-nil, zero value otherwise.
+func (a *ActionsPermissions) GetSelectedActionsURL() string {
+	if a == nil || a.SelectedActionsURL == nil {
+		return ""
+	}
+	return *a.SelectedActionsURL
+}
+
 // GetURL returns the URL field if it's non-nil, zero value otherwise.
 func (a *AdminEnforcement) GetURL() string {
 	if a == nil || a.URL == nil {
@@ -4268,6 +4308,14 @@ func (i *Installation) GetAppID() int64 {
 	return *i.AppID
 }
 
+// GetAppSlug returns the AppSlug field if it's non-nil, zero value otherwise.
+func (i *Installation) GetAppSlug() string {
+	if i == nil || i.AppSlug == nil {
+		return ""
+	}
+	return *i.AppSlug
+}
+
 // GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
 func (i *Installation) GetCreatedAt() Timestamp {
 	if i == nil || i.CreatedAt == nil {
@@ -4276,6 +4324,14 @@ func (i *Installation) GetCreatedAt() Timestamp {
 	return *i.CreatedAt
 }
 
+// GetHasMultipleSingleFiles returns the HasMultipleSingleFiles field if it's non-nil, zero value otherwise.
+func (i *Installation) GetHasMultipleSingleFiles() bool {
+	if i == nil || i.HasMultipleSingleFiles == nil {
+		return false
+	}
+	return *i.HasMultipleSingleFiles
+}
+
 // GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise.
 func (i *Installation) GetHTMLURL() string {
 	if i == nil || i.HTMLURL == nil {
@@ -4332,6 +4388,22 @@ func (i *Installation) GetSingleFileName() string {
 	return *i.SingleFileName
 }
 
+// GetSuspendedAt returns the SuspendedAt field if it's non-nil, zero value otherwise.
+func (i *Installation) GetSuspendedAt() Timestamp {
+	if i == nil || i.SuspendedAt == nil {
+		return Timestamp{}
+	}
+	return *i.SuspendedAt
+}
+
+// GetSuspendedBy returns the SuspendedBy field.
+func (i *Installation) GetSuspendedBy() *User {
+	if i == nil {
+		return nil
+	}
+	return i.SuspendedBy
+}
+
 // GetTargetID returns the TargetID field if it's non-nil, zero value otherwise.
 func (i *Installation) GetTargetID() int64 {
 	if i == nil || i.TargetID == nil {
@@ -15676,6 +15748,14 @@ func (w *WorkflowRun) GetLogsURL() string {
 	return *w.LogsURL
 }
 
+// GetName returns the Name field if it's non-nil, zero value otherwise.
+func (w *WorkflowRun) GetName() string {
+	if w == nil || w.Name == nil {
+		return ""
+	}
+	return *w.Name
+}
+
 // GetNodeID returns the NodeID field if it's non-nil, zero value otherwise.
 func (w *WorkflowRun) GetNodeID() string {
 	if w == nil || w.NodeID == nil {
diff --git a/github/orgs_actions_allowed.go b/github/orgs_actions_allowed.go
new file mode 100644
index 00000000..9032d033
--- /dev/null
+++ b/github/orgs_actions_allowed.go
@@ -0,0 +1,58 @@
+// Copyright 2021 The go-github AUTHORS. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package github
+
+import (
+	"context"
+	"fmt"
+)
+
+// ActionsAllowed represents selected actions that are allowed in an organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/actions#get-allowed-actions-for-an-organization
+type ActionsAllowed struct {
+	GithubOwnedAllowed *bool    `json:"github_owned_allowed,omitempty"`
+	VerifiedAllowed    *bool    `json:"verified_allowed,omitempty"`
+	PatternsAllowed    []string `json:"patterns_allowed,omitempty"`
+}
+
+func (a ActionsAllowed) String() string {
+	return Stringify(a)
+}
+
+// GetActionsAllowed gets the actions that are allowed in an organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/actions#get-allowed-actions-for-an-organization
+func (s *OrganizationsService) GetActionsAllowed(ctx context.Context, org string) (*ActionsAllowed, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/permissions/selected-actions", org)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	actionsAllowed := new(ActionsAllowed)
+	resp, err := s.client.Do(ctx, req, actionsAllowed)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return actionsAllowed, resp, nil
+}
+
+// EditActionsAllowed sets the actions that are allowed in an organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/actions#set-allowed-actions-for-an-organization
+func (s *OrganizationsService) EditActionsAllowed(ctx context.Context, org string, actionsAllowed ActionsAllowed) (*ActionsAllowed, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/permissions/selected-actions", org)
+	req, err := s.client.NewRequest("PUT", u, actionsAllowed)
+	if err != nil {
+		return nil, nil, err
+	}
+	p := new(ActionsAllowed)
+	resp, err := s.client.Do(ctx, req, p)
+	return p, resp, err
+}
diff --git a/github/orgs_actions_permissions.go b/github/orgs_actions_permissions.go
new file mode 100644
index 00000000..b8a10b25
--- /dev/null
+++ b/github/orgs_actions_permissions.go
@@ -0,0 +1,58 @@
+// Copyright 2021 The go-github AUTHORS. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package github
+
+import (
+	"context"
+	"fmt"
+)
+
+// ActionsPermissions represents a policy for repositories and allowed actions in an organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/actions#permissions
+type ActionsPermissions struct {
+	EnabledRepositories *string `json:"enabled_repositories,omitempty"`
+	AllowedActions      *string `json:"allowed_actions,omitempty"`
+	SelectedActionsURL  *string `json:"selected_actions_url,omitempty"`
+}
+
+func (a ActionsPermissions) String() string {
+	return Stringify(a)
+}
+
+// GetActionsPermissions gets the GitHub Actions permissions policy for repositories and allowed actions in an organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/actions#get-github-actions-permissions-for-an-organization
+func (s *OrganizationsService) GetActionsPermissions(ctx context.Context, org string) (*ActionsPermissions, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/permissions", org)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	permissions := new(ActionsPermissions)
+	resp, err := s.client.Do(ctx, req, permissions)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return permissions, resp, nil
+}
+
+// EditActionsPermissions sets the permissions policy for repositories and allowed actions in an organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/actions#set-github-actions-permissions-for-an-organization
+func (s *OrganizationsService) EditActionsPermissions(ctx context.Context, org string, actionsPermissions ActionsPermissions) (*ActionsPermissions, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/permissions", org)
+	req, err := s.client.NewRequest("PUT", u, actionsPermissions)
+	if err != nil {
+		return nil, nil, err
+	}
+	p := new(ActionsPermissions)
+	resp, err := s.client.Do(ctx, req, p)
+	return p, resp, err
+}
diff --git a/github/repos_contents.go b/github/repos_contents.go
index 924fee2f..d88ae74b 100644
--- a/github/repos_contents.go
+++ b/github/repos_contents.go
@@ -144,6 +144,36 @@ func (s *RepositoriesService) DownloadContents(ctx context.Context, owner, repo,
 	return nil, resp, fmt.Errorf("No file named %s found in %s", filename, dir)
 }
 
+// DownloadContentsWithMeta is identical to DownloadContents but additionally
+// returns the RepositoryContent of the requested file. This additional data
+// is useful for future operations involving the requested file. For merely
+// reading the content of a file, DownloadContents is perfectly adequate.
+//
+// It is possible for the download to result in a failed response when the
+// returned error is nil. Callers should check the returned Response status
+// code to verify the content is from a successful response.
+func (s *RepositoriesService) DownloadContentsWithMeta(ctx context.Context, owner, repo, filepath string, opts *RepositoryContentGetOptions) (io.ReadCloser, *RepositoryContent, *Response, error) {
+	dir := path.Dir(filepath)
+	filename := path.Base(filepath)
+	_, dirContents, resp, err := s.GetContents(ctx, owner, repo, dir, opts)
+	if err != nil {
+		return nil, nil, resp, err
+	}
+	for _, contents := range dirContents {
+		if *contents.Name == filename {
+			if contents.DownloadURL == nil || *contents.DownloadURL == "" {
+				return nil, contents, resp, fmt.Errorf("No download link found for %s", filepath)
+			}
+			dlResp, err := s.client.client.Get(*contents.DownloadURL)
+			if err != nil {
+				return nil, contents, &Response{Response: dlResp}, err
+			}
+			return dlResp.Body, contents, &Response{Response: dlResp}, nil
+		}
+	}
+	return nil, nil, resp, fmt.Errorf("No file named %s found in %s", filename, dir)
+}
+
 // GetContents can return either the metadata and content of a single file
 // (when path references a file) or the metadata of all the files and/or
 // subdirectories of a directory (when path references a directory). To make it
diff --git a/update-urls/main.go b/update-urls/main.go
index bdfd0882..d0d4955a 100644
--- a/update-urls/main.go
+++ b/update-urls/main.go
@@ -55,38 +55,39 @@ var (
 	// skipMethods holds methods which are skipped because they do not have GitHub v3
 	// API URLs or are otherwise problematic in parsing, discovering, and/or fixing.
 	skipMethods = map[string]bool{
-		"ActionsService.DownloadArtifact":            true,
-		"AdminService.CreateOrg":                     true,
-		"AdminService.CreateUser":                    true,
-		"AdminService.CreateUserImpersonation":       true,
-		"AdminService.DeleteUserImpersonation":       true,
-		"AdminService.GetAdminStats":                 true,
-		"AdminService.RenameOrg":                     true,
-		"AdminService.RenameOrgByName":               true,
-		"AdminService.UpdateTeamLDAPMapping":         true,
-		"AdminService.UpdateUserLDAPMapping":         true,
-		"AppsService.FindRepositoryInstallationByID": true,
-		"AuthorizationsService.CreateImpersonation":  true,
-		"AuthorizationsService.DeleteImpersonation":  true,
-		"IssueImportService.CheckStatus":             true,
-		"IssueImportService.CheckStatusSince":        true,
-		"IssueImportService.Create":                  true,
-		"MarketplaceService.marketplaceURI":          true,
-		"OrganizationsService.GetByID":               true,
-		"RepositoriesService.DeletePreReceiveHook":   true,
-		"RepositoriesService.DownloadContents":       true,
-		"RepositoriesService.GetArchiveLink":         true,
-		"RepositoriesService.GetByID":                true,
-		"RepositoriesService.GetPreReceiveHook":      true,
-		"RepositoriesService.ListPreReceiveHooks":    true,
-		"RepositoriesService.UpdatePreReceiveHook":   true,
-		"SearchService.search":                       true,
-		"TeamsService.ListTeamMembersByID":           true,
-		"UsersService.DemoteSiteAdmin":               true,
-		"UsersService.GetByID":                       true,
-		"UsersService.PromoteSiteAdmin":              true,
-		"UsersService.Suspend":                       true,
-		"UsersService.Unsuspend":                     true,
+		"ActionsService.DownloadArtifact":              true,
+		"AdminService.CreateOrg":                       true,
+		"AdminService.CreateUser":                      true,
+		"AdminService.CreateUserImpersonation":         true,
+		"AdminService.DeleteUserImpersonation":         true,
+		"AdminService.GetAdminStats":                   true,
+		"AdminService.RenameOrg":                       true,
+		"AdminService.RenameOrgByName":                 true,
+		"AdminService.UpdateTeamLDAPMapping":           true,
+		"AdminService.UpdateUserLDAPMapping":           true,
+		"AppsService.FindRepositoryInstallationByID":   true,
+		"AuthorizationsService.CreateImpersonation":    true,
+		"AuthorizationsService.DeleteImpersonation":    true,
+		"IssueImportService.CheckStatus":               true,
+		"IssueImportService.CheckStatusSince":          true,
+		"IssueImportService.Create":                    true,
+		"MarketplaceService.marketplaceURI":            true,
+		"OrganizationsService.GetByID":                 true,
+		"RepositoriesService.DeletePreReceiveHook":     true,
+		"RepositoriesService.DownloadContents":         true,
+		"RepositoriesService.DownloadContentsWithMeta": true,
+		"RepositoriesService.GetArchiveLink":           true,
+		"RepositoriesService.GetByID":                  true,
+		"RepositoriesService.GetPreReceiveHook":        true,
+		"RepositoriesService.ListPreReceiveHooks":      true,
+		"RepositoriesService.UpdatePreReceiveHook":     true,
+		"SearchService.search":                         true,
+		"TeamsService.ListTeamMembersByID":             true,
+		"UsersService.DemoteSiteAdmin":                 true,
+		"UsersService.GetByID":                         true,
+		"UsersService.PromoteSiteAdmin":                true,
+		"UsersService.Suspend":                         true,
+		"UsersService.Unsuspend":                       true,
 	}
 
 	helperOverrides = map[string]overrideFunc{


Test patch:
diff --git a/github/apps_installation_test.go b/github/apps_installation_test.go
index a73498c6..5e8a18de 100644
--- a/github/apps_installation_test.go
+++ b/github/apps_installation_test.go
@@ -10,6 +10,7 @@ import (
 	"fmt"
 	"net/http"
 	"reflect"
+	"strings"
 	"testing"
 )
 
@@ -17,8 +18,10 @@ func TestAppsService_ListRepos(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
+	wantAcceptHeaders := []string{mediaTypeTopicsPreview, mediaTypeRepositoryVisibilityPreview}
 	mux.HandleFunc("/installation/repositories", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", strings.Join(wantAcceptHeaders, ", "))
 		testFormValues(t, r, values{
 			"page":     "1",
 			"per_page": "2",
@@ -52,8 +55,10 @@ func TestAppsService_ListUserRepos(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
+	wantAcceptHeaders := []string{mediaTypeTopicsPreview, mediaTypeRepositoryVisibilityPreview}
 	mux.HandleFunc("/user/installations/1/repositories", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", strings.Join(wantAcceptHeaders, ", "))
 		testFormValues(t, r, values{
 			"page":     "1",
 			"per_page": "2",
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index 5090e52e..53d459b0 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -23,6 +23,56 @@ func TestAbuseRateLimitError_GetRetryAfter(tt *testing.T) {
 	a.GetRetryAfter()
 }
 
+func TestActionsAllowed_GetGithubOwnedAllowed(tt *testing.T) {
+	var zeroValue bool
+	a := &ActionsAllowed{GithubOwnedAllowed: &zeroValue}
+	a.GetGithubOwnedAllowed()
+	a = &ActionsAllowed{}
+	a.GetGithubOwnedAllowed()
+	a = nil
+	a.GetGithubOwnedAllowed()
+}
+
+func TestActionsAllowed_GetVerifiedAllowed(tt *testing.T) {
+	var zeroValue bool
+	a := &ActionsAllowed{VerifiedAllowed: &zeroValue}
+	a.GetVerifiedAllowed()
+	a = &ActionsAllowed{}
+	a.GetVerifiedAllowed()
+	a = nil
+	a.GetVerifiedAllowed()
+}
+
+func TestActionsPermissions_GetAllowedActions(tt *testing.T) {
+	var zeroValue string
+	a := &ActionsPermissions{AllowedActions: &zeroValue}
+	a.GetAllowedActions()
+	a = &ActionsPermissions{}
+	a.GetAllowedActions()
+	a = nil
+	a.GetAllowedActions()
+}
+
+func TestActionsPermissions_GetEnabledRepositories(tt *testing.T) {
+	var zeroValue string
+	a := &ActionsPermissions{EnabledRepositories: &zeroValue}
+	a.GetEnabledRepositories()
+	a = &ActionsPermissions{}
+	a.GetEnabledRepositories()
+	a = nil
+	a.GetEnabledRepositories()
+}
+
+func TestActionsPermissions_GetSelectedActionsURL(tt *testing.T) {
+	var zeroValue string
+	a := &ActionsPermissions{SelectedActionsURL: &zeroValue}
+	a.GetSelectedActionsURL()
+	a = &ActionsPermissions{}
+	a.GetSelectedActionsURL()
+	a = nil
+	a.GetSelectedActionsURL()
+}
+
 func TestAdminEnforcement_GetURL(tt *testing.T) {
 	var zeroValue string
 	a := &AdminEnforcement{URL: &zeroValue}
@@ -5003,6 +5053,16 @@ func TestInstallation_GetAppID(tt *testing.T) {
 	i.GetAppID()
 }
 
+func TestInstallation_GetAppSlug(tt *testing.T) {
+	var zeroValue string
+	i := &Installation{AppSlug: &zeroValue}
+	i.GetAppSlug()
+	i = &Installation{}
+	i.GetAppSlug()
+	i = nil
+	i.GetAppSlug()
+}
+
 func TestInstallation_GetCreatedAt(tt *testing.T) {
 	var zeroValue Timestamp
 	i := &Installation{CreatedAt: &zeroValue}
@@ -5013,6 +5073,16 @@ func TestInstallation_GetCreatedAt(tt *testing.T) {
 	i.GetCreatedAt()
 }
 
+func TestInstallation_GetHasMultipleSingleFiles(tt *testing.T) {
+	var zeroValue bool
+	i := &Installation{HasMultipleSingleFiles: &zeroValue}
+	i.GetHasMultipleSingleFiles()
+	i = &Installation{}
+	i.GetHasMultipleSingleFiles()
+	i = nil
+	i.GetHasMultipleSingleFiles()
+}
+
 func TestInstallation_GetHTMLURL(tt *testing.T) {
 	var zeroValue string
 	i := &Installation{HTMLURL: &zeroValue}
@@ -5080,6 +5150,23 @@ func TestInstallation_GetSingleFileName(tt *testing.T) {
 	i.GetSingleFileName()
 }
 
+func TestInstallation_GetSuspendedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	i := &Installation{SuspendedAt: &zeroValue}
+	i.GetSuspendedAt()
+	i = &Installation{}
+	i.GetSuspendedAt()
+	i = nil
+	i.GetSuspendedAt()
+}
+
+func TestInstallation_GetSuspendedBy(tt *testing.T) {
+	i := &Installation{}
+	i.GetSuspendedBy()
+	i = nil
+	i.GetSuspendedBy()
+}
+
 func TestInstallation_GetTargetID(tt *testing.T) {
 	var zeroValue int64
 	i := &Installation{TargetID: &zeroValue}
@@ -18384,6 +18471,16 @@ func TestWorkflowRun_GetLogsURL(tt *testing.T) {
 	w.GetLogsURL()
 }
 
+func TestWorkflowRun_GetName(tt *testing.T) {
+	var zeroValue string
+	w := &WorkflowRun{Name: &zeroValue}
+	w.GetName()
+	w = &WorkflowRun{}
+	w.GetName()
+	w = nil
+	w.GetName()
+}
+
 func TestWorkflowRun_GetNodeID(tt *testing.T) {
 	var zeroValue string
 	w := &WorkflowRun{NodeID: &zeroValue}
diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go
index d77747d8..9ff36ff3 100644
--- a/github/github-stringify_test.go
+++ b/github/github-stringify_test.go
@@ -13,6 +13,29 @@ import (
 
 func Float64(v float64) *float64 { return &v }
 
+func TestActionsAllowed_String(t *testing.T) {
+	v := ActionsAllowed{
+		GithubOwnedAllowed: Bool(false),
+		VerifiedAllowed:    Bool(false),
+	}
+	want := `github.ActionsAllowed{GithubOwnedAllowed:false, VerifiedAllowed:false}`
+	if got := v.String(); got != want {
+		t.Errorf("ActionsAllowed.String = %v, want %v", got, want)
+	}
+}
+
+func TestActionsPermissions_String(t *testing.T) {
+	v := ActionsPermissions{
+		EnabledRepositories: String(""),
+		AllowedActions:      String(""),
+		SelectedActionsURL:  String(""),
+	}
+	want := `github.ActionsPermissions{EnabledRepositories:"", AllowedActions:"", SelectedActionsURL:""}`
+	if got := v.String(); got != want {
+		t.Errorf("ActionsPermissions.String = %v, want %v", got, want)
+	}
+}
+
 func TestAdminStats_String(t *testing.T) {
 	v := AdminStats{
 		Issues:     &IssueStats{},
@@ -569,22 +592,26 @@ func TestImport_String(t *testing.T) {
 
 func TestInstallation_String(t *testing.T) {
 	v := Installation{
-		ID:                  Int64(0),
-		NodeID:              String(""),
-		AppID:               Int64(0),
-		TargetID:            Int64(0),
-		Account:             &User{},
-		AccessTokensURL:     String(""),
-		RepositoriesURL:     String(""),
-		HTMLURL:             String(""),
-		TargetType:          String(""),
-		SingleFileName:      String(""),
-		RepositorySelection: String(""),
-		Permissions:         &InstallationPermissions{},
-		CreatedAt:           &Timestamp{},
-		UpdatedAt:           &Timestamp{},
-	}
-	want := `github.Installation{ID:0, NodeID:"", AppID:0, TargetID:0, Account:github.User{}, AccessTokensURL:"", RepositoriesURL:"", HTMLURL:"", TargetType:"", SingleFileName:"", RepositorySelection:"", Permissions:github.InstallationPermissions{}, CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}`
+		ID:                     Int64(0),
+		NodeID:                 String(""),
+		AppID:                  Int64(0),
+		AppSlug:                String(""),
+		TargetID:               Int64(0),
+		Account:                &User{},
+		AccessTokensURL:        String(""),
+		RepositoriesURL:        String(""),
+		HTMLURL:                String(""),
+		TargetType:             String(""),
+		SingleFileName:         String(""),
+		RepositorySelection:    String(""),
+		Permissions:            &InstallationPermissions{},
+		CreatedAt:              &Timestamp{},
+		UpdatedAt:              &Timestamp{},
+		HasMultipleSingleFiles: Bool(false),
+		SuspendedBy:            &User{},
+		SuspendedAt:            &Timestamp{},
+	}
+	want := `github.Installation{ID:0, NodeID:"", AppID:0, AppSlug:"", TargetID:0, Account:github.User{}, AccessTokensURL:"", RepositoriesURL:"", HTMLURL:"", TargetType:"", SingleFileName:"", RepositorySelection:"", Permissions:github.InstallationPermissions{}, CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, HasMultipleSingleFiles:false, SuspendedBy:github.User{}, SuspendedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}`
 	if got := v.String(); got != want {
 		t.Errorf("Installation.String = %v, want %v", got, want)
 	}
diff --git a/github/orgs_actions_allowed_test.go b/github/orgs_actions_allowed_test.go
new file mode 100644
index 00000000..5b8276be
--- /dev/null
+++ b/github/orgs_actions_allowed_test.go
@@ -0,0 +1,84 @@
+// Copyright 2021 The go-github AUTHORS. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package github
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"reflect"
+	"testing"
+)
+
+func TestOrganizationsService_GetActionsAllowed(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/actions/permissions/selected-actions", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{"github_owned_allowed":true, "verified_allowed":false, "patterns_allowed":["a/b"]}`)
+	})
+
+	ctx := context.Background()
+	org, _, err := client.Organizations.GetActionsAllowed(ctx, "o")
+	if err != nil {
+		t.Errorf("Organizations.GetActionsAllowed returned error: %v", err)
+	}
+	want := &ActionsAllowed{GithubOwnedAllowed: Bool(true), VerifiedAllowed: Bool(false), PatternsAllowed: []string{"a/b"}}
+	if !reflect.DeepEqual(org, want) {
+		t.Errorf("Organizations.GetActionsAllowed returned %+v, want %+v", org, want)
+	}
+
+	const methodName = "GetActionsAllowed"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Organizations.GetActionsAllowed(ctx, "\n")
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.GetActionsAllowed(ctx, "o")
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_EditActionsAllowed(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+	input := &ActionsAllowed{GithubOwnedAllowed: Bool(true), VerifiedAllowed: Bool(false), PatternsAllowed: []string{"a/b"}}
+
+	mux.HandleFunc("/orgs/o/actions/permissions/selected-actions", func(w http.ResponseWriter, r *http.Request) {
+		v := new(ActionsAllowed)
+		json.NewDecoder(r.Body).Decode(v)
+
+		testMethod(t, r, "PUT")
+		if !reflect.DeepEqual(v, input) {
+			t.Errorf("Request body = %+v, want %+v", v, input)
+		}
+
+		fmt.Fprint(w, `{"github_owned_allowed":true, "verified_allowed":false, "patterns_allowed":["a/b"]}`)
+	})
+
+	ctx := context.Background()
+	org, _, err := client.Organizations.EditActionsAllowed(ctx, "o", *input)
+	if err != nil {
+		t.Errorf("Organizations.EditActionsAllowed returned error: %v", err)
+	}
+
+	want := &ActionsAllowed{GithubOwnedAllowed: Bool(true), VerifiedAllowed: Bool(false), PatternsAllowed: []string{"a/b"}}
+	if !reflect.DeepEqual(org, want) {
+		t.Errorf("Organizations.EditActionsAllowed returned %+v, want %+v", org, want)
+	}
+
+	const methodName = "EditActionsAllowed"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Organizations.EditActionsAllowed(ctx, "\n", *input)
+		return err
+	})
+}
diff --git a/github/orgs_actions_permissions_test.go b/github/orgs_actions_permissions_test.go
new file mode 100644
index 00000000..cb27c6b1
--- /dev/null
+++ b/github/orgs_actions_permissions_test.go
@@ -0,0 +1,85 @@
+// Copyright 2021 The go-github AUTHORS. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package github
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"reflect"
+	"testing"
+)
+
+func TestOrganizationsService_GetActionsPermissions(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/actions/permissions", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{"enabled_repositories": "all", "allowed_actions": "all"}`)
+	})
+
+	ctx := context.Background()
+	org, _, err := client.Organizations.GetActionsPermissions(ctx, "o")
+	if err != nil {
+		t.Errorf("Organizations.GetActionsPermissions returned error: %v", err)
+	}
+	want := &ActionsPermissions{EnabledRepositories: String("all"), AllowedActions: String("all")}
+	if !reflect.DeepEqual(org, want) {
+		t.Errorf("Organizations.GetActionsPermissions returned %+v, want %+v", org, want)
+	}
+
+	const methodName = "GetActionsPermissions"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Organizations.GetActionsPermissions(ctx, "\n")
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.GetActionsPermissions(ctx, "o")
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_EditActionsPermissions(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	input := &ActionsPermissions{EnabledRepositories: String("all"), AllowedActions: String("selected")}
+
+	mux.HandleFunc("/orgs/o/actions/permissions", func(w http.ResponseWriter, r *http.Request) {
+		v := new(ActionsPermissions)
+		json.NewDecoder(r.Body).Decode(v)
+
+		testMethod(t, r, "PUT")
+		if !reflect.DeepEqual(v, input) {
+			t.Errorf("Request body = %+v, want %+v", v, input)
+		}
+
+		fmt.Fprint(w, `{"enabled_repositories": "all", "allowed_actions": "selected"}`)
+	})
+
+	ctx := context.Background()
+	org, _, err := client.Organizations.EditActionsPermissions(ctx, "o", *input)
+	if err != nil {
+		t.Errorf("Organizations.EditActionsPermissions returned error: %v", err)
+	}
+
+	want := &ActionsPermissions{EnabledRepositories: String("all"), AllowedActions: String("selected")}
+	if !reflect.DeepEqual(org, want) {
+		t.Errorf("Organizations.EditActionsPermissions returned %+v, want %+v", org, want)
+	}
+
+	const methodName = "EditActionsPermissions"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Organizations.EditActionsPermissions(ctx, "\n", *input)
+		return err
+	})
+}
diff --git a/github/repos_contents_test.go b/github/repos_contents_test.go
index afc260b3..c8cfb45c 100644
--- a/github/repos_contents_test.go
+++ b/github/repos_contents_test.go
@@ -247,6 +247,155 @@ func TestRepositoriesService_DownloadContents_NoFile(t *testing.T) {
 	}
 }
 
+func TestRepositoriesService_DownloadContentsWithMeta_Success(t *testing.T) {
+	client, mux, serverURL, teardown := setup()
+	defer teardown()
+	mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `[{
+		  "type": "file",
+		  "name": "f",
+		  "download_url": "`+serverURL+baseURLPath+`/download/f"
+		}]`)
+	})
+	mux.HandleFunc("/download/f", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, "foo")
+	})
+
+	ctx := context.Background()
+	r, c, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
+	if err != nil {
+		t.Errorf("Repositories.DownloadContentsWithMeta returned error: %v", err)
+	}
+
+	if got, want := resp.Response.StatusCode, http.StatusOK; got != want {
+		t.Errorf("Repositories.DownloadContentsWithMeta returned status code %v, want %v", got, want)
+	}
+
+	bytes, err := ioutil.ReadAll(r)
+	if err != nil {
+		t.Errorf("Error reading response body: %v", err)
+	}
+	r.Close()
+
+	if got, want := string(bytes), "foo"; got != want {
+		t.Errorf("Repositories.DownloadContentsWithMeta returned %v, want %v", got, want)
+	}
+
+	if c != nil && c.Name != nil {
+		if got, want := *c.Name, "f"; got != want {
+			t.Errorf("Repositories.DownloadContentsWithMeta returned content name %v, want %v", got, want)
+		}
+	} else {
+		t.Errorf("Returned RepositoryContent is null")
+	}
+
+	const methodName = "DownloadContentsWithMeta"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, _, err = client.Repositories.DownloadContentsWithMeta(ctx, "\n", "\n", "\n", nil)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, cot, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		if cot != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, cot)
+		}
+		return resp, err
+	})
+}
+
+func TestRepositoriesService_DownloadContentsWithMeta_FailedResponse(t *testing.T) {
+	client, mux, serverURL, teardown := setup()
+	defer teardown()
+	mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `[{
+			"type": "file",
+			"name": "f",
+			"download_url": "`+serverURL+baseURLPath+`/download/f"
+		  }]`)
+	})
+	mux.HandleFunc("/download/f", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		w.WriteHeader(http.StatusInternalServerError)
+		fmt.Fprint(w, "foo error")
+	})
+
+	ctx := context.Background()
+	r, c, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
+	if err != nil {
+		t.Errorf("Repositories.DownloadContentsWithMeta returned error: %v", err)
+	}
+
+	if got, want := resp.Response.StatusCode, http.StatusInternalServerError; got != want {
+		t.Errorf("Repositories.DownloadContentsWithMeta returned status code %v, want %v", got, want)
+	}
+
+	bytes, err := ioutil.ReadAll(r)
+	if err != nil {
+		t.Errorf("Error reading response body: %v", err)
+	}
+	r.Close()
+
+	if got, want := string(bytes), "foo error"; got != want {
+		t.Errorf("Repositories.DownloadContentsWithMeta returned %v, want %v", got, want)
+	}
+
+	if c != nil && c.Name != nil {
+		if got, want := *c.Name, "f"; got != want {
+			t.Errorf("Repositories.DownloadContentsWithMeta returned content name %v, want %v", got, want)
+		}
+	} else {
+		t.Errorf("Returned RepositoryContent is null")
+	}
+}
+
+func TestRepositoriesService_DownloadContentsWithMeta_NoDownloadURL(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+	mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `[{
+		  "type": "file",
+		  "name": "f",
+		}]`)
+	})
+
+	ctx := context.Background()
+	_, _, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
+	if err == nil {
+		t.Errorf("Repositories.DownloadContentsWithMeta did not return expected error")
+	}
+
+	if resp == nil {
+		t.Errorf("Repositories.DownloadContentsWithMeta did not return expected response")
+	}
+}
+
+func TestRepositoriesService_DownloadContentsWithMeta_NoFile(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+	mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `[]`)
+	})
+
+	ctx := context.Background()
+	_, _, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
+	if err == nil {
+		t.Errorf("Repositories.DownloadContentsWithMeta did not return expected error")
+	}
+
+	if resp == nil {
+		t.Errorf("Repositories.DownloadContentsWithMeta did not return expected response")
+	}
+}
+
 func TestRepositoriesService_GetContents_File(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()