Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Allow SCP-style URLs for Git #129

Merged
merged 3 commits into from
Nov 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
// git@host.com: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
}
58 changes: 58 additions & 0 deletions detect_git_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package getter

import (
"testing"
)

func TestGitDetector(t *testing.T) {
cases := []struct {
Input string
Output string
}{
{"git@github.com:hashicorp/foo.git", "git::ssh://git@github.com/hashicorp/foo.git"},
{
"git@github.com:org/project.git?ref=test-branch",
"git::ssh://git@github.com/org/project.git?ref=test-branch",
},
{
"git@github.com:hashicorp/foo.git//bar",
"git::ssh://git@github.com/hashicorp/foo.git//bar",
},
{
"git@github.com:hashicorp/foo.git?foo=bar",
"git::ssh://git@github.com/hashicorp/foo.git?foo=bar",
},
{
"git@github.xyz.com:org/project.git",
"git::ssh://git@github.xyz.com/org/project.git",
},
{
"git@github.xyz.com:org/project.git?ref=test-branch",
"git::ssh://git@github.xyz.com/org/project.git?ref=test-branch",
},
{
"git@github.xyz.com:org/project.git//module/a",
"git::ssh://git@github.xyz.com/org/project.git//module/a",
},
{
"git@github.xyz.com:org/project.git//module/a?ref=test-branch",
"git::ssh://git@github.xyz.com/org/project.git//module/a?ref=test-branch",
},
}

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, "git@github.com:") {
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
{"git@github.com:hashicorp/foo.git", "git::ssh://git@github.com/hashicorp/foo.git"},
{
"git@github.com:hashicorp/foo.git//bar",
"git::ssh://git@github.com/hashicorp/foo.git//bar",
},
{
"git@github.com:hashicorp/foo.git?foo=bar",
"git::ssh://git@github.com/hashicorp/foo.git?foo=bar",
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests were moved to detect_git_test.go, not removed.

}

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://person@someothergit.com/foo/bar",
"",
"git::https://person@someothergit.com/foo/bar",
false,
},
{
"git::https://person@someothergit.com/foo/bar",
"/bar",
"git::https://person@someothergit.com/foo/bar",
false,
},
{
"./foo/archive//*",
"/bar",
"file:///bar/foo/archive//*",
false,
},

// https://github.com/hashicorp/go-getter/pull/124
{
"git::ssh://git@my.custom.git/dir1/dir2",
"",
"git::ssh://git@my.custom.git/dir1/dir2",
false,
},
{
"git::git@my.custom.git:dir1/dir2",
"/foo",
"git::ssh://git@my.custom.git/dir1/dir2",
false,
},
{
"git::git@my.custom.git:dir1/dir2",
"",
"git::ssh://git@my.custom.git/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)
}
})
}
}
20 changes: 20 additions & 0 deletions get_git.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,26 @@ func (g *GitGetter) Get(dst string, u *url.URL) error {
}
}

// For SSH-style URLs, if they use the SCP syntax of host:path, then
// the URL will be mangled. We detect that here and correct the path.
// Example: host:path/bar will turn into host/path/bar
if u.Scheme == "ssh" {
if idx := strings.Index(u.Host, ":"); idx > -1 {
// Copy the URL so we don't modify the input
var newU url.URL = *u
u = &newU

// Path includes the part after the ':'.
u.Path = u.Host[idx+1:] + u.Path
if u.Path[0] != '/' {
u.Path = "/" + u.Path
}

// Host trims up to the :
u.Host = u.Host[:idx]
}
}

// Clone or update the repository
_, err := os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
Expand Down