Repository: google/go-github
Pull request #1535 (2020-05-22 15:11:11)

Add organization secret API support
Related to #1525 and #1532.

Adds organization secrets https://developer.github.com/v3/actions/secrets/#get-an-organization-public-key and updates the repository secret actions API (probably would match how they would be named if org support had been in from the beginning, though it would be considered a breaking change).

Some possible issues:
- Checking with GitHub support if parameter selected repository ids should be an array of integers instead of array of strings. I think its been ~1 week?
-  Naming of the functions using the API endpoint header results in very long names.
- Handling of extra values in `Secret` and `EncryptedSecret` for organization secrets that repository secrets don't have: does omitempty make sense or should they be separate structs?
- Should there be an underlying function used for both organization and repository secrets that takes the base url as an argument? Quite a bit of duplication.
- Repository id (and id array) argument using int64 (or int64[]), or use the Repository type for the argument?
- Handling of selected repository IDs parameter as a type that wraps int64[], so that it gets marshaled to json correctly for `SetSelectedRepositoriesForOrganizationSecret` or is there a cleaner way to do this and use int64[] directly?

codecov[bot]: # [Codecov](https://codecov.io/gh/google/go-github/pull/1535?src=pr&el=h1) Report
> Merging [#1535](https://codecov.io/gh/google/go-github/pull/1535?src=pr&el=desc) into [master](https://codecov.io/gh/google/go-github/commit/45c502145579f884f5a107c227333fd25090b7e3&el=desc) will **increase** coverage by `5.00%`.
> The diff coverage is `64.55%`.

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

```diff
@@            Coverage Diff             @@
##           master    #1535      +/-   ##
==========================================
+ Coverage   68.03%   73.04%   +5.00%     
==========================================
  Files          94       94              
  Lines        8528     6963    -1565     
==========================================
- Hits         5802     5086     -716     
+ Misses       1844      977     -867     
- Partials      882      900      +18     
```


| [Impacted Files](https://codecov.io/gh/google/go-github/pull/1535?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [github/actions\_secrets.go](https://codecov.io/gh/google/go-github/pull/1535/diff?src=pr&el=tree#diff-Z2l0aHViL2FjdGlvbnNfc2VjcmV0cy5nbw==) | `61.01% <64.55%> (+9.23%)` | :arrow_up: |
| [github/github.go](https://codecov.io/gh/google/go-github/pull/1535/diff?src=pr&el=tree#diff-Z2l0aHViL2dpdGh1Yi5nbw==) | `89.62% <0.00%> (-0.03%)` | :arrow_down: |
| [github/admin.go](https://codecov.io/gh/google/go-github/pull/1535/diff?src=pr&el=tree#diff-Z2l0aHViL2FkbWluLmdv) | `100.00% <0.00%> (ø)` | |
| [github/strings.go](https://codecov.io/gh/google/go-github/pull/1535/diff?src=pr&el=tree#diff-Z2l0aHViL3N0cmluZ3MuZ28=) | `100.00% <0.00%> (ø)` | |
| [github/activity.go](https://codecov.io/gh/google/go-github/pull/1535/diff?src=pr&el=tree#diff-Z2l0aHViL2FjdGl2aXR5Lmdv) | `100.00% <0.00%> (ø)` | |
| [github/timestamp.go](https://codecov.io/gh/google/go-github/pull/1535/diff?src=pr&el=tree#diff-Z2l0aHViL3RpbWVzdGFtcC5nbw==) | `100.00% <0.00%> (ø)` | |
| [github/admin\_stats.go](https://codecov.io/gh/google/go-github/pull/1535/diff?src=pr&el=tree#diff-Z2l0aHViL2FkbWluX3N0YXRzLmdv) | `100.00% <0.00%> (ø)` | |
| [github/repos\_traffic.go](https://codecov.io/gh/google/go-github/pull/1535/diff?src=pr&el=tree#diff-Z2l0aHViL3JlcG9zX3RyYWZmaWMuZ28=) | `100.00% <0.00%> (ø)` | |
| [github/pulls\_reviewers.go](https://codecov.io/gh/google/go-github/pull/1535/diff?src=pr&el=tree#diff-Z2l0aHViL3B1bGxzX3Jldmlld2Vycy5nbw==) | `100.00% <0.00%> (ø)` | |
| [github/git\_commits.go](https://codecov.io/gh/google/go-github/pull/1535/diff?src=pr&el=tree#diff-Z2l0aHViL2dpdF9jb21taXRzLmdv) | `91.30% <0.00%> (+1.30%)` | :arrow_up: |
| ... and [82 more](https://codecov.io/gh/google/go-github/pull/1535/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/google/go-github/pull/1535?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/1535?src=pr&el=footer). Last update [45c5021...5599c9b](https://codecov.io/gh/google/go-github/pull/1535?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/github/actions_artifacts.go b/github/actions_artifacts.go
index 17358495..a236253e 100644
--- a/github/actions_artifacts.go
+++ b/github/actions_artifacts.go
@@ -36,6 +36,30 @@ type ArtifactList struct {
 	Artifacts  []*Artifact `json:"artifacts,omitempty"`
 }
 
+// ListArtifacts lists all artifacts that belong to a repository.
+//
+// GitHub API docs: https://developer.github.com/v3/actions/artifacts/#list-artifacts-for-a-repository
+func (s *ActionsService) ListArtifacts(ctx context.Context, owner, repo string, opts *ListOptions) (*ArtifactList, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/actions/artifacts", owner, repo)
+	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
+	}
+
+	artifactList := new(ArtifactList)
+	resp, err := s.client.Do(ctx, req, artifactList)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return artifactList, resp, nil
+}
+
 // ListWorkflowRunArtifacts lists all artifacts that belong to a workflow run.
 //
 // GitHub API docs: https://developer.github.com/v3/actions/artifacts/#list-workflow-run-artifacts
diff --git a/github/actions_secrets.go b/github/actions_secrets.go
index 1640b5a0..695accfb 100644
--- a/github/actions_secrets.go
+++ b/github/actions_secrets.go
@@ -16,10 +16,10 @@ type PublicKey struct {
 	Key   *string `json:"key"`
 }
 
-// GetPublicKey gets a public key that should be used for secret encryption.
+// GetRepoPublicKey gets a public key that should be used for secret encryption.
 //
-// GitHub API docs: https://developer.github.com/v3/actions/secrets/#get-your-public-key
-func (s *ActionsService) GetPublicKey(ctx context.Context, owner, repo string) (*PublicKey, *Response, error) {
+// GitHub API docs: https://developer.github.com/v3/actions/secrets/#get-a-repository-public-key
+func (s *ActionsService) GetRepoPublicKey(ctx context.Context, owner, repo string) (*PublicKey, *Response, error) {
 	u := fmt.Sprintf("repos/%v/%v/actions/secrets/public-key", owner, repo)
 	req, err := s.client.NewRequest("GET", u, nil)
 	if err != nil {
@@ -35,11 +35,32 @@ func (s *ActionsService) GetPublicKey(ctx context.Context, owner, repo string) (
 	return pubKey, resp, nil
 }
 
+// GetOrgPublicKey gets a public key that should be used for secret encryption.
+//
+// GitHub API docs: https://developer.github.com/v3/actions/secrets/#get-an-organization-public-key
+func (s *ActionsService) GetOrgPublicKey(ctx context.Context, org string) (*PublicKey, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/secrets/public-key", org)
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pubKey := new(PublicKey)
+	resp, err := s.client.Do(ctx, req, pubKey)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pubKey, resp, nil
+}
+
 // Secret represents a repository action secret.
 type Secret struct {
-	Name      string    `json:"name"`
-	CreatedAt Timestamp `json:"created_at"`
-	UpdatedAt Timestamp `json:"updated_at"`
+	Name                    string    `json:"name"`
+	CreatedAt               Timestamp `json:"created_at"`
+	UpdatedAt               Timestamp `json:"updated_at"`
+	Visibility              string    `json:"visibility,omitempty"`
+	SelectedRepositoriesURL string    `json:"selected_repositories_url,omitempty"`
 }
 
 // Secrets represents one item from the ListSecrets response.
@@ -48,12 +69,12 @@ type Secrets struct {
 	Secrets    []*Secret `json:"secrets"`
 }
 
-// ListSecrets lists all secrets available in a repository
+// ListRepoSecrets lists all secrets available in a repository
 // without revealing their encrypted values.
 //
-// GitHub API docs: https://developer.github.com/v3/actions/secrets/#list-secrets-for-a-repository
-func (s *ActionsService) ListSecrets(ctx context.Context, owner, repo string, opts *ListOptions) (*Secrets, *Response, error) {
-	u := fmt.Sprintf("repos/%s/%s/actions/secrets", owner, repo)
+// GitHub API docs: https://developer.github.com/v3/actions/secrets/#list-repository-secrets
+func (s *ActionsService) ListRepoSecrets(ctx context.Context, owner, repo string, opts *ListOptions) (*Secrets, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/actions/secrets", owner, repo)
 	u, err := addOptions(u, opts)
 	if err != nil {
 		return nil, nil, err
@@ -73,10 +94,10 @@ func (s *ActionsService) ListSecrets(ctx context.Context, owner, repo string, op
 	return secrets, resp, nil
 }
 
-// GetSecret gets a single secret without revealing its encrypted value.
+// GetRepoSecret gets a single repository secret without revealing its encrypted value.
 //
-// GitHub API docs: https://developer.github.com/v3/actions/secrets/#get-a-secret
-func (s *ActionsService) GetSecret(ctx context.Context, owner, repo, name string) (*Secret, *Response, error) {
+// GitHub API docs: https://developer.github.com/v3/actions/secrets/#get-a-repository-secret
+func (s *ActionsService) GetRepoSecret(ctx context.Context, owner, repo, name string) (*Secret, *Response, error) {
 	u := fmt.Sprintf("repos/%v/%v/actions/secrets/%v", owner, repo, name)
 	req, err := s.client.NewRequest("GET", u, nil)
 	if err != nil {
@@ -92,21 +113,26 @@ func (s *ActionsService) GetSecret(ctx context.Context, owner, repo, name string
 	return secret, resp, nil
 }
 
+// SelectedRepoIDs are the repository IDs that have access to the secret.
+type SelectedRepoIDs []int64
+
 // EncryptedSecret represents a secret that is encrypted using a public key.
 //
 // The value of EncryptedValue must be your secret, encrypted with
 // LibSodium (see documentation here: https://libsodium.gitbook.io/doc/bindings_for_other_languages)
 // using the public key retrieved using the GetPublicKey method.
 type EncryptedSecret struct {
-	Name           string `json:"-"`
-	KeyID          string `json:"key_id"`
-	EncryptedValue string `json:"encrypted_value"`
+	Name                  string          `json:"-"`
+	KeyID                 string          `json:"key_id"`
+	EncryptedValue        string          `json:"encrypted_value"`
+	Visibility            string          `json:"visibility,omitempty"`
+	SelectedRepositoryIDs SelectedRepoIDs `json:"selected_repository_ids,omitempty"`
 }
 
-// CreateOrUpdateSecret creates or updates a secret with an encrypted value.
+// CreateOrUpdateRepoSecret creates or updates a repository secret with an encrypted value.
 //
-// GitHub API docs: https://developer.github.com/v3/actions/secrets/#create-or-update-a-secret-for-a-repository
-func (s *ActionsService) CreateOrUpdateSecret(ctx context.Context, owner, repo string, eSecret *EncryptedSecret) (*Response, error) {
+// GitHub API docs: https://developer.github.com/v3/actions/secrets/#create-or-update-a-repository-secret
+func (s *ActionsService) CreateOrUpdateRepoSecret(ctx context.Context, owner, repo string, eSecret *EncryptedSecret) (*Response, error) {
 	u := fmt.Sprintf("repos/%v/%v/actions/secrets/%v", owner, repo, eSecret.Name)
 
 	req, err := s.client.NewRequest("PUT", u, eSecret)
@@ -117,10 +143,10 @@ func (s *ActionsService) CreateOrUpdateSecret(ctx context.Context, owner, repo s
 	return s.client.Do(ctx, req, nil)
 }
 
-// DeleteSecret deletes a secret in a repository using the secret name.
+// DeleteRepoSecret deletes a secret in a repository using the secret name.
 //
-// GitHub API docs: https://developer.github.com/v3/actions/secrets/#delete-a-secret-from-a-repository
-func (s *ActionsService) DeleteSecret(ctx context.Context, owner, repo, name string) (*Response, error) {
+// GitHub API docs: https://developer.github.com/v3/actions/secrets/#delete-a-repository-secret
+func (s *ActionsService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) (*Response, error) {
 	u := fmt.Sprintf("repos/%v/%v/actions/secrets/%v", owner, repo, name)
 
 	req, err := s.client.NewRequest("DELETE", u, nil)
@@ -130,3 +156,144 @@ func (s *ActionsService) DeleteSecret(ctx context.Context, owner, repo, name str
 
 	return s.client.Do(ctx, req, nil)
 }
+
+// ListOrgSecrets lists all secrets available in an organization
+// without revealing their encrypted values.
+//
+// GitHub API docs: https://developer.github.com/v3/actions/secrets/#list-organization-secrets
+func (s *ActionsService) ListOrgSecrets(ctx context.Context, org string, opts *ListOptions) (*Secrets, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/secrets", 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
+	}
+
+	secrets := new(Secrets)
+	resp, err := s.client.Do(ctx, req, &secrets)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return secrets, resp, nil
+}
+
+// GetOrgSecret gets a single organization secret without revealing its encrypted value.
+//
+// GitHub API docs: https://developer.github.com/v3/actions/secrets/#get-an-organization-secret
+func (s *ActionsService) GetOrgSecret(ctx context.Context, org, name string) (*Secret, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/secrets/%v", org, name)
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	secret := new(Secret)
+	resp, err := s.client.Do(ctx, req, secret)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return secret, resp, nil
+}
+
+// CreateOrUpdateOrgSecret creates or updates an organization secret with an encrypted value.
+//
+// GitHub API docs: https://developer.github.com/v3/actions/secrets/#create-or-update-an-organization-secret
+func (s *ActionsService) CreateOrUpdateOrgSecret(ctx context.Context, org string, eSecret *EncryptedSecret) (*Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/secrets/%v", org, eSecret.Name)
+
+	req, err := s.client.NewRequest("PUT", u, eSecret)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(ctx, req, nil)
+}
+
+// SelectedReposList represents the list of repositories selected for an organization secret.
+type SelectedReposList struct {
+	TotalCount   *int          `json:"total_count,omitempty"`
+	Repositories []*Repository `json:"repositories,omitempty"`
+}
+
+// ListSelectedReposForOrgSecret lists all repositories that have access to a secret.
+//
+// GitHub API docs: https://developer.github.com/v3/actions/secrets/#list-selected-repositories-for-an-organization-secret
+func (s *ActionsService) ListSelectedReposForOrgSecret(ctx context.Context, org, name string) (*SelectedReposList, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories", org, name)
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	result := new(SelectedReposList)
+	resp, err := s.client.Do(ctx, req, result)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return result, resp, nil
+}
+
+// SetSelectedReposForOrgSecret sets the repositories that have access to a secret.
+//
+// GitHub API docs: https://developer.github.com/v3/actions/secrets/#set-selected-repositories-for-an-organization-secret
+func (s *ActionsService) SetSelectedReposForOrgSecret(ctx context.Context, org, name string, ids SelectedRepoIDs) (*Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories", org, name)
+
+	type repoIDs struct {
+		SelectedIDs SelectedRepoIDs `json:"selected_repository_ids,omitempty"`
+	}
+
+	req, err := s.client.NewRequest("PUT", u, repoIDs{SelectedIDs: ids})
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(ctx, req, nil)
+}
+
+// AddSelectedRepoToOrgSecret adds a repository to an organization secret.
+//
+// GitHub API docs: https://developer.github.com/v3/actions/secrets/#add-selected-repository-to-an-organization-secret
+func (s *ActionsService) AddSelectedRepoToOrgSecret(ctx context.Context, org, name string, repo *Repository) (*Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories/%v", org, name, *repo.ID)
+	req, err := s.client.NewRequest("PUT", u, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(ctx, req, nil)
+}
+
+// RemoveSelectedRepoFromOrgSecret removes a repository from an organization secret.
+//
+// GitHub API docs: https://developer.github.com/v3/actions/secrets/#remove-selected-repository-from-an-organization-secret
+func (s *ActionsService) RemoveSelectedRepoFromOrgSecret(ctx context.Context, org, name string, repo *Repository) (*Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories/%v", org, name, *repo.ID)
+	req, err := s.client.NewRequest("DELETE", u, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(ctx, req, nil)
+}
+
+// DeleteOrgSecret deletes a secret in an organization using the secret name.
+//
+// GitHub API docs: https://developer.github.com/v3/actions/secrets/#delete-an-organization-secret
+func (s *ActionsService) DeleteOrgSecret(ctx context.Context, org, name string) (*Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/secrets/%v", org, name)
+
+	req, err := s.client.NewRequest("DELETE", u, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(ctx, req, nil)
+}
diff --git a/github/github-accessors.go b/github/github-accessors.go
index a72cfa02..4489e4dc 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -11756,6 +11756,14 @@ func (r *RunnerApplicationDownload) GetOS() string {
 	return *r.OS
 }
 
+// GetTotalCount returns the TotalCount field if it's non-nil, zero value otherwise.
+func (s *SelectedReposList) GetTotalCount() int {
+	if s == nil || s.TotalCount == nil {
+		return 0
+	}
+	return *s.TotalCount
+}
+
 // GetName returns the Name field if it's non-nil, zero value otherwise.
 func (s *ServiceHook) GetName() string {
 	if s == nil || s.Name == nil {


Test patch:
diff --git a/github/actions_artifacts_test.go b/github/actions_artifacts_test.go
index 2d478e45..bdad8310 100644
--- a/github/actions_artifacts_test.go
+++ b/github/actions_artifacts_test.go
@@ -14,6 +14,70 @@ import (
 	"testing"
 )
 
+func TestActionsService_ListArtifacts(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/actions/artifacts", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testFormValues(t, r, values{"page": "2"})
+		fmt.Fprint(w,
+			`{
+				"total_count":1, 
+				"artifacts":[{"id":1}]
+			}`,
+		)
+	})
+
+	opts := &ListOptions{Page: 2}
+	artifacts, _, err := client.Actions.ListArtifacts(context.Background(), "o", "r", opts)
+	if err != nil {
+		t.Errorf("Actions.ListArtifacts returned error: %v", err)
+	}
+
+	want := &ArtifactList{TotalCount: Int64(1), Artifacts: []*Artifact{{ID: Int64(1)}}}
+	if !reflect.DeepEqual(artifacts, want) {
+		t.Errorf("Actions.ListArtifacts returned %+v, want %+v", artifacts, want)
+	}
+}
+
+func TestActionsService_ListArtifacts_invalidOwner(t *testing.T) {
+	client, _, _, teardown := setup()
+	defer teardown()
+
+	_, _, err := client.Actions.ListArtifacts(context.Background(), "%", "r", nil)
+	testURLParseError(t, err)
+}
+
+func TestActionsService_ListArtifacts_invalidRepo(t *testing.T) {
+	client, _, _, teardown := setup()
+	defer teardown()
+
+	_, _, err := client.Actions.ListArtifacts(context.Background(), "o", "%", nil)
+	testURLParseError(t, err)
+}
+
+func TestActionsService_ListArtifacts_notFound(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/actions/artifacts", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		w.WriteHeader(http.StatusNotFound)
+	})
+
+	artifacts, resp, err := client.Actions.ListArtifacts(context.Background(), "o", "r", nil)
+	if err == nil {
+		t.Errorf("Expected HTTP 404 response")
+	}
+	if got, want := resp.Response.StatusCode, http.StatusNotFound; got != want {
+		t.Errorf("Actions.ListArtifacts return status %d, want %d", got, want)
+	}
+	if artifacts != nil {
+		t.Errorf("Actions.ListArtifacts return %+v, want nil", artifacts)
+	}
+}
+
 func TestActionsService_ListWorkflowRunArtifacts(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
diff --git a/github/actions_secrets_test.go b/github/actions_secrets_test.go
index 0a17e583..61f051b5 100644
--- a/github/actions_secrets_test.go
+++ b/github/actions_secrets_test.go
@@ -14,7 +14,7 @@ import (
 	"time"
 )
 
-func TestActionsService_GetPublicKey(t *testing.T) {
+func TestActionsService_GetRepoPublicKey(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
@@ -23,18 +23,18 @@ func TestActionsService_GetPublicKey(t *testing.T) {
 		fmt.Fprint(w, `{"key_id":"1234","key":"2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"}`)
 	})
 
-	key, _, err := client.Actions.GetPublicKey(context.Background(), "o", "r")
+	key, _, err := client.Actions.GetRepoPublicKey(context.Background(), "o", "r")
 	if err != nil {
-		t.Errorf("Actions.GetPublicKey returned error: %v", err)
+		t.Errorf("Actions.GetRepoPublicKey returned error: %v", err)
 	}
 
 	want := &PublicKey{KeyID: String("1234"), Key: String("2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234")}
 	if !reflect.DeepEqual(key, want) {
-		t.Errorf("Actions.GetPublicKey returned %+v, want %+v", key, want)
+		t.Errorf("Actions.GetRepoPublicKey returned %+v, want %+v", key, want)
 	}
 }
 
-func TestActionsService_ListSecrets(t *testing.T) {
+func TestActionsService_ListRepoSecrets(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
@@ -45,9 +45,9 @@ func TestActionsService_ListSecrets(t *testing.T) {
 	})
 
 	opts := &ListOptions{Page: 2, PerPage: 2}
-	secrets, _, err := client.Actions.ListSecrets(context.Background(), "o", "r", opts)
+	secrets, _, err := client.Actions.ListRepoSecrets(context.Background(), "o", "r", opts)
 	if err != nil {
-		t.Errorf("Actions.ListSecrets returned error: %v", err)
+		t.Errorf("Actions.ListRepoSecrets returned error: %v", err)
 	}
 
 	want := &Secrets{
@@ -58,11 +58,11 @@ func TestActionsService_ListSecrets(t *testing.T) {
 		},
 	}
 	if !reflect.DeepEqual(secrets, want) {
-		t.Errorf("Actions.ListSecrets returned %+v, want %+v", secrets, want)
+		t.Errorf("Actions.ListRepoSecrets returned %+v, want %+v", secrets, want)
 	}
 }
 
-func TestActionsService_GetSecret(t *testing.T) {
+func TestActionsService_GetRepoSecret(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
@@ -71,9 +71,9 @@ func TestActionsService_GetSecret(t *testing.T) {
 		fmt.Fprint(w, `{"name":"NAME","created_at":"2019-01-02T15:04:05Z","updated_at":"2020-01-02T15:04:05Z"}`)
 	})
 
-	secret, _, err := client.Actions.GetSecret(context.Background(), "o", "r", "NAME")
+	secret, _, err := client.Actions.GetRepoSecret(context.Background(), "o", "r", "NAME")
 	if err != nil {
-		t.Errorf("Actions.GetSecret returned error: %v", err)
+		t.Errorf("Actions.GetRepoSecret returned error: %v", err)
 	}
 
 	want := &Secret{
@@ -82,11 +82,11 @@ func TestActionsService_GetSecret(t *testing.T) {
 		UpdatedAt: Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)},
 	}
 	if !reflect.DeepEqual(secret, want) {
-		t.Errorf("Actions.GetSecret returned %+v, want %+v", secret, want)
+		t.Errorf("Actions.GetRepoSecret returned %+v, want %+v", secret, want)
 	}
 }
 
-func TestActionsService_CreateOrUpdateSecret(t *testing.T) {
+func TestActionsService_CreateOrUpdateRepoSecret(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
@@ -102,13 +102,13 @@ func TestActionsService_CreateOrUpdateSecret(t *testing.T) {
 		EncryptedValue: "QIv=",
 		KeyID:          "1234",
 	}
-	_, err := client.Actions.CreateOrUpdateSecret(context.Background(), "o", "r", input)
+	_, err := client.Actions.CreateOrUpdateRepoSecret(context.Background(), "o", "r", input)
 	if err != nil {
-		t.Errorf("Actions.CreateOrUpdateSecret returned error: %v", err)
+		t.Errorf("Actions.CreateOrUpdateRepoSecret returned error: %v", err)
 	}
 }
 
-func TestActionsService_DeleteSecret(t *testing.T) {
+func TestActionsService_DeleteRepoSecret(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
@@ -116,8 +116,192 @@ func TestActionsService_DeleteSecret(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	_, err := client.Actions.DeleteSecret(context.Background(), "o", "r", "NAME")
+	_, err := client.Actions.DeleteRepoSecret(context.Background(), "o", "r", "NAME")
 	if err != nil {
-		t.Errorf("Actions.DeleteSecret returned error: %v", err)
+		t.Errorf("Actions.DeleteRepoSecret returned error: %v", err)
+	}
+}
+
+func TestActionsService_GetOrgPublicKey(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/actions/secrets/public-key", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{"key_id":"012345678","key":"2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"}`)
+	})
+
+	key, _, err := client.Actions.GetOrgPublicKey(context.Background(), "o")
+	if err != nil {
+		t.Errorf("Actions.GetOrgPublicKey returned error: %v", err)
+	}
+
+	want := &PublicKey{KeyID: String("012345678"), Key: String("2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234")}
+	if !reflect.DeepEqual(key, want) {
+		t.Errorf("Actions.GetOrgPublicKey returned %+v, want %+v", key, want)
+	}
+}
+
+func TestActionsService_ListOrgSecrets(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/actions/secrets", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testFormValues(t, r, values{"per_page": "2", "page": "2"})
+		fmt.Fprint(w, `{"total_count":3,"secrets":[{"name":"GIST_ID","created_at":"2019-08-10T14:59:22Z","updated_at":"2020-01-10T14:59:22Z","visibility":"private"},{"name":"DEPLOY_TOKEN","created_at":"2019-08-10T14:59:22Z","updated_at":"2020-01-10T14:59:22Z","visibility":"all"},{"name":"GH_TOKEN","created_at":"2019-08-10T14:59:22Z","updated_at":"2020-01-10T14:59:22Z","visibility":"selected","selected_repositories_url":"https://api.github.com/orgs/octo-org/actions/secrets/SUPER_SECRET/repositories"}]}`)
+	})
+
+	opts := &ListOptions{Page: 2, PerPage: 2}
+	secrets, _, err := client.Actions.ListOrgSecrets(context.Background(), "o", opts)
+	if err != nil {
+		t.Errorf("Actions.ListOrgSecrets returned error: %v", err)
+	}
+
+	want := &Secrets{
+		TotalCount: 3,
+		Secrets: []*Secret{
+			{Name: "GIST_ID", CreatedAt: Timestamp{time.Date(2019, time.August, 10, 14, 59, 22, 0, time.UTC)}, UpdatedAt: Timestamp{time.Date(2020, time.January, 10, 14, 59, 22, 0, time.UTC)}, Visibility: "private"},
+			{Name: "DEPLOY_TOKEN", CreatedAt: Timestamp{time.Date(2019, time.August, 10, 14, 59, 22, 0, time.UTC)}, UpdatedAt: Timestamp{time.Date(2020, time.January, 10, 14, 59, 22, 0, time.UTC)}, Visibility: "all"},
+			{Name: "GH_TOKEN", CreatedAt: Timestamp{time.Date(2019, time.August, 10, 14, 59, 22, 0, time.UTC)}, UpdatedAt: Timestamp{time.Date(2020, time.January, 10, 14, 59, 22, 0, time.UTC)}, Visibility: "selected", SelectedRepositoriesURL: "https://api.github.com/orgs/octo-org/actions/secrets/SUPER_SECRET/repositories"},
+		},
+	}
+	if !reflect.DeepEqual(secrets, want) {
+		t.Errorf("Actions.ListOrgSecrets returned %+v, want %+v", secrets, want)
+	}
+}
+
+func TestActionsService_GetOrgSecret(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/actions/secrets/NAME", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{"name":"NAME","created_at":"2019-01-02T15:04:05Z","updated_at":"2020-01-02T15:04:05Z","visibility":"selected","selected_repositories_url":"https://api.github.com/orgs/octo-org/actions/secrets/SUPER_SECRET/repositories"}`)
+	})
+
+	secret, _, err := client.Actions.GetOrgSecret(context.Background(), "o", "NAME")
+	if err != nil {
+		t.Errorf("Actions.GetOrgSecret returned error: %v", err)
+	}
+
+	want := &Secret{
+		Name:                    "NAME",
+		CreatedAt:               Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)},
+		UpdatedAt:               Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)},
+		Visibility:              "selected",
+		SelectedRepositoriesURL: "https://api.github.com/orgs/octo-org/actions/secrets/SUPER_SECRET/repositories",
+	}
+	if !reflect.DeepEqual(secret, want) {
+		t.Errorf("Actions.GetOrgSecret returned %+v, want %+v", secret, want)
+	}
+}
+
+func TestActionsService_CreateOrUpdateOrgSecret(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/actions/secrets/NAME", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PUT")
+		testHeader(t, r, "Content-Type", "application/json")
+		testBody(t, r, `{"key_id":"1234","encrypted_value":"QIv=","visibility":"selected","selected_repository_ids":[1296269,1269280]}`+"\n")
+		w.WriteHeader(http.StatusCreated)
+	})
+
+	input := &EncryptedSecret{
+		Name:                  "NAME",
+		EncryptedValue:        "QIv=",
+		KeyID:                 "1234",
+		Visibility:            "selected",
+		SelectedRepositoryIDs: SelectedRepoIDs{1296269, 1269280},
+	}
+	_, err := client.Actions.CreateOrUpdateOrgSecret(context.Background(), "o", input)
+	if err != nil {
+		t.Errorf("Actions.CreateOrUpdateOrgSecret returned error: %v", err)
+	}
+}
+
+func TestActionsService_ListSelectedReposForOrgSecret(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/actions/secrets/NAME/repositories", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprintf(w, `{"total_count":1,"repositories":[{"id":1}]}`)
+	})
+
+	repos, _, err := client.Actions.ListSelectedReposForOrgSecret(context.Background(), "o", "NAME")
+	if err != nil {
+		t.Errorf("Actions.ListSelectedReposForOrgSecret returned error: %v", err)
+	}
+
+	want := &SelectedReposList{
+		TotalCount: Int(1),
+		Repositories: []*Repository{
+			{ID: Int64(1)},
+		},
+	}
+	if !reflect.DeepEqual(repos, want) {
+		t.Errorf("Actions.ListSelectedReposForOrgSecret returned %+v, want %+v", repos, want)
+	}
+}
+
+func TestActionsService_SetSelectedReposForOrgSecret(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/actions/secrets/NAME/repositories", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PUT")
+		testHeader(t, r, "Content-Type", "application/json")
+		testBody(t, r, `{"selected_repository_ids":[64780797]}`+"\n")
+	})
+
+	_, err := client.Actions.SetSelectedReposForOrgSecret(context.Background(), "o", "NAME", SelectedRepoIDs{64780797})
+	if err != nil {
+		t.Errorf("Actions.SetSelectedReposForOrgSecret returned error: %v", err)
+	}
+}
+
+func TestActionsService_AddSelectedRepoToOrgSecret(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/actions/secrets/NAME/repositories/1234", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PUT")
+	})
+
+	repo := &Repository{ID: Int64(1234)}
+	_, err := client.Actions.AddSelectedRepoToOrgSecret(context.Background(), "o", "NAME", repo)
+	if err != nil {
+		t.Errorf("Actions.AddSelectedRepoToOrgSecret returned error: %v", err)
+	}
+}
+
+func TestActionsService_RemoveSelectedRepoFromOrgSecret(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/actions/secrets/NAME/repositories/1234", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+	})
+
+	repo := &Repository{ID: Int64(1234)}
+	_, err := client.Actions.RemoveSelectedRepoFromOrgSecret(context.Background(), "o", "NAME", repo)
+	if err != nil {
+		t.Errorf("Actions.RemoveSelectedRepoFromOrgSecret returned error: %v", err)
+	}
+}
+
+func TestActionsService_DeleteOrgSecret(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/actions/secrets/NAME", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+	})
+
+	_, err := client.Actions.DeleteOrgSecret(context.Background(), "o", "NAME")
+	if err != nil {
+		t.Errorf("Actions.DeleteOrgSecret returned error: %v", err)
 	}
 }