github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-cluster/pkg/api/client.go (about) 1 // Copyright 2024 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package api 5 6 import ( 7 "context" 8 "encoding/json" 9 "fmt" 10 "io" 11 "net/http" 12 "net/url" 13 "strings" 14 "time" 15 ) 16 17 type Client struct { 18 baseURL string 19 } 20 21 func NewClient(url string) *Client { 22 return &Client{baseURL: strings.TrimRight(url, "/")} 23 } 24 25 func (client Client) GetSessionSeries(ctx context.Context, sessionID string) (*Series, error) { 26 return getJSON[Series](ctx, client.baseURL+"/sessions/"+sessionID+"/series") 27 } 28 29 func (client Client) GetSeries(ctx context.Context, seriesID string) (*Series, error) { 30 return getJSON[Series](ctx, client.baseURL+"/series/"+seriesID) 31 } 32 33 type UploadTriageResultReq struct { 34 SkipReason string `json:"skip_reason"` 35 Log []byte `json:"log"` 36 } 37 38 func (client Client) UploadTriageResult(ctx context.Context, sessionID string, req *UploadTriageResultReq) error { 39 _, err := postJSON[UploadTriageResultReq, any](ctx, client.baseURL+"/sessions/"+sessionID+"/triage_result", req) 40 return err 41 } 42 43 type TreesResp struct { 44 Trees []*Tree `json:"trees"` 45 FuzzTargets []*FuzzTriageTarget `json:"fuzz_targets"` 46 } 47 48 func (client Client) GetTrees(ctx context.Context) (*TreesResp, error) { 49 return getJSON[TreesResp](ctx, client.baseURL+"/trees") 50 } 51 52 type LastBuildReq struct { 53 Arch string 54 ConfigName string 55 TreeName string 56 Commit string 57 Status string 58 } 59 60 const BuildSuccess = "success" 61 62 func (client Client) LastBuild(ctx context.Context, req *LastBuildReq) (*Build, error) { 63 return postJSON[LastBuildReq, Build](ctx, client.baseURL+"/builds/last", req) 64 } 65 66 type UploadBuildReq struct { 67 Build 68 Config []byte `json:"config"` 69 Log []byte `json:"log"` 70 } 71 72 type UploadBuildResp struct { 73 ID string 74 } 75 76 func (client Client) UploadBuild(ctx context.Context, req *UploadBuildReq) (*UploadBuildResp, error) { 77 return postJSON[UploadBuildReq, UploadBuildResp](ctx, client.baseURL+"/builds/upload", req) 78 } 79 80 func (client Client) UploadTestResult(ctx context.Context, req *TestResult) error { 81 _, err := postJSON[TestResult, any](ctx, client.baseURL+"/tests/upload", req) 82 return err 83 } 84 85 func (client Client) UploadTestArtifacts(ctx context.Context, sessionID, testName string, 86 tarGzContent io.Reader) error { 87 v := url.Values{} 88 v.Add("session", sessionID) 89 v.Add("test", testName) 90 url := client.baseURL + "/tests/upload_artifacts?" + v.Encode() 91 _, err := postMultiPartFile[any](ctx, url, tarGzContent) 92 return err 93 } 94 95 func (client Client) UploadFinding(ctx context.Context, req *NewFinding) error { 96 _, err := postJSON[NewFinding, any](ctx, client.baseURL+"/findings/upload", req) 97 return err 98 } 99 100 type UploadSeriesResp struct { 101 ID string `json:"id"` 102 Saved bool `json:"saved"` 103 } 104 105 func (client Client) UploadSeries(ctx context.Context, req *Series) (*UploadSeriesResp, error) { 106 return postJSON[Series, UploadSeriesResp](ctx, client.baseURL+"/series/upload", req) 107 } 108 109 type UploadSessionResp struct { 110 ID string `json:"id"` 111 } 112 113 func (client Client) UploadSession(ctx context.Context, req *NewSession) (*UploadSessionResp, error) { 114 return postJSON[NewSession, UploadSessionResp](ctx, client.baseURL+"/sessions/upload", req) 115 } 116 117 type BaseFindingInfo struct { 118 BuildID string `json:"buildID"` 119 Title string `json:"title"` 120 } 121 122 func (client Client) UploadBaseFinding(ctx context.Context, req *BaseFindingInfo) error { 123 _, err := postJSON[BaseFindingInfo, any](ctx, client.baseURL+"/base_findings/upload", req) 124 return err 125 } 126 127 type BaseFindingStatus struct { 128 Observed bool `json:"observed"` 129 } 130 131 func (client Client) BaseFindingStatus(ctx context.Context, req *BaseFindingInfo) (*BaseFindingStatus, error) { 132 return postJSON[BaseFindingInfo, BaseFindingStatus](ctx, client.baseURL+"/base_findings/status", req) 133 } 134 135 const requestTimeout = time.Minute 136 137 func finishRequest[Resp any](httpReq *http.Request) (*Resp, error) { 138 client := &http.Client{ 139 Timeout: requestTimeout, 140 } 141 resp, err := client.Do(httpReq) 142 if err != nil { 143 return nil, err 144 } 145 defer resp.Body.Close() 146 if err := ensure200(resp); err != nil { 147 return nil, err 148 } 149 var data Resp 150 err = json.NewDecoder(resp.Body).Decode(&data) 151 if err != nil { 152 return nil, err 153 } 154 return &data, nil 155 } 156 157 func ensure200(resp *http.Response) error { 158 if resp.StatusCode != http.StatusOK { 159 bodyBytes, _ := io.ReadAll(resp.Body) 160 if len(bodyBytes) > 128 { 161 bodyBytes = bodyBytes[:128] 162 } 163 return fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, bodyBytes) 164 } 165 return nil 166 }