cli/pkg/cmd/secret/set/http.go
2021-08-25 12:41:30 +02:00

149 lines
4.3 KiB
Go

package set
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"sort"
"strings"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/pkg/cmd/secret/shared"
)
type SecretPayload struct {
EncryptedValue string `json:"encrypted_value"`
Visibility string `json:"visibility,omitempty"`
Repositories []int `json:"selected_repository_ids,omitempty"`
KeyID string `json:"key_id"`
}
type PubKey struct {
Raw [32]byte
ID string `json:"key_id"`
Key string
}
func getPubKey(client *api.Client, host, path string) (*PubKey, error) {
pk := PubKey{}
err := client.REST(host, "GET", path, nil, &pk)
if err != nil {
return nil, err
}
if pk.Key == "" {
return nil, fmt.Errorf("failed to find public key at %s/%s", host, path)
}
decoded, err := base64.StdEncoding.DecodeString(pk.Key)
if err != nil {
return nil, fmt.Errorf("failed to decode public key: %w", err)
}
copy(pk.Raw[:], decoded[0:32])
return &pk, nil
}
func getOrgPublicKey(client *api.Client, host, orgName string) (*PubKey, error) {
return getPubKey(client, host, fmt.Sprintf("orgs/%s/actions/secrets/public-key", orgName))
}
func getRepoPubKey(client *api.Client, repo ghrepo.Interface) (*PubKey, error) {
return getPubKey(client, repo.RepoHost(), fmt.Sprintf("repos/%s/actions/secrets/public-key",
ghrepo.FullName(repo)))
}
func getEnvPubKey(client *api.Client, repo ghrepo.Interface, envName string) (*PubKey, error) {
return getPubKey(client, repo.RepoHost(), fmt.Sprintf("repos/%s/environments/%s/secrets/public-key",
ghrepo.FullName(repo), envName))
}
func putSecret(client *api.Client, host, path string, payload SecretPayload) error {
payloadBytes, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("failed to serialize: %w", err)
}
requestBody := bytes.NewReader(payloadBytes)
return client.REST(host, "PUT", path, requestBody, nil)
}
func putOrgSecret(client *api.Client, host string, pk *PubKey, opts SetOptions, eValue string) error {
secretName := opts.SecretName
orgName := opts.OrgName
visibility := opts.Visibility
var repositoryIDs []int
var err error
if orgName != "" && visibility == shared.Selected {
repositoryIDs, err = mapRepoNameToID(client, host, orgName, opts.RepositoryNames)
if err != nil {
return fmt.Errorf("failed to look up IDs for repositories %v: %w", opts.RepositoryNames, err)
}
}
payload := SecretPayload{
EncryptedValue: eValue,
KeyID: pk.ID,
Repositories: repositoryIDs,
Visibility: visibility,
}
path := fmt.Sprintf("orgs/%s/actions/secrets/%s", orgName, secretName)
return putSecret(client, host, path, payload)
}
func putEnvSecret(client *api.Client, pk *PubKey, repo ghrepo.Interface, envName string, secretName, eValue string) error {
payload := SecretPayload{
EncryptedValue: eValue,
KeyID: pk.ID,
}
path := fmt.Sprintf("repos/%s/environments/%s/secrets/%s", ghrepo.FullName(repo), envName, secretName)
return putSecret(client, repo.RepoHost(), path, payload)
}
func putRepoSecret(client *api.Client, pk *PubKey, repo ghrepo.Interface, secretName, eValue string) error {
payload := SecretPayload{
EncryptedValue: eValue,
KeyID: pk.ID,
}
path := fmt.Sprintf("repos/%s/actions/secrets/%s", ghrepo.FullName(repo), secretName)
return putSecret(client, repo.RepoHost(), path, payload)
}
// This does similar logic to `api.RepoNetwork`, but without the overfetching.
func mapRepoNameToID(client *api.Client, host, orgName string, repositoryNames []string) ([]int, error) {
queries := make([]string, 0, len(repositoryNames))
for i, repoName := range repositoryNames {
queries = append(queries, fmt.Sprintf(`
repo_%03d: repository(owner: %q, name: %q) {
databaseId
}
`, i, orgName, repoName))
}
query := fmt.Sprintf(`query MapRepositoryNames { %s }`, strings.Join(queries, ""))
graphqlResult := make(map[string]*struct {
DatabaseID int `json:"databaseId"`
})
if err := client.GraphQL(host, query, nil, &graphqlResult); err != nil {
return nil, fmt.Errorf("failed to look up repositories: %w", err)
}
repoKeys := make([]string, 0, len(repositoryNames))
for k := range graphqlResult {
repoKeys = append(repoKeys, k)
}
sort.Strings(repoKeys)
result := make([]int, len(repositoryNames))
for i, k := range repoKeys {
result[i] = graphqlResult[k].DatabaseID
}
return result, nil
}