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  }