-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Implement support for action secrets #1402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5ed8db9
c6643e9
1908e4b
00f2f03
bbff48b
6367ea6
bbe3d53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Copyright 2020 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 | ||
|
||
// ActionsService handles communication with the actions related | ||
// methods of the GitHub API. | ||
// | ||
// GitHub API docs: https://developer.github.com/v3/actions/ | ||
type ActionsService service |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// Copyright 2020 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" | ||
) | ||
|
||
// PublicKey represents the public key that should be used to encrypt secrets. | ||
type PublicKey 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-your-public-key | ||
func (s *ActionsService) GetPublicKey(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 { | ||
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"` | ||
} | ||
|
||
// Secrets represents one item from the ListSecrets response. | ||
type Secrets struct { | ||
TotalCount int `json:"total_count"` | ||
Secrets []*Secret `json:"secrets"` | ||
} | ||
|
||
// ListSecrets 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, opt *ListOptions) (*Secrets, *Response, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When you get a chance, could you please change |
||
u := fmt.Sprintf("repos/%s/%s/actions/secrets", owner, repo) | ||
u, err := addOptions(u, opt) | ||
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 | ||
} | ||
|
||
// GetSecret gets a single secret without revealing its encrypted value. | ||
// | ||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#get-a-secret | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please contact [email protected] and let them know that they shouldn't need pagination for this endpoint, yet their documentation shows that they do? (Or maybe just ask why they do... whichever you prefer.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've reached out to GitHub support, more details below in comment below. |
||
func (s *ActionsService) GetSecret(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 { | ||
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 | ||
} | ||
|
||
// 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 { | ||
martinssipenko marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Name string `json:"-"` | ||
KeyID string `json:"key_id"` | ||
EncryptedValue string `json:"encrypted_value"` | ||
} | ||
|
||
// CreateOrUpdateSecret creates or updates a 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) { | ||
u := fmt.Sprintf("repos/%v/%v/actions/secrets/%v", owner, repo, 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-a-secret-from-a-repository | ||
func (s *ActionsService) DeleteSecret(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) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return s.client.Do(ctx, req, nil) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
// Copyright 2020 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" | ||
"net/http" | ||
"reflect" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestActionsService_GetPublicKey(t *testing.T) { | ||
client, mux, _, teardown := setup() | ||
defer teardown() | ||
|
||
mux.HandleFunc("/repos/o/r/actions/secrets/public-key", func(w http.ResponseWriter, r *http.Request) { | ||
testMethod(t, r, "GET") | ||
fmt.Fprint(w, `{"key_id":"1234","key":"2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"}`) | ||
}) | ||
|
||
key, _, err := client.Actions.GetPublicKey(context.Background(), "o", "r") | ||
if err != nil { | ||
t.Errorf("Actions.GetPublicKey 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) | ||
} | ||
} | ||
|
||
func TestActionsService_ListSecrets(t *testing.T) { | ||
client, mux, _, teardown := setup() | ||
defer teardown() | ||
|
||
mux.HandleFunc("/repos/o/r/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":4,"secrets":[{"name":"A","created_at":"2019-01-02T15:04:05Z","updated_at":"2020-01-02T15:04:05Z"},{"name":"B","created_at":"2019-01-02T15:04:05Z","updated_at":"2020-01-02T15:04:05Z"}]}`) | ||
}) | ||
|
||
opt := &ListOptions{Page: 2, PerPage: 2} | ||
secrets, _, err := client.Actions.ListSecrets(context.Background(), "o", "r", opt) | ||
if err != nil { | ||
t.Errorf("Actions.ListSecrets returned error: %v", err) | ||
} | ||
|
||
want := &Secrets{ | ||
TotalCount: 4, | ||
Secrets: []*Secret{ | ||
{Name: "A", 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)}}, | ||
{Name: "B", 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)}}, | ||
}, | ||
} | ||
if !reflect.DeepEqual(secrets, want) { | ||
t.Errorf("Actions.ListSecrets returned %+v, want %+v", secrets, want) | ||
} | ||
} | ||
|
||
func TestActionsService_GetSecret(t *testing.T) { | ||
client, mux, _, teardown := setup() | ||
defer teardown() | ||
|
||
mux.HandleFunc("/repos/o/r/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"}`) | ||
}) | ||
|
||
secret, _, err := client.Actions.GetSecret(context.Background(), "o", "r", "NAME") | ||
if err != nil { | ||
t.Errorf("Actions.GetSecret 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)}, | ||
} | ||
if !reflect.DeepEqual(secret, want) { | ||
t.Errorf("Actions.GetSecret returned %+v, want %+v", secret, want) | ||
} | ||
} | ||
|
||
func TestActionsService_CreateOrUpdateSecret(t *testing.T) { | ||
client, mux, _, teardown := setup() | ||
defer teardown() | ||
|
||
mux.HandleFunc("/repos/o/r/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="}`+"\n") | ||
w.WriteHeader(http.StatusCreated) | ||
}) | ||
|
||
input := &EncryptedSecret{ | ||
Name: "NAME", | ||
EncryptedValue: "QIv=", | ||
KeyID: "1234", | ||
} | ||
_, err := client.Actions.CreateOrUpdateSecret(context.Background(), "o", "r", input) | ||
if err != nil { | ||
t.Errorf("Actions.CreateOrUpdateSecret returned error: %v", err) | ||
} | ||
} | ||
|
||
func TestActionsService_DeleteSecret(t *testing.T) { | ||
client, mux, _, teardown := setup() | ||
defer teardown() | ||
|
||
mux.HandleFunc("/repos/o/r/actions/secrets/NAME", func(w http.ResponseWriter, r *http.Request) { | ||
testMethod(t, r, "DELETE") | ||
}) | ||
|
||
_, err := client.Actions.DeleteSecret(context.Background(), "o", "r", "NAME") | ||
if err != nil { | ||
t.Errorf("Actions.DeleteSecret returned error: %v", err) | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.