| // package travisci provides a library for interacting with the Github app |
| // Travis CI via it's API: https://docs.travis-ci.com/api/#api-v3 |
| // |
| // Authentication is done using a travis access token as described in |
| // https://docs.travis-ci.com/api/#authentication |
| |
| package travisci |
| |
| import ( |
| "context" |
| "encoding/json" |
| "fmt" |
| "io" |
| "net/http" |
| "net/url" |
| "strconv" |
| |
| "go.skia.org/infra/go/httputils" |
| "go.skia.org/infra/go/util" |
| ) |
| |
| const ( |
| TRAVIS_API_URL = "https://api.travis-ci.org" |
| TRAVIS_API_VERSION = "3" |
| |
| BUILD_STATE_CREATED = "created" |
| BUILD_STATE_PASSED = "passed" |
| BUILD_STATE_FAILED = "failed" |
| |
| FETCH_PULL_REQUESTS_LIMIT = 100 |
| ) |
| |
| // TravisCI is an object used for iteracting with the Travis CI V3 API. |
| // Docs are here: https://docs.travis-ci.com/api/#api-v3 |
| type TravisCI struct { |
| client *http.Client |
| ctx context.Context |
| repoOwner string |
| repoName string |
| apiURL string |
| token string |
| } |
| |
| type Builds struct { |
| Builds []*Build `json:"builds"` |
| } |
| |
| type Build struct { |
| Id int `json:"id"` |
| Duration int `json:"duration"` |
| State string `json:"state"` |
| StartedAt string `json:"started_at"` |
| PullRequestNumber int `json:"pull_request_number"` |
| } |
| |
| // NewTravisCI returns a new TravisCI instance. |
| func NewTravisCI(ctx context.Context, repoOwner, repoName, accessToken string) (*TravisCI, error) { |
| return &TravisCI{ |
| client: httputils.NewTimeoutClient(), |
| ctx: ctx, |
| repoOwner: repoOwner, |
| repoName: repoName, |
| apiURL: TRAVIS_API_URL, |
| token: accessToken, |
| }, nil |
| } |
| |
| func (t *TravisCI) GetPullRequestBuilds(pullNumber int, createdBy string) ([]*Build, error) { |
| params := url.Values{} |
| params.Add("limit", strconv.Itoa(FETCH_PULL_REQUESTS_LIMIT)) |
| params.Add("sort_by", "id:desc") |
| params.Add("event_type", "pull_request") |
| params.Add("created_by", createdBy) |
| repositorySlug := fmt.Sprintf("%s/%s", t.repoOwner, t.repoName) |
| suburl := fmt.Sprintf("/repo/%s/builds?%s", url.QueryEscape(repositorySlug), params.Encode()) |
| |
| builds := &Builds{} |
| if err := t.get(suburl, builds); err != nil { |
| return nil, fmt.Errorf("API call to %s failed: %s", suburl, err) |
| } |
| pullRequestBuilds := []*Build{} |
| for _, build := range builds.Builds { |
| if build.PullRequestNumber == pullNumber { |
| pullRequestBuilds = append(pullRequestBuilds, build) |
| } |
| } |
| return pullRequestBuilds, nil |
| } |
| |
| func (t *TravisCI) GetBuildURL(buildID int) string { |
| return fmt.Sprintf("https://travis-ci.org/flutter/engine/builds/%d", buildID) |
| } |
| |
| // get retrieves the given suburl and populates 'rv' with the result. |
| func (t *TravisCI) get(suburl string, rv interface{}) error { |
| getURL := t.apiURL + suburl |
| req, err := http.NewRequest("GET", getURL, nil) |
| if err != nil { |
| return err |
| } |
| req.Header.Set("Travis-API-Version", TRAVIS_API_VERSION) |
| req.Header.Set("Authorization", fmt.Sprintf("token %s", t.token)) |
| |
| resp, err := t.client.Do(req) |
| if err != nil { |
| return fmt.Errorf("Failed to GET %s: %s", getURL, err) |
| } |
| if resp.StatusCode == http.StatusNotFound { |
| return fmt.Errorf("Build not found: %s", getURL) |
| } |
| if resp.StatusCode >= http.StatusBadRequest { |
| return fmt.Errorf("Error retrieving %s: %d %s", getURL, resp.StatusCode, resp.Status) |
| } |
| defer util.Close(resp.Body) |
| body, err := io.ReadAll(resp.Body) |
| if err != nil { |
| return fmt.Errorf("Could not read response body: %s", err) |
| } |
| if err := json.Unmarshal(body, &rv); err != nil { |
| return fmt.Errorf("Failed to decode JSON: %s", err) |
| } |
| return nil |
| } |