Skip to content

Commit

Permalink
detect scp-style git urls
Browse files Browse the repository at this point in the history
This detects URLs in the format of [email protected]:dir1/dir2 and
convert them to git::ssh://[email protected]/dir1/dir2.
  • Loading branch information
mitchellh committed Nov 17, 2018
1 parent 9fd5bfc commit 4e6a4f2
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 44 deletions.
1 change: 1 addition & 0 deletions detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var Detectors []Detector
func init() {
Detectors = []Detector{
new(GitHubDetector),
new(GitDetector),
new(BitBucketDetector),
new(S3Detector),
new(FileDetector),
Expand Down
26 changes: 26 additions & 0 deletions detect_git.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package getter

// GitDetector implements Detector to detect Git SSH URLs such as
// [email protected]:dir1/dir2 and converts them to proper URLs.
type GitDetector struct{}

func (d *GitDetector) Detect(src, _ string) (string, bool, error) {
if len(src) == 0 {
return "", false, nil
}

u, err := detectSSH(src)
if err != nil {
return "", true, err
}
if u == nil {
return "", false, nil
}

// We require the username to be "git" to assume that this is a Git URL
if u.User.Username() != "git" {
return "", false, nil
}

return "git::" + u.String(), true, nil
}
38 changes: 38 additions & 0 deletions detect_git_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package getter

import (
"testing"
)

func TestGitDetector(t *testing.T) {
cases := []struct {
Input string
Output string
}{
{"[email protected]:hashicorp/foo.git", "git::ssh://[email protected]/hashicorp/foo.git"},
{
"[email protected]:hashicorp/foo.git//bar",
"git::ssh://[email protected]/hashicorp/foo.git//bar",
},
{
"[email protected]:hashicorp/foo.git?foo=bar",
"git::ssh://[email protected]/hashicorp/foo.git?foo=bar",
},
}

pwd := "/pwd"
f := new(GitDetector)
for i, tc := range cases {
output, ok, err := f.Detect(tc.Input, pwd)
if err != nil {
t.Fatalf("err: %s", err)
}
if !ok {
t.Fatal("not ok")
}

if output != tc.Output {
t.Fatalf("%d: bad: %#v", i, output)
}
}
}
26 changes: 0 additions & 26 deletions detect_github.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ func (d *GitHubDetector) Detect(src, _ string) (string, bool, error) {

if strings.HasPrefix(src, "github.com/") {
return d.detectHTTP(src)
} else if strings.HasPrefix(src, "[email protected]:") {
return d.detectSSH(src)
}

return "", false, nil
Expand Down Expand Up @@ -47,27 +45,3 @@ func (d *GitHubDetector) detectHTTP(src string) (string, bool, error) {

return "git::" + url.String(), true, nil
}

func (d *GitHubDetector) detectSSH(src string) (string, bool, error) {
idx := strings.Index(src, ":")
qidx := strings.Index(src, "?")
if qidx == -1 {
qidx = len(src)
}

var u url.URL
u.Scheme = "ssh"
u.User = url.User("git")
u.Host = "github.com"
u.Path = src[idx+1 : qidx]
if qidx < len(src) {
q, err := url.ParseQuery(src[qidx+1:])
if err != nil {
return "", true, fmt.Errorf("error parsing GitHub SSH URL: %s", err)
}

u.RawQuery = q.Encode()
}

return "git::" + u.String(), true, nil
}
11 changes: 0 additions & 11 deletions detect_github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,6 @@ func TestGitHubDetector(t *testing.T) {
"github.com/hashicorp/foo.git?foo=bar",
"git::https://github.com/hashicorp/foo.git?foo=bar",
},

// SSH
{"[email protected]:hashicorp/foo.git", "git::ssh://[email protected]/hashicorp/foo.git"},
{
"[email protected]:hashicorp/foo.git//bar",
"git::ssh://[email protected]/hashicorp/foo.git//bar",
},
{
"[email protected]:hashicorp/foo.git?foo=bar",
"git::ssh://[email protected]/hashicorp/foo.git?foo=bar",
},
}

pwd := "/pwd"
Expand Down
49 changes: 49 additions & 0 deletions detect_ssh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package getter

import (
"fmt"
"net/url"
"regexp"
"strings"
)

// Note that we do not have an SSH-getter currently so this file serves
// only to hold the detectSSH helper that is used by other detectors.

// sshPattern matches SCP-like SSH patterns (user@host:path)
var sshPattern = regexp.MustCompile("^(?:([^@]+)@)?([^:]+):/?(.+)$")

// detectSSH determines if the src string matches an SSH-like URL and
// converts it into a net.URL compatible string. This returns nil if the
// string doesn't match the SSH pattern.
//
// This function is tested indirectly via detect_git_test.go
func detectSSH(src string) (*url.URL, error) {
matched := sshPattern.FindStringSubmatch(src)
if matched == nil {
return nil, nil
}

user := matched[1]
host := matched[2]
path := matched[3]
qidx := strings.Index(path, "?")
if qidx == -1 {
qidx = len(path)
}

var u url.URL
u.Scheme = "ssh"
u.User = url.User(user)
u.Host = host
u.Path = path[0:qidx]
if qidx < len(path) {
q, err := url.ParseQuery(path[qidx+1:])
if err != nil {
return nil, fmt.Errorf("error parsing GitHub SSH URL: %s", err)
}
u.RawQuery = q.Encode()
}

return &u, nil
}
49 changes: 42 additions & 7 deletions detect_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package getter

import (
"fmt"
"testing"
)

Expand Down Expand Up @@ -37,21 +38,55 @@ func TestDetect(t *testing.T) {
"git::https://github.com/hashicorp/consul.git",
false,
},
{
"git::https://[email protected]/foo/bar",
"",
"git::https://[email protected]/foo/bar",
false,
},
{
"git::https://[email protected]/foo/bar",
"/bar",
"git::https://[email protected]/foo/bar",
false,
},
{
"./foo/archive//*",
"/bar",
"file:///bar/foo/archive//*",
false,
},

// https://github.com/hashicorp/go-getter/pull/124
{
"git::ssh://[email protected]/dir1/dir2",
"",
"git::ssh://[email protected]/dir1/dir2",
false,
},
{
"git::[email protected]:dir1/dir2",
"/foo",
"git::ssh://[email protected]/dir1/dir2",
false,
},
{
"git::[email protected]:dir1/dir2",
"",
"git::ssh://[email protected]/dir1/dir2",
false,
},
}

for i, tc := range cases {
output, err := Detect(tc.Input, tc.Pwd, Detectors)
if err != nil != tc.Err {
t.Fatalf("%d: bad err: %s", i, err)
}
if output != tc.Output {
t.Fatalf("%d: bad output: %s\nexpected: %s", i, output, tc.Output)
}
t.Run(fmt.Sprintf("%d %s", i, tc.Input), func(t *testing.T) {
output, err := Detect(tc.Input, tc.Pwd, Detectors)
if err != nil != tc.Err {
t.Fatalf("%d: bad err: %s", i, err)
}
if output != tc.Output {
t.Fatalf("%d: bad output: %s\nexpected: %s", i, output, tc.Output)
}
})
}
}

0 comments on commit 4e6a4f2

Please sign in to comment.