git-lfs/api/v1.go
Sebastian Schuberth eb855bad0d Expose the result message in case of an SSH authentication error
Not only expose the error message, but also the result message from
calling SSH which contains details about the cause. At the example of
using the wrong SSH key for authentication, with this the console shows

    download check: exit status 255: Permission denied (publickey).

instead of just

    download check: exit status 255

and the log shows

    trace git-lfs: ssh: download with url failed,
    error: exit status 255, message: Permission denied (publickey).

instead of just

    trace git-lfs: ssh: download attempted with url.
    Error: exit status 255
2016-10-20 14:03:46 +02:00

163 lines
4.1 KiB
Go

package api
import (
"net/http"
"net/url"
"path"
"github.com/github/git-lfs/auth"
"github.com/github/git-lfs/config"
"github.com/github/git-lfs/errors"
"github.com/github/git-lfs/httputil"
"github.com/rubyist/tracerx"
)
const (
MediaType = "application/vnd.git-lfs+json; charset=utf-8"
)
// doLegacyApiRequest runs the request to the LFS legacy API.
func DoLegacyRequest(cfg *config.Configuration, req *http.Request) (*http.Response, *ObjectResource, error) {
via := make([]*http.Request, 0, 4)
res, err := httputil.DoHttpRequestWithRedirects(cfg, req, via, true)
if err != nil {
return res, nil, err
}
obj := &ObjectResource{}
err = httputil.DecodeResponse(res, obj)
if err != nil {
httputil.SetErrorResponseContext(cfg, err, res)
return nil, nil, err
}
return res, obj, nil
}
type batchRequest struct {
TransferAdapterNames []string `json:"transfers,omitempty"`
Operation string `json:"operation"`
Objects []*ObjectResource `json:"objects"`
}
type batchResponse struct {
TransferAdapterName string `json:"transfer"`
Objects []*ObjectResource `json:"objects"`
}
// doApiBatchRequest runs the request to the LFS batch API. If the API returns a
// 401, the repo will be marked as having private access and the request will be
// re-run. When the repo is marked as having private access, credentials will
// be retrieved.
func DoBatchRequest(cfg *config.Configuration, req *http.Request) (*http.Response, *batchResponse, error) {
res, err := DoRequest(req, cfg.PrivateAccess(auth.GetOperationForRequest(req)))
if err != nil {
if res != nil && res.StatusCode == 401 {
return res, nil, errors.NewAuthError(err)
}
return res, nil, err
}
resp := &batchResponse{}
err = httputil.DecodeResponse(res, resp)
if err != nil {
httputil.SetErrorResponseContext(cfg, err, res)
}
return res, resp, err
}
// DoRequest runs a request to the LFS API, without parsing the response
// body. If the API returns a 401, the repo will be marked as having private
// access and the request will be re-run. When the repo is marked as having
// private access, credentials will be retrieved.
func DoRequest(req *http.Request, useCreds bool) (*http.Response, error) {
via := make([]*http.Request, 0, 4)
return httputil.DoHttpRequestWithRedirects(config.Config, req, via, useCreds)
}
func NewRequest(cfg *config.Configuration, method, oid string) (*http.Request, error) {
objectOid := oid
operation := "download"
if method == "POST" {
if oid != "batch" {
objectOid = ""
operation = "upload"
}
}
res, endpoint, err := auth.SshAuthenticate(cfg, operation, oid)
if err != nil {
tracerx.Printf("ssh: %s with %s failed, error: %s, message: %s",
operation, endpoint.SshUserAndHost, err.Error(), res.Message,
)
return nil, errors.Wrap(errors.New(res.Message), err.Error())
}
if len(res.Href) > 0 {
endpoint.Url = res.Href
}
u, err := ObjectUrl(endpoint, objectOid)
if err != nil {
return nil, err
}
req, err := httputil.NewHttpRequest(method, u.String(), res.Header)
if err != nil {
return nil, err
}
req.Header.Set("Accept", MediaType)
return req, nil
}
func NewBatchRequest(cfg *config.Configuration, operation string) (*http.Request, error) {
res, endpoint, err := auth.SshAuthenticate(cfg, operation, "")
if err != nil {
tracerx.Printf("ssh: %s with %s failed, error: %s, message: %s",
operation, endpoint.SshUserAndHost, err.Error(), res.Message,
)
return nil, errors.Wrap(errors.New(res.Message), err.Error())
}
if len(res.Href) > 0 {
endpoint.Url = res.Href
}
u, err := ObjectUrl(endpoint, "batch")
if err != nil {
return nil, err
}
req, err := httputil.NewHttpRequest("POST", u.String(), nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", MediaType)
if res.Header != nil {
for key, value := range res.Header {
req.Header.Set(key, value)
}
}
return req, nil
}
func ObjectUrl(endpoint config.Endpoint, oid string) (*url.URL, error) {
u, err := url.Parse(endpoint.Url)
if err != nil {
return nil, err
}
u.Path = path.Join(u.Path, "objects")
if len(oid) > 0 {
u.Path = path.Join(u.Path, oid)
}
return u, nil
}