github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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  }