github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/secret/set/http.go (about)

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