github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/release/update/kbweb.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package update 5 6 import ( 7 "bytes" 8 "crypto/tls" 9 "crypto/x509" 10 "encoding/json" 11 "fmt" 12 "io" 13 "log" 14 "net/http" 15 "time" 16 17 "github.com/keybase/client/go/libkb" 18 ) 19 20 const ( 21 kbwebAPIUrl = "https://api-1.core.keybaseapi.com" 22 ) 23 24 type kbwebClient struct { 25 http *http.Client 26 } 27 28 type APIResponseWrapper interface { 29 StatusCode() int 30 } 31 32 type AppResponseBase struct { 33 Status struct { 34 Code int 35 Desc string 36 } 37 } 38 39 func (s *AppResponseBase) StatusCode() int { 40 return s.Status.Code 41 } 42 43 // newKbwebClient constructs a Client 44 func newKbwebClient() (*kbwebClient, error) { 45 certPool := x509.NewCertPool() 46 ok := certPool.AppendCertsFromPEM([]byte(libkb.APICA)) 47 if !ok { 48 return nil, fmt.Errorf("Could not read CA for keybase.io") 49 } 50 client := &http.Client{ 51 Transport: &http.Transport{ 52 TLSClientConfig: &tls.Config{RootCAs: certPool}, 53 }, 54 } 55 return &kbwebClient{http: client}, nil 56 } 57 58 func (client *kbwebClient) post(keybaseToken string, path string, data []byte, response APIResponseWrapper) error { 59 req, err := http.NewRequest("POST", kbwebAPIUrl+path, bytes.NewBuffer(data)) 60 if err != nil { 61 return fmt.Errorf("newrequest failed, %v", err) 62 } 63 req.Header.Add("content-type", "application/json") 64 req.Header.Add("x-keybase-admin-token", keybaseToken) 65 resp, err := client.http.Do(req) 66 if err != nil { 67 return fmt.Errorf("request failed, %v", err) 68 } 69 defer resp.Body.Close() 70 71 body, err := io.ReadAll(resp.Body) 72 if err != nil { 73 return fmt.Errorf("body err, %v", err) 74 } 75 76 if response == nil { 77 response = new(AppResponseBase) 78 } 79 if err := json.Unmarshal(body, &response); err != nil { 80 return fmt.Errorf("json reply err, %v", err) 81 } 82 83 if response.StatusCode() != 0 { 84 return fmt.Errorf("Server returned failure, %s", body) 85 } 86 87 fmt.Printf("Success.\n") 88 return nil 89 } 90 91 type announceBuildArgs struct { 92 VersionA string `json:"version_a"` 93 VersionB string `json:"version_b"` 94 Platform string `json:"platform"` 95 } 96 97 // AnnounceBuild tells the API server about the existence of a new build. 98 // It does not enroll it in smoke testing. 99 func AnnounceBuild(keybaseToken string, buildA string, buildB string, platform string) error { 100 client, err := newKbwebClient() 101 if err != nil { 102 return fmt.Errorf("client create failed, %v", err) 103 } 104 args := &announceBuildArgs{ 105 VersionA: buildA, 106 VersionB: buildB, 107 Platform: platform, 108 } 109 jsonStr, err := json.Marshal(args) 110 if err != nil { 111 return fmt.Errorf("json marshal err, %v", err) 112 } 113 var data = jsonStr 114 return client.post(keybaseToken, "/_/api/1.0/pkg/add_build.json", data, nil) 115 } 116 117 type promoteBuildArgs struct { 118 VersionA string `json:"version_a"` 119 Platform string `json:"platform"` 120 } 121 122 type promoteBuildResponse struct { 123 AppResponseBase 124 ReleaseTimeMs int64 `json:"release_time"` 125 } 126 127 // KBWebPromote tells the API server that a new build is promoted. 128 func KBWebPromote(keybaseToken string, buildA string, platform string, dryRun bool) (releaseTime time.Time, err error) { 129 client, err := newKbwebClient() 130 if err != nil { 131 return releaseTime, fmt.Errorf("client create failed, %v", err) 132 } 133 args := &promoteBuildArgs{ 134 VersionA: buildA, 135 Platform: platform, 136 } 137 jsonStr, err := json.Marshal(args) 138 if err != nil { 139 return releaseTime, fmt.Errorf("json marshal err, %v", err) 140 } 141 var data = jsonStr 142 var response promoteBuildResponse 143 if dryRun { 144 log.Printf("DRYRUN: Would post %s\n", data) 145 return releaseTime, nil 146 } 147 err = client.post(keybaseToken, "/_/api/1.0/pkg/set_released.json", data, &response) 148 if err != nil { 149 return releaseTime, err 150 } 151 releaseTime = time.Unix(0, response.ReleaseTimeMs*int64(time.Millisecond)) 152 log.Printf("Release time set to %v for build %v", releaseTime, buildA) 153 return releaseTime, nil 154 } 155 156 type setBuildInTestingArgs struct { 157 VersionA string `json:"version_a"` 158 Platform string `json:"platform"` 159 InTesting string `json:"in_testing"` 160 MaxTesters int `json:"max_testers"` 161 } 162 163 // SetBuildInTesting tells the API server to enroll or unenroll a build in smoke testing. 164 func SetBuildInTesting(keybaseToken string, buildA string, platform string, inTesting string, maxTesters int) error { 165 client, err := newKbwebClient() 166 if err != nil { 167 return fmt.Errorf("client create failed, %v", err) 168 } 169 args := &setBuildInTestingArgs{ 170 VersionA: buildA, 171 Platform: platform, 172 InTesting: inTesting, 173 MaxTesters: maxTesters, 174 } 175 jsonStr, err := json.Marshal(args) 176 if err != nil { 177 return fmt.Errorf("json marshal err: %v", err) 178 } 179 var data = jsonStr 180 return client.post(keybaseToken, "/_/api/1.0/pkg/set_in_testing.json", data, nil) 181 }