Chromium Code Reviews| Index: go/src/infra/monorail/endpoints.go |
| diff --git a/go/src/infra/monorail/endpoints.go b/go/src/infra/monorail/endpoints.go |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..fe7de9f0d4b0284fdb4e4529a9daa9ec04e7378e |
| --- /dev/null |
| +++ b/go/src/infra/monorail/endpoints.go |
| @@ -0,0 +1,97 @@ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +package monorail |
| + |
| +import ( |
| + "bytes" |
| + "encoding/json" |
| + "fmt" |
| + "io" |
| + "io/ioutil" |
| + "net/http" |
| + |
| + "golang.org/x/net/context" |
| + "google.golang.org/grpc" |
| + |
| + "github.com/luci/luci-go/common/errors" |
| + "github.com/luci/luci-go/common/logging" |
| + |
| + "infra/httputil" |
| +) |
| + |
| +// epClient implements MonorailClient by sending requests to Monorail's |
| +// Cloud Endpoints API. |
| +// See https://monorail-staging.appspot.com/_ah/api/explorer#p/monorail/v1/ |
| +type epClient struct { |
| + HTTP *http.Client |
| + Host string |
| +} |
| + |
| +// NewEndpointsClient creates a MonorailClient that send requests to Monorail's |
| +// Cloud Endpoints. |
| +func NewEndpointsClient(client *http.Client, host string) MonorailClient { |
| + return &epClient{HTTP: client, Host: host} |
| +} |
| + |
| +func (c *epClient) call(ctx context.Context, urlSuffix string, request, response interface{}) error { |
| + var buf bytes.Buffer |
| + if err := json.NewEncoder(&buf).Encode(request); err != nil { |
| + return err |
| + } |
| + reqBytes := buf.Bytes() |
| + |
| + u := fmt.Sprintf("https://%s/_ah/api/monorail/v1/%s", c.Host, urlSuffix) |
| + req, err := http.NewRequest("POST", u, nil) |
| + if err != nil { |
| + return err |
| + } |
| + req.Header.Set("Content-Type", "application/json") |
| + req.Header.Set("Accept", "application/json") |
| + |
| + logging.Debugf(ctx, "POST %s %s", u, reqBytes) |
| + |
| + return httputil.RetryRequest(ctx, c.HTTP, req, reqBytes, func(res *http.Response) error { |
| + if res.StatusCode != http.StatusOK { |
| + text, _ := ioutil.ReadAll(io.LimitReader(res.Body, 1024)) |
| + err := fmt.Errorf("unexpected status %q. Response: %s", res.Status, text) |
| + if res.StatusCode == http.StatusNotFound { |
| + // Occasionally Cloud Endpoints returns 404. |
|
seanmccullough1
2016/06/03 18:45:33
Even when it really should be a 500 :)
nodir
2016/06/03 18:56:12
yes!
|
| + err = errors.WrapTransient(err) |
| + } |
| + return err |
| + } |
| + if response == nil { |
| + return nil |
| + } |
| + return json.NewDecoder(res.Body).Decode(response) |
| + }) |
| +} |
| + |
| +func (c *epClient) InsertIssue(ctx context.Context, req *InsertIssueRequest, options ...grpc.CallOption) (*InsertIssueResponse, error) { |
|
seanmccullough1
2016/06/03 18:45:33
Godoc here and below. Mention that even though the
nodir
2016/06/03 18:56:12
these functions are internal, so nobody will see t
|
| + if err := checkOptions(options); err != nil { |
| + return nil, err |
| + } |
| + if err := req.Validate(); err != nil { |
| + return nil, err |
| + } |
| + url := fmt.Sprintf("projects/%s/issues?sendEmail=%v", req.ProjectId, req.SendEmail) |
| + res := &InsertIssueResponse{&Issue{}} |
| + return res, c.call(ctx, url, &req.Issue, res.Issue) |
| +} |
| + |
| +func (c *epClient) InsertComment(ctx context.Context, req *InsertCommentRequest, options ...grpc.CallOption) (*InsertCommentResponse, error) { |
| + if err := checkOptions(options); err != nil { |
| + return nil, err |
| + } |
| + url := fmt.Sprintf("projects/%s/issues/%d/comments", req.Issue.ProjectId, req.Issue.IssueId) |
| + return &InsertCommentResponse{}, c.call(ctx, url, req.Comment, nil) |
| +} |
| + |
| +func checkOptions(options []grpc.CallOption) error { |
| + if len(options) > 0 { |
| + return errGrpcOptions |
| + } |
| + return nil |
| +} |