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 }