2016-05-18 17:17:28 +00:00
|
|
|
// NOTE: Subject to change, do not rely on this package from outside git-lfs source
|
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
2016-05-25 19:31:12 +00:00
|
|
|
"errors"
|
2016-05-18 17:17:28 +00:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2016-05-23 19:34:02 +00:00
|
|
|
|
2016-11-15 17:01:18 +00:00
|
|
|
"github.com/git-lfs/git-lfs/auth"
|
|
|
|
"github.com/git-lfs/git-lfs/config"
|
|
|
|
"github.com/git-lfs/git-lfs/httputil"
|
2016-05-18 17:17:28 +00:00
|
|
|
)
|
|
|
|
|
2016-05-25 19:31:12 +00:00
|
|
|
var (
|
|
|
|
// ErrNoOperationGiven is an error which is returned when no operation
|
|
|
|
// is provided in a RequestSchema object.
|
|
|
|
ErrNoOperationGiven = errors.New("lfs/api: no operation provided in schema")
|
|
|
|
)
|
|
|
|
|
2016-05-18 17:17:28 +00:00
|
|
|
// HttpLifecycle serves as the default implementation of the Lifecycle interface
|
|
|
|
// for HTTP requests. Internally, it leverages the *http.Client type to execute
|
|
|
|
// HTTP requests against a root *url.URL, as given in `NewHttpLifecycle`.
|
|
|
|
type HttpLifecycle struct {
|
2016-12-05 11:15:36 +00:00
|
|
|
cfg *config.Configuration
|
2016-05-18 17:17:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var _ Lifecycle = new(HttpLifecycle)
|
|
|
|
|
|
|
|
// NewHttpLifecycle initializes a new instance of the *HttpLifecycle type with a
|
|
|
|
// new *http.Client, and the given root (see above).
|
2016-12-06 10:06:14 +00:00
|
|
|
// Passing a nil Configuration will use the global config
|
2016-12-05 11:15:36 +00:00
|
|
|
func NewHttpLifecycle(cfg *config.Configuration) *HttpLifecycle {
|
|
|
|
if cfg == nil {
|
|
|
|
cfg = config.Config
|
|
|
|
}
|
2016-05-18 17:17:28 +00:00
|
|
|
return &HttpLifecycle{
|
2016-12-05 11:15:36 +00:00
|
|
|
cfg: cfg,
|
2016-05-18 17:17:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build implements the Lifecycle.Build function.
|
|
|
|
//
|
|
|
|
// HttpLifecycle in particular, builds an absolute path by parsing and then
|
|
|
|
// relativizing the `schema.Path` with respsect to the `HttpLifecycle.root`. If
|
|
|
|
// there was an error in determining this URL, then that error will be returned,
|
|
|
|
//
|
|
|
|
// After this is complete, a body is attached to the request if the
|
|
|
|
// schema contained one. If a body was present, and there an error occurred while
|
|
|
|
// serializing it into JSON, then that error will be returned and the
|
|
|
|
// *http.Request will not be generated.
|
|
|
|
//
|
2016-05-20 23:50:36 +00:00
|
|
|
// In all cases, credentials are attached to the HTTP request as described in
|
2016-11-15 17:07:11 +00:00
|
|
|
// the `auth` package (see github.com/git-lfs/git-lfs/auth#GetCreds).
|
2016-05-20 23:50:36 +00:00
|
|
|
//
|
2016-05-18 17:17:28 +00:00
|
|
|
// Finally, all of these components are combined together and the resulting
|
|
|
|
// request is returned.
|
2016-05-18 17:43:56 +00:00
|
|
|
func (l *HttpLifecycle) Build(schema *RequestSchema) (*http.Request, error) {
|
2016-05-25 19:31:12 +00:00
|
|
|
path, err := l.absolutePath(schema.Operation, schema.Path)
|
2016-05-18 17:17:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-05-20 23:50:36 +00:00
|
|
|
body, err := l.body(schema)
|
2016-05-18 17:17:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-05-18 19:19:20 +00:00
|
|
|
req, err := http.NewRequest(schema.Method, path.String(), body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-12-05 11:15:36 +00:00
|
|
|
if _, err = auth.GetCreds(l.cfg, req); err != nil {
|
2016-05-23 19:34:02 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2016-05-20 23:50:36 +00:00
|
|
|
|
|
|
|
req.URL.RawQuery = l.queryParameters(schema).Encode()
|
2016-05-18 19:19:20 +00:00
|
|
|
|
|
|
|
return req, nil
|
2016-05-18 17:17:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Execute implements the Lifecycle.Execute function.
|
|
|
|
//
|
|
|
|
// Internally, the *http.Client is used to execute the underlying *http.Request.
|
|
|
|
// If the client returned an error corresponding to a failure to make the
|
|
|
|
// request, then that error will be returned immediately, and the response is
|
|
|
|
// guaranteed not to be serialized.
|
|
|
|
//
|
|
|
|
// Once the response has been gathered from the server, it is unmarshled into
|
|
|
|
// the given `into interface{}` which is identical to the one provided in the
|
|
|
|
// original RequestSchema. If an error occured while decoding, then that error
|
|
|
|
// is returned.
|
|
|
|
//
|
|
|
|
// Otherwise, the api.Response is returned, along with no error, signaling that
|
|
|
|
// the request completed successfully.
|
2016-05-18 17:43:56 +00:00
|
|
|
func (l *HttpLifecycle) Execute(req *http.Request, into interface{}) (Response, error) {
|
2016-12-05 11:15:36 +00:00
|
|
|
resp, err := httputil.DoHttpRequestWithRedirects(l.cfg, req, []*http.Request{}, true)
|
2016-05-18 17:17:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-05-26 19:54:58 +00:00
|
|
|
// TODO(taylor): check status >=500, handle content type, return error,
|
|
|
|
// halt immediately.
|
|
|
|
|
2016-05-18 17:17:28 +00:00
|
|
|
if into != nil {
|
|
|
|
decoder := json.NewDecoder(resp.Body)
|
2016-05-18 21:44:34 +00:00
|
|
|
if err = decoder.Decode(into); err != nil {
|
2016-05-18 17:17:28 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return WrapHttpResponse(resp), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cleanup implements the Lifecycle.Cleanup function by closing the Body
|
2016-05-20 23:50:36 +00:00
|
|
|
// attached to the response.
|
2016-05-18 17:43:56 +00:00
|
|
|
func (l *HttpLifecycle) Cleanup(resp Response) error {
|
2016-05-18 17:17:28 +00:00
|
|
|
return resp.Body().Close()
|
|
|
|
}
|
|
|
|
|
2016-05-20 23:50:36 +00:00
|
|
|
// absolutePath returns the absolute path made by combining a given relative
|
2016-05-25 19:31:12 +00:00
|
|
|
// path with the root URL of the endpoint corresponding to the given operation.
|
|
|
|
//
|
|
|
|
// If there was an error in parsing the relative path, then that error will be
|
|
|
|
// returned.
|
|
|
|
func (l *HttpLifecycle) absolutePath(operation Operation, path string) (*url.URL, error) {
|
|
|
|
if len(operation) == 0 {
|
|
|
|
return nil, ErrNoOperationGiven
|
|
|
|
}
|
|
|
|
|
2016-12-05 11:15:36 +00:00
|
|
|
root, err := url.Parse(l.cfg.Endpoint(string(operation)).Url)
|
2016-05-25 19:31:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-05-18 17:17:28 +00:00
|
|
|
rel, err := url.Parse(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-05-25 19:31:12 +00:00
|
|
|
return root.ResolveReference(rel), nil
|
2016-05-18 17:17:28 +00:00
|
|
|
}
|
|
|
|
|
2016-05-20 23:50:36 +00:00
|
|
|
// body returns an io.Reader which reads out a JSON-encoded copy of the payload
|
2016-05-18 17:17:28 +00:00
|
|
|
// attached to a given *RequestSchema, if it is present. If no body is present
|
|
|
|
// in the request, then nil is returned instead.
|
|
|
|
//
|
|
|
|
// If an error was encountered while attempting to marshal the body, then that
|
|
|
|
// will be returned instead, along with a nil io.Reader.
|
2016-05-20 23:50:36 +00:00
|
|
|
func (l *HttpLifecycle) body(schema *RequestSchema) (io.ReadCloser, error) {
|
2016-05-18 17:17:28 +00:00
|
|
|
if schema.Body == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
body, err := json.Marshal(schema.Body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ioutil.NopCloser(bytes.NewReader(body)), nil
|
|
|
|
}
|
2016-05-18 19:19:20 +00:00
|
|
|
|
2016-05-20 23:50:36 +00:00
|
|
|
// queryParameters returns a url.Values containing all of the provided query
|
|
|
|
// parameters as given in the *RequestSchema. If no query parameters were given,
|
|
|
|
// then an empty url.Values is returned instead.
|
|
|
|
func (l *HttpLifecycle) queryParameters(schema *RequestSchema) url.Values {
|
2016-05-18 19:19:20 +00:00
|
|
|
vals := url.Values{}
|
|
|
|
if schema.Query != nil {
|
|
|
|
for k, v := range schema.Query {
|
|
|
|
vals.Add(k, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return vals
|
|
|
|
}
|