github.com/andrewhsu/cli/v2@v2.0.1-0.20210910131313-d4b4061f5b89/pkg/cmd/secret/set/http.go (about)

     1  package set
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/andrewhsu/cli/v2/api"
    12  	"github.com/andrewhsu/cli/v2/internal/ghrepo"
    13  	"github.com/andrewhsu/cli/v2/pkg/cmd/secret/shared"
    14  )
    15  
    16  type SecretPayload struct {
    17  	EncryptedValue string `json:"encrypted_value"`
    18  	Visibility     string `json:"visibility,omitempty"`
    19  	Repositories   []int  `json:"selected_repository_ids,omitempty"`
    20  	KeyID          string `json:"key_id"`
    21  }
    22  
    23  type PubKey struct {
    24  	Raw [32]byte
    25  	ID  string `json:"key_id"`
    26  	Key string
    27  }
    28  
    29  func getPubKey(client *api.Client, host, path string) (*PubKey, error) {
    30  	pk := PubKey{}
    31  	err := client.REST(host, "GET", path, nil, &pk)
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  
    36  	if pk.Key == "" {
    37  		return nil, fmt.Errorf("failed to find public key at %s/%s", host, path)
    38  	}
    39  
    40  	decoded, err := base64.StdEncoding.DecodeString(pk.Key)
    41  	if err != nil {
    42  		return nil, fmt.Errorf("failed to decode public key: %w", err)
    43  	}
    44  
    45  	copy(pk.Raw[:], decoded[0:32])
    46  	return &pk, nil
    47  }
    48  
    49  func getOrgPublicKey(client *api.Client, host, orgName string) (*PubKey, error) {
    50  	return getPubKey(client, host, fmt.Sprintf("orgs/%s/actions/secrets/public-key", orgName))
    51  }
    52  
    53  func getRepoPubKey(client *api.Client, repo ghrepo.Interface) (*PubKey, error) {
    54  	return getPubKey(client, repo.RepoHost(), fmt.Sprintf("repos/%s/actions/secrets/public-key",
    55  		ghrepo.FullName(repo)))
    56  }
    57  
    58  func getEnvPubKey(client *api.Client, repo ghrepo.Interface, envName string) (*PubKey, error) {
    59  	return getPubKey(client, repo.RepoHost(), fmt.Sprintf("repos/%s/environments/%s/secrets/public-key",
    60  		ghrepo.FullName(repo), envName))
    61  }
    62  
    63  func putSecret(client *api.Client, host, path string, payload SecretPayload) error {
    64  	payloadBytes, err := json.Marshal(payload)
    65  	if err != nil {
    66  		return fmt.Errorf("failed to serialize: %w", err)
    67  	}
    68  	requestBody := bytes.NewReader(payloadBytes)
    69  
    70  	return client.REST(host, "PUT", path, requestBody, nil)
    71  }
    72  
    73  func putOrgSecret(client *api.Client, host string, pk *PubKey, opts SetOptions, eValue string) error {
    74  	secretName := opts.SecretName
    75  	orgName := opts.OrgName
    76  	visibility := opts.Visibility
    77  
    78  	var repositoryIDs []int
    79  	var err error
    80  	if orgName != "" && visibility == shared.Selected {
    81  		repositoryIDs, err = mapRepoNameToID(client, host, orgName, opts.RepositoryNames)
    82  		if err != nil {
    83  			return fmt.Errorf("failed to look up IDs for repositories %v: %w", opts.RepositoryNames, err)
    84  		}
    85  	}
    86  
    87  	payload := SecretPayload{
    88  		EncryptedValue: eValue,
    89  		KeyID:          pk.ID,
    90  		Repositories:   repositoryIDs,
    91  		Visibility:     visibility,
    92  	}
    93  	path := fmt.Sprintf("orgs/%s/actions/secrets/%s", orgName, secretName)
    94  
    95  	return putSecret(client, host, path, payload)
    96  }
    97  
    98  func putEnvSecret(client *api.Client, pk *PubKey, repo ghrepo.Interface, envName string, secretName, eValue string) error {
    99  	payload := SecretPayload{
   100  		EncryptedValue: eValue,
   101  		KeyID:          pk.ID,
   102  	}
   103  	path := fmt.Sprintf("repos/%s/environments/%s/secrets/%s", ghrepo.FullName(repo), envName, secretName)
   104  	return putSecret(client, repo.RepoHost(), path, payload)
   105  }
   106  
   107  func putRepoSecret(client *api.Client, pk *PubKey, repo ghrepo.Interface, secretName, eValue string) error {
   108  	payload := SecretPayload{
   109  		EncryptedValue: eValue,
   110  		KeyID:          pk.ID,
   111  	}
   112  	path := fmt.Sprintf("repos/%s/actions/secrets/%s", ghrepo.FullName(repo), secretName)
   113  	return putSecret(client, repo.RepoHost(), path, payload)
   114  }
   115  
   116  // This does similar logic to `api.RepoNetwork`, but without the overfetching.
   117  func mapRepoNameToID(client *api.Client, host, orgName string, repositoryNames []string) ([]int, error) {
   118  	queries := make([]string, 0, len(repositoryNames))
   119  	for i, repoName := range repositoryNames {
   120  		queries = append(queries, fmt.Sprintf(`
   121  			repo_%03d: repository(owner: %q, name: %q) {
   122  				databaseId
   123  			}
   124  		`, i, orgName, repoName))
   125  	}
   126  
   127  	query := fmt.Sprintf(`query MapRepositoryNames { %s }`, strings.Join(queries, ""))
   128  
   129  	graphqlResult := make(map[string]*struct {
   130  		DatabaseID int `json:"databaseId"`
   131  	})
   132  
   133  	if err := client.GraphQL(host, query, nil, &graphqlResult); err != nil {
   134  		return nil, fmt.Errorf("failed to look up repositories: %w", err)
   135  	}
   136  
   137  	repoKeys := make([]string, 0, len(repositoryNames))
   138  	for k := range graphqlResult {
   139  		repoKeys = append(repoKeys, k)
   140  	}
   141  	sort.Strings(repoKeys)
   142  
   143  	result := make([]int, len(repositoryNames))
   144  	for i, k := range repoKeys {
   145  		result[i] = graphqlResult[k].DatabaseID
   146  	}
   147  
   148  	return result, nil
   149  }