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 }