diff --git a/github/github-accessors.go b/github/github-accessors.go index 884f1acc6de..badca1b6790 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -6956,6 +6956,30 @@ func (o *OrganizationInstallations) GetTotalCount() int { return *o.TotalCount } +// GetKey returns the Key field if it's non-nil, zero value otherwise. +func (o *OrganizationPublicKey) GetKey() string { + if o == nil || o.Key == nil { + return "" + } + return *o.Key +} + +// GetKeyID returns the KeyID field if it's non-nil, zero value otherwise. +func (o *OrganizationPublicKey) GetKeyID() string { + if o == nil || o.KeyID == nil { + return "" + } + return *o.KeyID +} + +// GetTotalCount returns the TotalCount field if it's non-nil, zero value otherwise. +func (o *OrganizationSecretSelectedRepositories) GetTotalCount() int64 { + if o == nil || o.TotalCount == nil { + return 0 + } + return *o.TotalCount +} + // GetAction returns the Action field if it's non-nil, zero value otherwise. func (o *OrgBlockEvent) GetAction() string { if o == nil || o.Action == nil { diff --git a/github/orgs_actions_secrets.go b/github/orgs_actions_secrets.go new file mode 100644 index 00000000000..7f30e864eac --- /dev/null +++ b/github/orgs_actions_secrets.go @@ -0,0 +1,196 @@ +package github + +import ( + "context" + "fmt" +) + +// PublicKey represents the public key that should be used to encrypt secrets. +type OrganizationPublicKey struct { + KeyID *string `json:"key_id"` + Key *string `json:"key"` +} + +// GetPublicKey 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 *OrganizationsService) GetPublicKey(ctx context.Context, owner string) (*OrganizationPublicKey, *Response, error) { + u := fmt.Sprintf("orgs/%v/actions/secrets/public-key", owner) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + pubKey := new(OrganizationPublicKey) + resp, err := s.client.Do(ctx, req, pubKey) + if err != nil { + return nil, resp, err + } + + return pubKey, resp, nil +} + +type OrganizationSecret struct { + Name string `json:"name"` + CreatedAt Timestamp `json:"created_at"` + UpdatedAt Timestamp `json:"updated_at"` + Visibility string `json:"visibility"` + SelectedRepositoriesUrl string `json:"selected_repositories_url"` +} + +type OrganizationSecrets struct { + TotalCount int `json:"total_count"` + Secrets []*OrganizationSecret `json:"secrets"` +} + +// ListSecrets 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 *OrganizationsService) ListSecrets(ctx context.Context, owner string, opts *ListOptions) (*OrganizationSecrets, *Response, error) { + u := fmt.Sprintf("orgs/%s/actions/secrets", owner) + 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(OrganizationSecrets) + resp, err := s.client.Do(ctx, req, &secrets) + if err != nil { + return nil, resp, err + } + + return secrets, resp, nil +} + +// GetSecret gets a single secret without revealing its encrypted value. +// +// GitHub API docs: https://developer.github.com/v3/actions/secrets/#get-an-organization-secret +func (s *OrganizationsService) GetSecret(ctx context.Context, owner, name string) (*OrganizationSecret, *Response, error) { + u := fmt.Sprintf("orgs/%v/actions/secrets/%v", owner, name) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + secret := new(OrganizationSecret) + resp, err := s.client.Do(ctx, req, secret) + if err != nil { + return nil, resp, err + } + + return secret, resp, nil +} + +// OrganizationEncryptedSecret represents an Organization 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 OrganizationEncryptedSecret struct { + Name string `json:"-"` + KeyID string `json:"key_id"` + EncryptedValue string `json:"encrypted_value"` + Visibility string `json:"visibility"` + SelectedRepositoryIDs []string `json:"selected_repository_ids,omitempty"` +} + +// CreateOrUpdateSecret creates or updates a secret with an encrypted value. +// +// GitHub API docs: https://developer.github.com/v3/actions/secrets/#create-or-update-an-organization-secret +func (s *OrganizationsService) CreateOrUpdateSecret(ctx context.Context, owner string, eSecret *OrganizationEncryptedSecret) (*Response, error) { + u := fmt.Sprintf("orgs/%v/actions/secrets/%v", owner, eSecret.Name) + + req, err := s.client.NewRequest("PUT", u, eSecret) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// DeleteSecret deletes a secret in a repository using the secret name. +// +// GitHub API docs: https://developer.github.com/v3/actions/secrets/#delete-an-organization-secret +func (s *OrganizationsService) DeleteSecret(ctx context.Context, owner, name string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/actions/secrets/%v", owner, name) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// OrganizationSecretSelectedRepositories represents all repositories that have been selected to have access to an OrganizationSecret +type OrganizationSecretSelectedRepositories struct { + TotalCount *int64 `json:"total_count,omitempty"` + Repositories []*Repository `json:"repositories,omitempty"` +} + +// Lists all repositories that have been selected when the visibility for repository access to a secret is set to selected. +// +// GitHub API docs: https://developer.github.com/v3/actions/secrets/#list-selected-repositories-for-an-organization-secret +func (s *OrganizationsService) ListSecretSelectedRepositories(ctx context.Context, owner, name string) (*OrganizationSecretSelectedRepositories, *Response, error) { + u := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories", owner, name) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + secretSelectedRepositories := new(OrganizationSecretSelectedRepositories) + resp, err := s.client.Do(ctx, req, secretSelectedRepositories) + if err != nil { + return nil, resp, err + } + + return secretSelectedRepositories, resp, nil +} + +// Replaces all repositories for an organization secret when the visibility for repository access is set to selected. +// +// GitHub API docs: https://developer.github.com/v3/actions/secrets/#set-selected-repositories-for-an-organization-secret +func (s *OrganizationsService) SetSecretSelectedRepositories(ctx context.Context, owner, name string, repositoryIDs []int64) (*Response, error) { + u := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories", owner, name) + + req, err := s.client.NewRequest("PUT", u, repositoryIDs) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// Adds a repository to an organization secret when the visibility for repository access is set to selected. +// +// GitHub API docs: https://developer.github.com/v3/actions/secrets/#add-selected-repository-to-an-organization-secret +func (s *OrganizationsService) AddSelectedRepositoryToSecret(ctx context.Context, owner, name string, repositoryID int64) (*Response, error) { + u := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories/%v", owner, name, repositoryID) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// Removes a repository from an organization secret when the visibility for repository access is set to selected +// +// GitHub API docs: https://developer.github.com/v3/actions/secrets/#remove-selected-repository-from-an-organization-secret +func (s *OrganizationsService) RemoveSelectedRepositoryFromSecret(ctx context.Context, owner, name string, repositoryID int64) (*Response, error) { + u := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories/%v", owner, name, repositoryID) + + 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/orgs_actions_secrets_test.go b/github/orgs_actions_secrets_test.go new file mode 100644 index 00000000000..43d711c5bbc --- /dev/null +++ b/github/orgs_actions_secrets_test.go @@ -0,0 +1,192 @@ +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestOrganizationsService_GetPublicKey(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":"012345678912345678","key":"2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"}`) + }) + + key, _, err := client.Organizations.GetPublicKey(context.Background(), "o") + if err != nil { + t.Errorf("Organizations.GetPublicKey returned error: %v", err) + } + + want := &OrganizationPublicKey{KeyID: String("012345678912345678"), Key: String("2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234")} + if !reflect.DeepEqual(key, want) { + t.Errorf("Organizations.GetPublicKey returned %+v, want %+v", key, want) + } +} + +func TestOrganizationsService_ListSecrets(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.Organizations.ListSecrets(context.Background(), "o", opts) + if err != nil { + t.Errorf("Organizations.ListSecrets returned error: %v", err) + } + + want := &OrganizationSecrets{ + TotalCount: 3, + Secrets: []*OrganizationSecret{ + {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("Organizations.ListSecrets returned %+v, want %+v", secrets, want) + } +} + +func TestOrganizationsService_GetSecret(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/actions/secrets/GH_TOKEN", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"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"}`) + }) + + secret, _, err := client.Organizations.GetSecret(context.Background(), "o", "GH_TOKEN") + if err != nil { + t.Errorf("Organizations.GetSecret returned error: %v", err) + } + + want := &OrganizationSecret{ + 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(secret, want) { + t.Errorf("Organizations.GetSecret returned %+v, want %+v", secret, want) + } +} + +func TestOrgnizationsService_CreateOrUpdateSecret(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":["A","B","C"]}`+"\n") + w.WriteHeader(http.StatusCreated) + }) + + input := &OrganizationEncryptedSecret{ + Name: "NAME", + EncryptedValue: "QIv=", + KeyID: "1234", + Visibility: "selected", + SelectedRepositoryIDs: []string{"A", "B", "C"}, + } + _, err := client.Organizations.CreateOrUpdateSecret(context.Background(), "o", input) + if err != nil { + t.Errorf("Organizations.CreateOrUpdateSecret returned error: %v", err) + } +} + +func TestOrgnizationsService_DeleteSecret(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") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.DeleteSecret(context.Background(), "o", "NAME") + if err != nil { + t.Errorf("Organizations.DeleteSecret returned error: %v", err) + } +} + +func TestOrganizationsService_ListSecretSelectedRepositories(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/actions/secrets/SECRET_NAME/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"total_count":2,"repositories":[{"id":1},{"id":2}]}`) + }) + + secretSelectedRepositories, _, err := client.Organizations.ListSecretSelectedRepositories(context.Background(), "o", "SECRET_NAME") + if err != nil { + t.Errorf("Organizations.ListSecretSelectedRepositories returned error: %v", err) + } + + want := &OrganizationSecretSelectedRepositories{ + TotalCount: Int64(2), + Repositories: []*Repository{{ID: Int64(1)}, {ID: Int64(2)}}, + } + + if !reflect.DeepEqual(secretSelectedRepositories, want) { + t.Errorf("Organizations.ListSecretSelectedRepositories returned %+v, want %+v", secretSelectedRepositories, want) + } +} + +func TestOrganizationsService_SetSecretSelectedRepositories(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/actions/secrets/SECRET_NAME/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testHeader(t, r, "Content-Type", "application/json") + }) + _, err := client.Organizations.SetSecretSelectedRepositories(context.Background(), "o", "SECRET_NAME", []int64{64780797}) + if err != nil { + t.Errorf("Organizations.SetSecretSelectedRepositories returned error: %v", err) + } +} + +func TestOrganizationsService_AddSelectedRepositoryToSecret(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/actions/secrets/SECRET_NAME/repositories/64780797", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + }) + + _, err := client.Organizations.AddSelectedRepositoryToSecret(context.Background(), "o", "SECRET_NAME", int64(64780797)) + if err != nil { + t.Errorf("Organizations.AddSelectedRepositoryToSecret returned error: %v", err) + } +} + +func TestOrganizationsService_RemoveSelectedRepositoryFromSecret(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/actions/secrets/SECRET_NAME/repositories/64780797", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.RemoveSelectedRepositoryFromSecret(context.Background(), "o", "SECRET_NAME", int64(64780797)) + if err != nil { + t.Errorf("Organizations.RemoveSelectedRepositoryFromSecret returned error: %v", err) + } +}