Merge pull request #2080 from git-lfs/auth-caching
ssh auth and credential helper caching
This commit is contained in:
commit
1274d627bb
@ -45,6 +45,11 @@ be scoped inside the configuration for a remote.
|
|||||||
Sets the maximum time, in seconds, for the HTTP client to maintain keepalive
|
Sets the maximum time, in seconds, for the HTTP client to maintain keepalive
|
||||||
connections. Default: 30 minutes.
|
connections. Default: 30 minutes.
|
||||||
|
|
||||||
|
* `lfs.cachecredentials`
|
||||||
|
|
||||||
|
Enables in-memory SSH and Git Credential caching for a single 'git lfs'
|
||||||
|
command. Default: false. This will default to true in v2.1.0.
|
||||||
|
|
||||||
### Transfer (upload / download) settings
|
### Transfer (upload / download) settings
|
||||||
|
|
||||||
These settings control how the upload and download of LFS content occurs.
|
These settings control how the upload and download of LFS content occurs.
|
||||||
|
@ -18,7 +18,7 @@ var UserAgent = "git-lfs"
|
|||||||
const MediaType = "application/vnd.git-lfs+json; charset=utf-8"
|
const MediaType = "application/vnd.git-lfs+json; charset=utf-8"
|
||||||
|
|
||||||
func (c *Client) NewRequest(method string, e Endpoint, suffix string, body interface{}) (*http.Request, error) {
|
func (c *Client) NewRequest(method string, e Endpoint, suffix string, body interface{}) (*http.Request, error) {
|
||||||
sshRes, err := c.resolveSSHEndpoint(e, method)
|
sshRes, err := c.SSH.Resolve(e, method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tracerx.Printf("ssh: %s failed, error: %s, message: %s",
|
tracerx.Printf("ssh: %s failed, error: %s, message: %s",
|
||||||
e.SshUserAndHost, err.Error(), sshRes.Message,
|
e.SshUserAndHost, err.Error(), sshRes.Message,
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rubyist/tracerx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CredentialHelper interface {
|
type CredentialHelper interface {
|
||||||
@ -28,11 +30,62 @@ func bufferCreds(c Creds) *bytes.Buffer {
|
|||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withCredentialCache(helper CredentialHelper) CredentialHelper {
|
||||||
|
return &credentialCacher{
|
||||||
|
creds: make(map[string]Creds),
|
||||||
|
helper: helper,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type credentialCacher struct {
|
||||||
|
creds map[string]Creds
|
||||||
|
helper CredentialHelper
|
||||||
|
}
|
||||||
|
|
||||||
|
func credCacheKey(creds Creds) string {
|
||||||
|
parts := []string{
|
||||||
|
creds["protocol"],
|
||||||
|
creds["host"],
|
||||||
|
creds["path"],
|
||||||
|
}
|
||||||
|
return strings.Join(parts, "//")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *credentialCacher) Fill(creds Creds) (Creds, error) {
|
||||||
|
key := credCacheKey(creds)
|
||||||
|
if cache, ok := c.creds[key]; ok {
|
||||||
|
tracerx.Printf("creds: git credential cache (%q, %q, %q)",
|
||||||
|
creds["protocol"], creds["host"], creds["path"])
|
||||||
|
return cache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
creds, err := c.helper.Fill(creds)
|
||||||
|
if err == nil && len(creds["username"]) > 0 && len(creds["password"]) > 0 {
|
||||||
|
c.creds[key] = creds
|
||||||
|
}
|
||||||
|
return creds, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *credentialCacher) Reject(creds Creds) error {
|
||||||
|
delete(c.creds, credCacheKey(creds))
|
||||||
|
return c.helper.Reject(creds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *credentialCacher) Approve(creds Creds) error {
|
||||||
|
err := c.helper.Approve(creds)
|
||||||
|
if err == nil {
|
||||||
|
c.creds[credCacheKey(creds)] = creds
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
type commandCredentialHelper struct {
|
type commandCredentialHelper struct {
|
||||||
SkipPrompt bool
|
SkipPrompt bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *commandCredentialHelper) Fill(creds Creds) (Creds, error) {
|
func (h *commandCredentialHelper) Fill(creds Creds) (Creds, error) {
|
||||||
|
tracerx.Printf("creds: git credential fill (%q, %q, %q)",
|
||||||
|
creds["protocol"], creds["host"], creds["path"])
|
||||||
return h.exec("fill", creds)
|
return h.exec("fill", creds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
268
lfsapi/creds_test.go
Normal file
268
lfsapi/creds_test.go
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
package lfsapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// test that cache satisfies Fill() without looking at creds
|
||||||
|
func TestCredsCacheFillFromCache(t *testing.T) {
|
||||||
|
creds := newFakeCreds()
|
||||||
|
cache := withCredentialCache(creds).(*credentialCacher)
|
||||||
|
cache.creds["http//lfs.test//foo/bar"] = Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
"username": "u",
|
||||||
|
"password": "p",
|
||||||
|
}
|
||||||
|
|
||||||
|
filled, err := cache.Fill(Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
require.NotNil(t, filled)
|
||||||
|
assert.Equal(t, "u", filled["username"])
|
||||||
|
assert.Equal(t, "p", filled["password"])
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(cache.creds))
|
||||||
|
cached, ok := cache.creds["http//lfs.test//foo/bar"]
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "u", cached["username"])
|
||||||
|
assert.Equal(t, "p", cached["password"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// test that cache caches Fill() value from creds
|
||||||
|
func TestCredsCacheFillFromValidHelperFill(t *testing.T) {
|
||||||
|
creds := newFakeCreds()
|
||||||
|
cache := withCredentialCache(creds).(*credentialCacher)
|
||||||
|
|
||||||
|
creds.list = append(creds.list, Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
"username": "u",
|
||||||
|
"password": "p",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, 0, len(cache.creds))
|
||||||
|
|
||||||
|
filled, err := cache.Fill(Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
require.NotNil(t, filled)
|
||||||
|
assert.Equal(t, "u", filled["username"])
|
||||||
|
assert.Equal(t, "p", filled["password"])
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(cache.creds))
|
||||||
|
cached, ok := cache.creds["http//lfs.test//foo/bar"]
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "u", cached["username"])
|
||||||
|
assert.Equal(t, "p", cached["password"])
|
||||||
|
|
||||||
|
creds.list = make([]Creds, 0)
|
||||||
|
filled2, err := cache.Fill(Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
require.NotNil(t, filled2)
|
||||||
|
assert.Equal(t, "u", filled2["username"])
|
||||||
|
assert.Equal(t, "p", filled2["password"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// test that cache ignores Fill() value from creds with missing username+password
|
||||||
|
func TestCredsCacheFillFromInvalidHelperFill(t *testing.T) {
|
||||||
|
creds := newFakeCreds()
|
||||||
|
cache := withCredentialCache(creds).(*credentialCacher)
|
||||||
|
|
||||||
|
creds.list = append(creds.list, Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
"username": "no-password",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, 0, len(cache.creds))
|
||||||
|
|
||||||
|
filled, err := cache.Fill(Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
"username": "u",
|
||||||
|
"password": "p",
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
require.NotNil(t, filled)
|
||||||
|
assert.Equal(t, "no-password", filled["username"])
|
||||||
|
assert.Equal(t, "", filled["password"])
|
||||||
|
|
||||||
|
assert.Equal(t, 0, len(cache.creds))
|
||||||
|
}
|
||||||
|
|
||||||
|
// test that cache ignores Fill() value from creds with error
|
||||||
|
func TestCredsCacheFillFromErroringHelperFill(t *testing.T) {
|
||||||
|
creds := newFakeCreds()
|
||||||
|
cache := withCredentialCache(&erroringCreds{creds}).(*credentialCacher)
|
||||||
|
|
||||||
|
creds.list = append(creds.list, Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
"username": "u",
|
||||||
|
"password": "p",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, 0, len(cache.creds))
|
||||||
|
|
||||||
|
filled, err := cache.Fill(Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
require.NotNil(t, filled)
|
||||||
|
assert.Equal(t, "u", filled["username"])
|
||||||
|
assert.Equal(t, "p", filled["password"])
|
||||||
|
|
||||||
|
assert.Equal(t, 0, len(cache.creds))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCredsCacheRejectWithoutError(t *testing.T) {
|
||||||
|
creds := newFakeCreds()
|
||||||
|
cache := withCredentialCache(creds).(*credentialCacher)
|
||||||
|
|
||||||
|
cache.creds["http//lfs.test//foo/bar"] = Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
"username": "u",
|
||||||
|
"password": "p",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cache.Reject(Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 0, len(cache.creds))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCredsCacheRejectWithError(t *testing.T) {
|
||||||
|
creds := newFakeCreds()
|
||||||
|
cache := withCredentialCache(&erroringCreds{creds}).(*credentialCacher)
|
||||||
|
|
||||||
|
cache.creds["http//lfs.test//foo/bar"] = Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
"username": "u",
|
||||||
|
"password": "p",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cache.Reject(Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, 0, len(cache.creds))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCredsCacheApproveWithoutError(t *testing.T) {
|
||||||
|
creds := newFakeCreds()
|
||||||
|
cache := withCredentialCache(creds).(*credentialCacher)
|
||||||
|
|
||||||
|
assert.Equal(t, 0, len(cache.creds))
|
||||||
|
|
||||||
|
err := cache.Approve(Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
"username": "U",
|
||||||
|
"password": "P",
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 1, len(cache.creds))
|
||||||
|
cached, ok := cache.creds["http//lfs.test//foo/bar"]
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "U", cached["username"])
|
||||||
|
assert.Equal(t, "P", cached["password"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCredsCacheApproveWithError(t *testing.T) {
|
||||||
|
creds := newFakeCreds()
|
||||||
|
cache := withCredentialCache(&erroringCreds{creds}).(*credentialCacher)
|
||||||
|
|
||||||
|
assert.Equal(t, 0, len(cache.creds))
|
||||||
|
|
||||||
|
err := cache.Approve(Creds{
|
||||||
|
"protocol": "http",
|
||||||
|
"host": "lfs.test",
|
||||||
|
"path": "foo/bar",
|
||||||
|
"username": "u",
|
||||||
|
"password": "p",
|
||||||
|
})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, 0, len(cache.creds))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFakeCreds() *fakeCreds {
|
||||||
|
return &fakeCreds{list: make([]Creds, 0)}
|
||||||
|
}
|
||||||
|
|
||||||
|
type erroringCreds struct {
|
||||||
|
helper CredentialHelper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *erroringCreds) Fill(creds Creds) (Creds, error) {
|
||||||
|
c, _ := e.helper.Fill(creds)
|
||||||
|
return c, errors.New("fill error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *erroringCreds) Reject(creds Creds) error {
|
||||||
|
e.helper.Reject(creds)
|
||||||
|
return errors.New("reject error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *erroringCreds) Approve(creds Creds) error {
|
||||||
|
e.helper.Approve(creds)
|
||||||
|
return errors.New("approve error")
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeCreds struct {
|
||||||
|
list []Creds
|
||||||
|
}
|
||||||
|
|
||||||
|
func credsMatch(c1, c2 Creds) bool {
|
||||||
|
return c1["protocol"] == c2["protocol"] &&
|
||||||
|
c1["host"] == c2["host"] &&
|
||||||
|
c1["path"] == c2["path"]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeCreds) Fill(creds Creds) (Creds, error) {
|
||||||
|
for _, saved := range f.list {
|
||||||
|
if credsMatch(creds, saved) {
|
||||||
|
return saved, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return creds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeCreds) Reject(creds Creds) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeCreds) Approve(creds Creds) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -22,6 +22,7 @@ var (
|
|||||||
type Client struct {
|
type Client struct {
|
||||||
Endpoints EndpointFinder
|
Endpoints EndpointFinder
|
||||||
Credentials CredentialHelper
|
Credentials CredentialHelper
|
||||||
|
SSH SSHResolver
|
||||||
Netrc NetrcFinder
|
Netrc NetrcFinder
|
||||||
|
|
||||||
DialTimeout int
|
DialTimeout int
|
||||||
@ -70,11 +71,20 @@ func NewClient(osEnv Env, gitEnv Env) (*Client, error) {
|
|||||||
|
|
||||||
httpsProxy, httpProxy, noProxy := getProxyServers(osEnv, gitEnv)
|
httpsProxy, httpProxy, noProxy := getProxyServers(osEnv, gitEnv)
|
||||||
|
|
||||||
|
var creds CredentialHelper = &commandCredentialHelper{
|
||||||
|
SkipPrompt: !osEnv.Bool("GIT_TERMINAL_PROMPT", true),
|
||||||
|
}
|
||||||
|
var sshResolver SSHResolver = &sshAuthClient{os: osEnv}
|
||||||
|
|
||||||
|
if gitEnv.Bool("lfs.cachecredentials", false) {
|
||||||
|
creds = withCredentialCache(creds)
|
||||||
|
sshResolver = withSSHCache(sshResolver)
|
||||||
|
}
|
||||||
|
|
||||||
c := &Client{
|
c := &Client{
|
||||||
Endpoints: NewEndpointFinder(gitEnv),
|
Endpoints: NewEndpointFinder(gitEnv),
|
||||||
Credentials: &commandCredentialHelper{
|
Credentials: creds,
|
||||||
SkipPrompt: !osEnv.Bool("GIT_TERMINAL_PROMPT", true),
|
SSH: sshResolver,
|
||||||
},
|
|
||||||
Netrc: netrc,
|
Netrc: netrc,
|
||||||
DialTimeout: gitEnv.Int("lfs.dialtimeout", 0),
|
DialTimeout: gitEnv.Int("lfs.dialtimeout", 0),
|
||||||
KeepaliveTimeout: gitEnv.Int("lfs.keepalive", 0),
|
KeepaliveTimeout: gitEnv.Int("lfs.keepalive", 0),
|
||||||
|
@ -7,18 +7,65 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/git-lfs/git-lfs/tools"
|
"github.com/git-lfs/git-lfs/tools"
|
||||||
"github.com/rubyist/tracerx"
|
"github.com/rubyist/tracerx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) resolveSSHEndpoint(e Endpoint, method string) (sshAuthResponse, error) {
|
type SSHResolver interface {
|
||||||
|
Resolve(Endpoint, string) (sshAuthResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withSSHCache(ssh SSHResolver) SSHResolver {
|
||||||
|
return &sshCache{
|
||||||
|
endpoints: make(map[string]*sshAuthResponse),
|
||||||
|
ssh: ssh,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshCache struct {
|
||||||
|
endpoints map[string]*sshAuthResponse
|
||||||
|
ssh SSHResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sshCache) Resolve(e Endpoint, method string) (sshAuthResponse, error) {
|
||||||
|
if len(e.SshUserAndHost) == 0 {
|
||||||
|
return sshAuthResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
key := strings.Join([]string{e.SshUserAndHost, e.SshPort, e.SshPath, method}, "//")
|
||||||
|
if res, ok := c.endpoints[key]; ok && (res.ExpiresAt.IsZero() || time.Until(res.ExpiresAt) > 5*time.Second) {
|
||||||
|
tracerx.Printf("ssh cache: %s git-lfs-authenticate %s %s",
|
||||||
|
e.SshUserAndHost, e.SshPath, endpointOperation(e, method))
|
||||||
|
return *res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.ssh.Resolve(e, method)
|
||||||
|
if err == nil {
|
||||||
|
c.endpoints[key] = &res
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshAuthResponse struct {
|
||||||
|
Message string `json:"-"`
|
||||||
|
Href string `json:"href"`
|
||||||
|
Header map[string]string `json:"header"`
|
||||||
|
ExpiresAt time.Time `json:"expires_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshAuthClient struct {
|
||||||
|
os Env
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sshAuthClient) Resolve(e Endpoint, method string) (sshAuthResponse, error) {
|
||||||
res := sshAuthResponse{}
|
res := sshAuthResponse{}
|
||||||
if len(e.SshUserAndHost) == 0 {
|
if len(e.SshUserAndHost) == 0 {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
exe, args := sshGetLFSExeAndArgs(c.osEnv, e, method)
|
exe, args := sshGetLFSExeAndArgs(c.os, e, method)
|
||||||
cmd := exec.Command(exe, args...)
|
cmd := exec.Command(exe, args...)
|
||||||
|
|
||||||
// Save stdout and stderr in separate buffers
|
// Save stdout and stderr in separate buffers
|
||||||
@ -42,13 +89,6 @@ func (c *Client) resolveSSHEndpoint(e Endpoint, method string) (sshAuthResponse,
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type sshAuthResponse struct {
|
|
||||||
Message string `json:"-"`
|
|
||||||
Href string `json:"href"`
|
|
||||||
Header map[string]string `json:"header"`
|
|
||||||
ExpiresAt string `json:"expires_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func sshGetLFSExeAndArgs(osEnv Env, e Endpoint, method string) (string, []string) {
|
func sshGetLFSExeAndArgs(osEnv Env, e Endpoint, method string) (string, []string) {
|
||||||
operation := endpointOperation(e, method)
|
operation := endpointOperation(e, method)
|
||||||
tracerx.Printf("ssh: %s git-lfs-authenticate %s %s",
|
tracerx.Printf("ssh: %s git-lfs-authenticate %s %s",
|
||||||
|
@ -1,13 +1,146 @@
|
|||||||
package lfsapi
|
package lfsapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestSSHCacheResolveFromCache(t *testing.T) {
|
||||||
|
ssh := newFakeResolver()
|
||||||
|
cache := withSSHCache(ssh).(*sshCache)
|
||||||
|
cache.endpoints["userandhost//1//path//post"] = &sshAuthResponse{
|
||||||
|
Href: "cache",
|
||||||
|
}
|
||||||
|
ssh.responses["userandhost"] = sshAuthResponse{Href: "real"}
|
||||||
|
|
||||||
|
e := Endpoint{
|
||||||
|
SshUserAndHost: "userandhost",
|
||||||
|
SshPort: "1",
|
||||||
|
SshPath: "path",
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := cache.Resolve(e, "post")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "cache", res.Href)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHCacheResolveFromCacheWithFutureExpiresAt(t *testing.T) {
|
||||||
|
ssh := newFakeResolver()
|
||||||
|
cache := withSSHCache(ssh).(*sshCache)
|
||||||
|
cache.endpoints["userandhost//1//path//post"] = &sshAuthResponse{
|
||||||
|
Href: "cache",
|
||||||
|
ExpiresAt: time.Now().Add(time.Duration(1) * time.Hour),
|
||||||
|
}
|
||||||
|
ssh.responses["userandhost"] = sshAuthResponse{Href: "real"}
|
||||||
|
|
||||||
|
e := Endpoint{
|
||||||
|
SshUserAndHost: "userandhost",
|
||||||
|
SshPort: "1",
|
||||||
|
SshPath: "path",
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := cache.Resolve(e, "post")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "cache", res.Href)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHCacheResolveFromCacheWithPastExpiresAt(t *testing.T) {
|
||||||
|
ssh := newFakeResolver()
|
||||||
|
cache := withSSHCache(ssh).(*sshCache)
|
||||||
|
cache.endpoints["userandhost//1//path//post"] = &sshAuthResponse{
|
||||||
|
Href: "cache",
|
||||||
|
ExpiresAt: time.Now().Add(time.Duration(-1) * time.Hour),
|
||||||
|
}
|
||||||
|
ssh.responses["userandhost"] = sshAuthResponse{Href: "real"}
|
||||||
|
|
||||||
|
e := Endpoint{
|
||||||
|
SshUserAndHost: "userandhost",
|
||||||
|
SshPort: "1",
|
||||||
|
SshPath: "path",
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := cache.Resolve(e, "post")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "real", res.Href)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHCacheResolveWithoutError(t *testing.T) {
|
||||||
|
ssh := newFakeResolver()
|
||||||
|
cache := withSSHCache(ssh).(*sshCache)
|
||||||
|
|
||||||
|
assert.Equal(t, 0, len(cache.endpoints))
|
||||||
|
|
||||||
|
ssh.responses["userandhost"] = sshAuthResponse{Href: "real"}
|
||||||
|
|
||||||
|
e := Endpoint{
|
||||||
|
SshUserAndHost: "userandhost",
|
||||||
|
SshPort: "1",
|
||||||
|
SshPath: "path",
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := cache.Resolve(e, "post")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "real", res.Href)
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(cache.endpoints))
|
||||||
|
cacheres, ok := cache.endpoints["userandhost//1//path//post"]
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.NotNil(t, cacheres)
|
||||||
|
assert.Equal(t, "real", cacheres.Href)
|
||||||
|
|
||||||
|
delete(ssh.responses, "userandhost")
|
||||||
|
res2, err := cache.Resolve(e, "post")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "real", res2.Href)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHCacheResolveWithError(t *testing.T) {
|
||||||
|
ssh := newFakeResolver()
|
||||||
|
cache := withSSHCache(ssh).(*sshCache)
|
||||||
|
|
||||||
|
assert.Equal(t, 0, len(cache.endpoints))
|
||||||
|
|
||||||
|
ssh.responses["userandhost"] = sshAuthResponse{Message: "resolve error", Href: "real"}
|
||||||
|
|
||||||
|
e := Endpoint{
|
||||||
|
SshUserAndHost: "userandhost",
|
||||||
|
SshPort: "1",
|
||||||
|
SshPath: "path",
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := cache.Resolve(e, "post")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "real", res.Href)
|
||||||
|
|
||||||
|
assert.Equal(t, 0, len(cache.endpoints))
|
||||||
|
delete(ssh.responses, "userandhost")
|
||||||
|
res2, err := cache.Resolve(e, "post")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "", res2.Href)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFakeResolver() *fakeResolver {
|
||||||
|
return &fakeResolver{responses: make(map[string]sshAuthResponse)}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeResolver struct {
|
||||||
|
responses map[string]sshAuthResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fakeResolver) Resolve(e Endpoint, method string) (sshAuthResponse, error) {
|
||||||
|
res := r.responses[e.SshUserAndHost]
|
||||||
|
var err error
|
||||||
|
if len(res.Message) > 0 {
|
||||||
|
err = errors.New(res.Message)
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
func TestSSHGetLFSExeAndArgs(t *testing.T) {
|
func TestSSHGetLFSExeAndArgs(t *testing.T) {
|
||||||
cli, err := NewClient(TestEnv(map[string]string{}), nil)
|
cli, err := NewClient(TestEnv(map[string]string{}), nil)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
Loading…
Reference in New Issue
Block a user